【问题标题】:Window Functions - Running Total with reset窗口函数 - 运行总计并重置
【发布时间】:2014-04-09 18:23:19
【问题描述】:

我正在使用 SQL Server 2012 构建库存计划/再订购引擎。

我有一堆过时的交易,称它们为贷方和借方。我想同时做两件事:

  1. 生成运行总计(每日净余额)
  2. 生成补充建议。 Replenish 将重置 Running 总计(在 #1 中)归零。

表格如下所示:

CREATE TABLE TX (TDate DATETIME, Qty   INT);

INSERT INTO TX VALUES ('2014-03-01', 20);  
INSERT INTO TX VALUES ('2014-03-02',-10); 
INSERT INTO TX VALUES ('2014-03-03',-20); 
INSERT INTO TX VALUES ('2014-03-04',-10); 
INSERT INTO TX VALUES ('2014-03-05', 30); 
INSERT INTO TX VALUES ('2014-03-06',-20);  
INSERT INTO TX VALUES ('2014-03-07', 10);  
INSERT INTO TX VALUES ('2014-03-08',-20); 
INSERT INTO TX VALUES ('2014-03-09', -5);  

我正在使用 SQL 2012 SUM OVER() 窗口函数来显示这些的运行总计。

select TDate, Qty, RunningTotal, RecommendedReplenish from (
    select 
        TDate, 
        Qty, 
        SUM(Qty) OVER (ORDER BY TDate ROWS UNBOUNDED PRECEDING)  as RunningTotal,
        -1 * (CASE WHEN Qty < 0 AND SUM(Qty) OVER (ORDER BY TDate ROWS UNBOUNDED     PRECEDING) < 0 
                THEN 
            CASE WHEN Qty >  SUM(Qty) OVER (ORDER BY TDate ROWS UNBOUNDED PRECEDING)     THEN Qty ELSE SUM(Qty) OVER (ORDER                        BY TDate ROWS UNBOUNDED PRECEDING) END
        ELSE 0 END) as RecommendedReplenish
        /* Wrong, does not account for balance resetting to zero */
    from TX 
) T order by TDate

如果它低于零,我需要找到一种方法将运行总计(又名 RT)重置为零。

我的查询中,Qty 和 RT 均为负数,并将其中较大的(负数较小的)作为第一次推荐的补充。这第一次可以正常工作。

我不知道如何从窗口运行总数中扣除这个。如果可能的话,我想在一个语句中做到这一点。

这是我正在寻找的输出的摘要:

TDate        Qty    R.Tot  Replenish     New RT
-----------  ----   -----  -----------  ---------
3/1/2014     20      20                    20
3/2/2014    -10      10                    10
3/3/2014    -20     -10       10            0
3/4/2014    -10     -20       10            0
3/5/2014     30      10                    30
3/6/2014    -20     -10                    10
3/7/2014     10       0                    20
3/8/2014    -20     -20                     0
3/9/2014    - 5     -25        5            0

Itzik Ben-Gan、Joe Celko 或其他 SQL 英雄,你在吗? :)

提前致谢!

【问题讨论】:

标签: sql sql-server tsql sql-server-2012


【解决方案1】:

这可以使用基于集合的解决方案来完成:

1.计算正常运行总和(称为RT)

2.计算RT的运行最小值(称为MN)

当 MN 为负数时,-MN 是到目前为止您必须补充的总量。当 MN 为负时,让replep_rt 为 -MN。因此,新的运行总计(称为 new_rt)是 rt +replenished_rt。如果您需要返回当前需要的补货数量,请从当前减去前一次的replenishment_rt(使用LAG)。

这是完整的解决方案查询:

与 c1 作为 ( 选择 *, sum(qty) over(order by tdate rows unbounded before) as rt 来自德克萨斯州 ), c2 作为 ( 选择 *, -- 负数时,mn 是总数量 -- 补充到现在,包括 min(rt) over(order by tdate rows unbounded before) as mn_cur 从 c1 ) 选择 tdate, qty, rt, replace_rt - lag(replenish_rt, 1, 0) over(order by tdate) 作为补充, rt + 补充_rt 作为 new_rt 从 c2 交叉应用(值(当 mn_cur 干杯, 伊齐克

【讨论】:

    【解决方案2】:

    呃,根据你的 cmets,我唯一能想到的就是使用光标,我讨厌这样做。

    SQL Fiddle
    declare @Date date
    declare @Qty int
    declare @RR int
    
    
    declare @running int  = 0
    
    declare @results table
    (dt date,
     qty int,
     rt int,
     rr int
    )
    
    declare C cursor for
    select TDate, Qty,
    RecommendedReplenish 
    from (
        select 
            TDate, 
            Qty,
            -1 * (CASE WHEN Qty < 0 AND SUM(Qty) OVER (ORDER BY TDate ROWS UNBOUNDED     PRECEDING) < 0 
                    THEN 
                CASE WHEN Qty >  SUM(Qty) OVER (ORDER BY TDate ROWS UNBOUNDED PRECEDING)     THEN Qty ELSE SUM(Qty) OVER (ORDER                        BY TDate ROWS UNBOUNDED PRECEDING) END
            ELSE 0 END) as RecommendedReplenish
            /* Wrong, does not account for balance resetting to zero */
        from TX 
    ) T order by TDate
    
    open c
    fetch next from c into @date,@qty,@rr
    WHILE @@FETCH_STATUS = 0
    BEGIN
    
    
    
      set @running = @running + @qty
      if @running <0
        begin
          set @running = 0
        end
    
      insert into @results values (@date,@qty,@running,@rr)
    
      fetch next from c into @date,@qty,@rr
    end
    close c
    deallocate c
    select
    *
    from @results
    

    据我所知,这可以为您提供所需的结果。它不漂亮,我确信它可以使用一些清理,但它有效。

    +-------------+------+-----+----+
    |     DT      | QTY  | RT  | RR |
    +-------------+------+-----+----+
    | 2014-03-01  |  20  | 20  |  0 |
    | 2014-03-02  | -10  | 10  |  0 |
    | 2014-03-03  | -20  |  0  | 10 |
    | 2014-03-04  | -10  |  0  | 10 |
    | 2014-03-05  |  30  | 30  |  0 |
    | 2014-03-06  | -20  | 10  | 10 |
    | 2014-03-07  |  10  | 20  |  0 |
    | 2014-03-08  | -20  |  0  | 20 |
    | 2014-03-09  |  -5  |  0  |  5 |
    +-------------+------+-----+----+
    

    【讨论】:

    • 感谢您的建议。不幸的是,它并不是那么简单......我包括了一个所需的输出样本。问题是 SUM(Qty) 既用于运行总计,又用于确定补充列。尝试将 Replenish 添加到 Running Total 会引入循环引用。
    • 好的,根据您的示例,我只能考虑使用光标。基于此修改了我的答案。
    • 呃确实.. 似乎没有办法,只能使用光标。您的代码具有正确的 RT,RR 就像这样简单..(剥离符号): set RR = null set running = running + qty if running
    【解决方案3】:

    使用临时表,您可以随时应用补货。不确定它是否会比@Andrew 的游标方法快得多;可能取决于 RT 跌至零以下的频率。我使用了一个简单的子查询来计算 RT,输入更少,结果相同,尽管我同意它需要额外的步骤。

    SQL Fiddle

    CREATE TABLE TX (TDate DATETIME, Qty   INT, Replenish INT NULL, RT INT NULL);
    
    INSERT INTO TX VALUES ('2014-03-01', 20, NULL, NULL);  
    INSERT INTO TX VALUES ('2014-03-02',-10, NULL, NULL); 
    INSERT INTO TX VALUES ('2014-03-03',-20, NULL, NULL); 
    INSERT INTO TX VALUES ('2014-03-04',-10, NULL, NULL); 
    INSERT INTO TX VALUES ('2014-03-05', 30, NULL, NULL); 
    INSERT INTO TX VALUES ('2014-03-06',-20, NULL, NULL);  
    INSERT INTO TX VALUES ('2014-03-07', 10, NULL, NULL);  
    INSERT INTO TX VALUES ('2014-03-08',-20, NULL, NULL); 
    INSERT INTO TX VALUES ('2014-03-09', -5, NULL, NULL);
    GO
    
    -- calculate (real) running-totals
    UPDATE TX 
       SET RT = (SELECT SUM(p.Qty)
                   FROM TX p
                  WHERE p.TDate <= upd.TDate)
      FROM TX upd
    GO
    
    -- create a loop to find if there are negative RT's and fix them untill there are none left
    DECLARE @below_zero_date DATETIME,
            @below_zero_value INT
    
    -- SELECT * FROM TX ORDER BY TDate
    
    SELECT @below_zero_value = NULL
    SELECT TOP 1 @below_zero_date = TDate,
                 @below_zero_value = RT
      FROM TX
     WHERE RT < 0
     ORDER BY TDate
    
    WHILE @below_zero_value IS NOT NULL
        BEGIN
            UPDATE TX
               SET RT = RT - @below_zero_value,
                   Replenish = (CASE TDate WHEN @below_zero_date THEN - @below_zero_value ELSE NULL END)
             WHERE TDate >= @below_zero_date
    
            -- SELECT * FROM TX ORDER BY TDate
    
            SELECT @below_zero_value = NULL
    
            SELECT TOP 1 @below_zero_date = TDate,
                         @below_zero_value = RT
              FROM TX
             WHERE RT < 0
               AND TDate > @below_zero_date
             ORDER BY TDate
        END
    
    SELECT * FROM TX ORDER BY TDate
    

    更新:添加AND TDate &gt; @below_zero_date 作为(次要)改进;只有当表中有“相当多”的数据时,它才会产生显着的效果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-30
      • 1970-01-01
      • 1970-01-01
      • 2021-08-22
      • 1970-01-01
      • 2021-12-16
      • 1970-01-01
      相关资源
      最近更新 更多