窗口功能 - 通过重置运行总计

时间:2014-03-06 21:06:14

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

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

我有一堆过时的交易,称之为信用和借记。我想一次做两件事:

  1. 生成运行总计(每日净余额)
  2. 生成补充建议。补货将重置正在运行 总计(#1)回到零。
  3. 表格如下:

    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)重置为零。

    我的查询,其中数量和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英雄,你在外面吗? :)

    提前致谢!

3 个答案:

答案 0 :(得分:12)

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

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

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

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

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

with c1 as
(
  select *,
    sum(qty) over(order by tdate rows unbounded preceding) as rt
  from tx
),
c2 as
(
  select *,
    -- when negative, mn is the total qty that had to be
    -- replenished until now, inclusive
    min(rt) over(order by tdate rows unbounded preceding) as mn_cur
  from c1
)
select tdate, qty, rt,
  replenish_rt - lag(replenish_rt, 1, 0) over(order by tdate) as replenish,
  rt + replenish_rt as new_rt
from c2
  cross apply(values(case when mn_cur < 0 then -mn_cur else 0 end)) as a1(replenish_rt);
干杯, 伊茨克

答案 1 :(得分:1)

呃,基于你的评论,我唯一能想到的就是使用一个我讨厌做的光标。

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 |
+-------------+------+-----+----+

答案 2 :(得分:1)

使用临时表,您可以随时应用补货。不确定它是否比@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 > @below_zero_date作为(次要)改进;只有在表格中有“相当多”的数据时,它才会产生重大影响。