【问题标题】:Is there a way to use a windowing function to keep a minimum total of 0 instead of having to have a loop?有没有办法使用窗口函数来保持最小总数为 0 而不必有一个循环?
【发布时间】:2020-02-11 14:06:18
【问题描述】:

我试图避免使用循环来获得运行总计。这是一个简化版本;真实版本包括几个类别的小计。我知道我可以使用窗口函数 [@Xata2] 来做到这一点,但是如果您将总数限制为非负数(即,如果总数为负数,则使用 0相反)[@Xata3],因为我放置的任何条件都在当前行上,而不是累积总数。

    DECLARE   @Xata TABLE (
          ID            INTEGER IDENTITY
        , result        INTEGER
        )

    DECLARE   @Xata2 TABLE (
          ID            INTEGER IDENTITY
        , result        INTEGER
        , total         INTEGER
        )

    DECLARE   @Xata3 TABLE (
          ID            INTEGER IDENTITY
        , result        INTEGER
        , total         INTEGER
        )

    DECLARE   @result   INTEGER
            , @total    INTEGER
            , @Counter  INTEGER

    INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
    INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
    INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
    INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
    INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
    INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
    INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
    INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
    INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
    INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)

    -- @Xata2: WINDOWING FUNCTION

    INSERT INTO @Xata2
        SELECT    result
                , SUM(result) OVER (ORDER BY ID)
            FROM @Xata

    SELECT    ID
            , result
            , total AS total_x2_neg
        FROM @Xata2

    SET @Counter = 0

    WHILE @Counter < (SELECT MAX(ID) FROM @Xata)
        BEGIN
            SET @Counter += 1

            SELECT @result = result FROM @Xata WHERE ID = @Counter
            SET @total = ISNULL((SELECT total FROM @Xata3 WHERE ID = @Counter - 1), 0)

            INSERT INTO @Xata3
                SELECT    @result
                        , IIF(@result + @total < 0, 0, @result + @total)

        END

    SELECT    ID
            , result
            , total AS total_x3_noneg
        FROM @Xata3

【问题讨论】:

  • 你能解释一下你的查询背后的逻辑是什么以及它应该做什么?
  • 这是我很久以来见过的最好的复制品。点赞
  • @Nick.McDermaid 我也很欣赏复制品,但我更喜欢修复值来修复输出。如果解决方案正确返回,则在此处使用随机值会使查看变得更加困难...无论如何,从我这边+1。
  • @DaveX 假设第 7 行包含结果-6total_x3_noneg 应该是 0 还是 5
  • @Menno,我认为第 7 到第 8 行回答了您的问题。我们有一个 1 和-3 的结果,我们看到0。我认为这是:计算减法,但永远不会低于零。

标签: sql-server tsql window-functions


【解决方案1】:

编辑

我把整个事情都包装在另一个运行总数中。

顺便说一句,优秀的复制品 - 我希望每个人都像这样工作

 DECLARE   @Xata TABLE (
      ID            INTEGER IDENTITY
    , result        INTEGER
    )



INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)
INSERT INTO @Xata (result) SELECT ROUND(RAND()* 9 - 4.5, 0)


SELECT x.ID, x.result, x.RT1, SUM(IIF(x.RT1<0,0,x.RT1)) OVER (ORDER BY ID) as RT2
from (
        SELECT  ID,  result
            , SUM(result) OVER (ORDER BY ID) as RT1
        FROM @Xata
    ) as x
ORDER BY x.ID

【讨论】:

  • 这不会导致第 2 行的值 1,对吗?
  • 样本数据是随机的,所以第 2 行总是不同的。但是对于1 的输入值(在r1 字段中),是的,它返回1。为什么不自己运行代码?
  • hmmm 等一下 - 你现在是我重新检查的,你是对的,它不会返回 OP 之后的内容
  • @Nick.McDermaid 不,您编辑的版本没有按预期返回。您可以尝试我的回答中的 mockup 以使用与问题中使用的相同的值...
【解决方案2】:

我的第一次尝试没有正确返回,谢谢@Menno 的提示。

递归 CTE 尝试这种方法

DECLARE   @Xata TABLE (ID INTEGER IDENTITY
                      ,result INTEGER);

INSERT INTO @Xata (result) VALUES(-3),(1),(2),(2),(0),(0),(-4),(-3),(2),(3);

WITH recCte AS
(
    SELECT ID
          ,result
          ,CASE WHEN result>0 THEN result ELSE 0 END AS runningTotalNoNeg 
    FROM @Xata 
    WHERE ID=1

    UNION ALL

    SELECT d.ID
          ,d.result
          ,CASE WHEN r.runningTotalNoNeg + d.result > 0 then r.runningTotalNoNeg + d.result ELSE 0 END
    FROM @Xata d
    INNER JOIN recCte r ON d.ID=r.ID+1
)
SELECT *
FROM recCte;

结果

ID  rt  runningTotalNoNeg
1  -3   0
2   1   1
3   2   3
4   2   5
5   0   5
6   0   5
7  -4   1
8  -3   0
9   2   2
10  3   5

【讨论】:

  • 复杂性在于例如第 7 行中的给定数据。@DaveX 之后的逻辑是运行总数可能不会低于 0。
  • +1 我也没有看到带有窗口函数的工作方法,递归 CTE 可能是最优雅的解决方案。
【解决方案3】:

尝试使用sum窗口函数和case语句:

select id, result,
       case when [sum] < 0 then 0 else [sum]
from (
    select id,
           result,
           sum(result) over (order by id) [sum]
    from MyTable
) a

【讨论】:

  • 这正是我必须改变之前的方法。 OP希望使用负值但不低于零。所以仅仅总结正值是不够的......在给定的示例中,-4 用于从5 步进到1。如果 5 为 4 或更少,它应该降级为 0...
  • 抱歉,您的编辑无法修复此问题。你可以用我的答案的 mockup 来试试这个......仅仅抑制负总和值是不够的......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-01-31
  • 1970-01-01
  • 2023-02-09
  • 2023-03-04
  • 1970-01-01
  • 1970-01-01
  • 2021-04-28
相关资源
最近更新 更多