SQL Server:分解混合集(运行总计和值)中的运行总计

时间:2014-11-07 13:48:28

标签: sql sql-server sql-server-2008 tsql running-total

我得到了一个包含以下列的表:ID,IsRunningTotal和Amount(就像在SQL示例的CTE中一样)。 Amount表示由IsRunningTotal标志标识的值或RunningTotal。

如果您想知道UseCase只是想象ID代表一年中的月份(例如ID:1 = 2014年1月),那么某个月的金额将作为RunningTotal(例如3月的3000)或仅作为一个值(例如1月份的1000)。

所以给出了以下示例DataSet:

ID   IsRunTot   Amount  
1    0          1000
2    0          1000
3    1          3000
4    1          4000
5    0          1000
6    0          1000
7    1          7000
8    1          8000

现在我想分解RunningTotals来获取每个ID的简单值(这里每行1000个)。 像:

ID   IsRunTot   Amount   Result
1    0          1000     1000
2    0          1000     1000
3    1          3000     1000
4    1          4000     1000
5    0          1000     1000
6    0          1000     1000
7    1          7000     1000
8    1          8000     1000

现在我得到了这个Mssql查询"正在进行中" (为SQL Server 2008 R2编写):

WITH MySet (ID, IsRunTot, Amount)
AS
(
 SELECT 1 AS ID, 0 AS IsRunTot, 1000 AS Amount      
   UNION
 SELECT 2 AS ID, 0 AS IsRunTot, 1000 AS Amount      
   UNION
 SELECT 3 AS ID, 1 AS IsRunTot, 3000 AS Amount      
   UNION
 SELECT 4 AS ID, 1 AS IsRunTot, 4000 AS Amount      
   UNION
 SELECT 5 AS ID, 0 AS IsRunTot, 1000 AS Amount      
   UNION
 SELECT 6 AS ID, 0 AS IsRunTot, 1000 AS Amount      
   UNION
 SELECT 7 AS ID, 1 AS IsRunTot, 7000 AS Amount      
   UNION
 SELECT 8 AS ID, 1 AS IsRunTot, 8000 AS Amount      
)
, MySet2 (ID, IsRunTot, Amount, BreakDown)
AS
(
   SELECT ID, IsRunTot, Amount, Amount AS BreakDown 
   FROM MySet WHERE ID = 1                      

      UNION ALL

   SELECT A.ID, A.IsRunTot, A.Amount
   , CASE WHEN A.IsRunTot = 1 AND B.IsRunTot = 1 THEN  A.Amount - B.Amount ELSE NULL END AS    BreakDown 
   FROM MySet A
   INNER JOIN MySet B
    ON A.ID - 1 = B.ID
)
SELECT *
FROM MySet2
OPTION (MAXRECURSION 32767);

如果前任是一个正在运行的Total并且产生以下结果,则该方法有效:

ID   IsRunTot   Amount   BreakDown
1    0          1000     1000
2    0          1000     NULL
3    1          3000     NULL
4    1          4000     1000
5    0          1000     NULL
6    0          1000     NULL
7    1          7000     NULL
8    1          8000     1000

如您所见,我错过了ID 3和7的细分结果。 如何扩展我的查询以产生所需的结果?

4 个答案:

答案 0 :(得分:2)

此解决方案会减去之前的运行总计及其间的所有值。

;WITH MySet (ID, IsRunTot, Amount)
AS
(
 SELECT 1, 0, 1000
   UNION SELECT 2, 0, 1000
   UNION SELECT 3, 1, 3000      
   UNION SELECT 4, 1, 4000      
   UNION SELECT 5, 0, 1000   
   UNION SELECT 6, 0, 1000    
   UNION SELECT 7, 1, 7000
   UNION SELECT 8, 1, 8000
)
SELECT A.ID, A.IsRunTot, A.Amount
, BreakDown = CASE WHEN A.IsRunTot = 1 THEN A.Amount - 
    (SELECT SUM(B.Amount) FROM MySet B WHERE B.ID BETWEEN ISNULL(
        (SELECT MAX(C.ID) FROM MySet C WHERE C.ID < A.ID AND IsRunTot = 1)
    ,1) AND A.ID - 1) END
FROM MySet A;

答案 1 :(得分:2)

以下内容利用CTE计算真实故障和运行总数。

DECLARE @Data TABLE (ID INT, IsRunTot BIT, Amount INT)
INSERT @Data VALUES (
1,0,1000),(
2,0,1000),(
3,1,3000),(
4,1,4000),(
5,0,1000),(
6,0,1000),(
7,1,7000),(
8,1,8000)
; WITH CTE AS (
    SELECT TOP 1
        ID,
        IsRunTot,
        Amount,
        Amount AS RunningTotal,
        Amount AS Breakdown
    FROM @Data
    ORDER BY ID
    UNION ALL
    SELECT
        D2.ID,
        D2.IsRunTot,
        D2.Amount,
        D1.RunningTotal + D2.Amount - (CASE WHEN D2.IsRunTot = 1 THEN D1.RunningTotal ELSE 0 END),
        D2.Amount - (CASE WHEN D2.IsRunTot = 1 THEN D1.RunningTotal ELSE 0 END)
    FROM CTE D1
        INNER JOIN @Data D2
            ON D1.ID + 1 = D2.ID
)
    SELECT *
    FROM CTE

这会产生输出

ID          IsRunTot Amount      RunningTotal Breakdown
----------- -------- ----------- ------------ -----------
1           0        1000        1000         1000
2           0        1000        2000         1000
3           1        3000        3000         1000
4           1        4000        4000         1000
5           0        1000        5000         1000
6           0        1000        6000         1000
7           1        7000        7000         1000
8           1        8000        8000         1000

答案 2 :(得分:1)

除了在其他答案中提出的基于集合的方法之外,一个选项可能是使用光标(有时可以表现得相当好,信不信由你)

SET NOCOUNT ON;

DECLARE @res TABLE (ID int, IsRunTot bit, Amount int, Breakdown int);
DECLARE @id int, @IsRunTot int, @Amount int;

DECLARE _cursor CURSOR FOR 
SELECT ID, IsRunTot, Amount FROM test100 ORDER BY ID;

OPEN _cursor

FETCH NEXT FROM _cursor INTO @id, @IsRunTot, @Amount

WHILE @@FETCH_STATUS = 0
BEGIN

    INSERT @res
    SELECT @ID, @IsRunTot, @Amount, 
       CASE WHEN @IsRunTot = 1 
         THEN @Amount - (SELECT SUM(breakdown) FROM @res WHERE id < @id)
         ELSE @Amount 
       END

    FETCH NEXT FROM _cursor INTO @id, @IsRunTot, @Amount
END 
CLOSE _cursor;
DEALLOCATE _cursor;
SELECT * FROM @res

使用您的样本数据,结果就是:

ID          IsRunTot Amount      BreakDown 
----------- -------- ----------- --------------------
1           0        1000        1000
2           0        1000        1000
3           1        3000        1000
4           1        4000        1000
5           0        1000        1000
6           0        1000        1000
7           1        7000        1000
8           1        8000        1000

答案 3 :(得分:1)

SQL2008支持SUM窗口聚合函数。 MSDN article

SELECT ID, IsRunTot, Amount, SUM(Amount) OVER()*IsRunTot
FROM MySet