当累积和跨越边界时,如何将数量溢出到后续列

时间:2016-02-17 04:45:51

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

我必须显示动态列,其中qty的总和等于1000.直到第一个边界,这些行显示为COL1。之后从该行开始,逐行添加数量,直到总和再次为1000。然后这些显示在COL2中。然后是COL3的相同程序,依此类推。

这是我的源表......

  ID    QTY
-------------
  1     240
  2     101
  3     43
  4     43
  5     24
  6     43
  7     59
  8     11
  9     65
  10    200
  11    16
  12    1
  13    195
  14    50
  15    40

预期产出:

ID  COL1  COL2
1    240    0
2    101    0
3     43    0
4     43    0
5     24    0
6     43    0
7     59    0
8     11    0
9     65    0
10    200   0
11     16   0
12      1   0
13    154   41
14      0   50
15      0   40

1 个答案:

答案 0 :(得分:2)

SQL Server 2012 +

的解决方案

您可能已经猜到这是通过使用动态SQL完成的。采取以下步骤:

  • 使用所有以前所有行#staging的所有列填充临时表(SUM):SUM(qty) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)。这样做是为了能够在下一步中使用LAG。原因:您不能在另一个窗口函数上设置窗口函数。
  • 使用 overflow 机制从登台表中进行选择。这使用LAG函数来查看是否越过了上边界或下边界。

该过程使用以下结构:

PS:我将样本数据加倍,以测试三列。

剧本:

CREATE TABLE #tt(id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,qty INT NOT NULL);
INSERT INTO #tt(qty)VALUES
(240),(101),(43),(43),(24),(43),(59),(11),(65),(200),(16),(1),(195),(50),(40),(240),(101),(43),(43),(24),(43),(59),(11),(65),(200),(16),(1),(195),(50),(40);

DECLARE @tot_cols INT=(CASE WHEN 0=(SELECT SUM(qty) FROM #tt) THEN 1 ELSE (SELECT SUM(qty) FROM #tt) END-1)/1000+1; -- 0 .. 1000 // 1001 .. 2000 // ...

DECLARE @staging_cols NVARCHAR(MAX)=(
    SELECT N',col'+n+N'=SUM(qty) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)'
    FROM (
            SELECT TOP (@tot_cols) CAST(ROW_NUMBER() OVER(ORDER BY t1.number) AS VARCHAR) AS n
            FROM master.dbo.spt_values AS t1 CROSS JOIN master.dbo.spt_values AS t2
         ) AS tally
    WHERE n<=@tot_cols
    FOR XML PATH('')
);
DECLARE @staging_create NVARCHAR(MAX)=
    N'SELECT id,qty'+@staging_cols+' INTO #staging FROM #tt;';

DECLARE @select_cols NVARCHAR(MAX)=(
    SELECT N',col'+n+N'='+
               N'CASE WHEN col'+n+N'>'+b_upper+N' ' +
                    N'THEN CASE WHEN LAG(col'+n+N') OVER (ORDER BY id)<='+b_upper+N' '+
                           N'THEN '+b_upper+N'-LAG(col'+n+N') OVER (ORDER BY id) '+
                           N'ELSE 0 '+
                           N'END ' +
                    N'WHEN col'+n+N'>'+b_lower+N' '+
                    N'THEN CASE WHEN LAG(col'+n+N') OVER (ORDER BY id)<='+b_lower+N' '+ 
                           N'THEN col'+n+N'-'+b_lower+N' '+
                           N'ELSE qty ' +
                           N'END ' +
                    N'ELSE 0 '+
                N'END'
    FROM (
            SELECT TOP (@tot_cols) CAST(ROW_NUMBER() OVER(ORDER BY t1.number) AS VARCHAR) AS n
            FROM master.dbo.spt_values t1 CROSS JOIN master.dbo.spt_values t2
         ) AS tally
         CROSS APPLY (
            SELECT b_lower=CAST(CASE WHEN n=1 THEN 0 ELSE (n-1)*1000 END AS VARCHAR),
                   b_upper=CAST(n*1000 AS VARCHAR)
         ) AS boundaries
    WHERE n<=@tot_cols
    FOR XML PATH('')
);
DECLARE @select_result NVARCHAR(MAX)=
    N'SELECT id'+REPLACE(REPLACE(@select_cols,N'&lt;',N'<'),N'&gt;',N'>')+N' FROM #staging ORDER BY id;';

DECLARE @stmt NVARCHAR(MAX)=@staging_create+@select_result+'DROP TABLE #staging;';
EXEC sp_executesql @stmt;

DROP TABLE #tt;

SQL Server 2008的解决方案

这具有相同的基础知识(具有累积和的临时表,滞后以与之前的值进行比较,计数表...)但使用其他构造。

2008R2不支持SUM()窗口,不支持LAG。累积总和使用CURSOR(2008R2中唯一合理的方法)完成;滞后是通过将登台表自我链接到先前的id来完成的。这是:

CREATE TABLE #tt(id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,qty INT NOT NULL);
INSERT INTO #tt(qty)VALUES
(240),(101),(43),(43),(24),(43),(59),(11),(65),(200),(16),(1),(195),(50),(40),(240),(101),(43),(43),(24),(43),(59),(11),(65),(200),(16),(1),(195),(50),(40);

DECLARE @tot_cols INT=(CASE WHEN 0=(SELECT SUM(qty) FROM #tt) THEN 1 ELSE (SELECT SUM(qty) FROM #tt) END-1)/1000+1; -- 0 .. 1000 // 1001 .. 2000 // ...

DECLARE @staging_cols NVARCHAR(MAX)=(
    SELECT N',col'+n+N'=0'
    FROM (
            SELECT TOP (@tot_cols) CAST(ROW_NUMBER() OVER(ORDER BY t1.number) AS VARCHAR) AS n
            FROM master.dbo.spt_values AS t1 CROSS JOIN master.dbo.spt_values AS t2
         ) AS tally
    WHERE n<=@tot_cols
    FOR XML PATH('')
);
DECLARE @staging_create NVARCHAR(MAX)=
    N'SELECT id,qty'+@staging_cols+' INTO #staging FROM #tt;ALTER TABLE #staging ADD CONSTRAINT PK_staging PRIMARY KEY CLUSTERED (id);';

DECLARE @spillover_cols_sel NVARCHAR(MAX)=STUFF(REPLACE(@staging_cols,N'=0',N''),1,1,N'');
DECLARE @spillover_cols_upd NVARCHAR(MAX)=STUFF(REPLACE(@staging_cols,N'=0',N'=@tot_qty'),1,1,N'');
DECLARE @staging_fill NVARCHAR(MAX)=
    N'DECLARE c_s CURSOR FOR SELECT qty FROM #staging ORDER BY id FOR UPDATE OF '+@spillover_cols_sel+';'+
    N'OPEN c_s; DECLARE @qty INT; DECLARE @tot_qty INT; SET @tot_qty=0; WHILE 1=1 BEGIN '+
    N'FETCH NEXT FROM c_s INTO @qty; IF @@FETCH_STATUS<>0 BREAK; SET @tot_qty=@tot_qty+@qty;'+
    N'UPDATE #staging SET '+@spillover_cols_upd+' WHERE CURRENT OF c_s;'+
    N'END CLOSE c_s;DEALLOCATE c_s;';

DECLARE @select_cols NVARCHAR(MAX)=(
    SELECT N',col'+n+N'='+
               N'CASE WHEN bt.col'+n+N'>'+b_upper+N' ' +
                    N'THEN CASE WHEN ISNULL(lt.col'+n+N',0)<='+b_upper+N' '+
                           N'THEN '+b_upper+N'-ISNULL(lt.col'+n+N',0) '+
                           N'ELSE 0 '+
                           N'END ' +
                    N'WHEN bt.col'+n+N'>'+b_lower+N' '+
                    N'THEN CASE WHEN ISNULL(lt.col'+n+N',0)<='+b_lower+N' '+    
                           N'THEN bt.col'+n+N'-'+b_lower+N' '+
                           N'ELSE bt.qty ' +
                           N'END ' +
                    N'ELSE 0 '+
                N'END'
    FROM (
            SELECT TOP (@tot_cols) CAST(ROW_NUMBER() OVER(ORDER BY t1.number) AS VARCHAR) AS n
            FROM master.dbo.spt_values t1 CROSS JOIN master.dbo.spt_values t2
         ) AS tally
         CROSS APPLY (
            SELECT b_lower=CAST(CASE WHEN n=1 THEN 0 ELSE (n-1)*1000 END AS VARCHAR),
                   b_upper=CAST(n*1000 AS VARCHAR)
         ) AS boundaries
    WHERE n<=@tot_cols
    FOR XML PATH('')
);

DECLARE @select_result NVARCHAR(MAX)=
    N'SELECT bt.id'+REPLACE(REPLACE(@select_cols,N'&lt;',N'<'),N'&gt;',N'>')+N' '+
    N'FROM #staging AS bt LEFT JOIN #staging AS lt ON lt.id=bt.id-1 ORDER BY bt.id;';

DECLARE @stmt NVARCHAR(MAX)=@staging_create+@staging_fill+@select_result+'DROP TABLE #staging;';
EXEC sp_executesql @stmt;

DROP TABLE #tt;

两个脚本的结果:

+----+------+------+------+
| id | col1 | col2 | col3 |
+----+------+------+------+
|  1 |  240 |    0 |    0 |
|  2 |  101 |    0 |    0 |
|  3 |   43 |    0 |    0 |
|  4 |   43 |    0 |    0 |
|  5 |   24 |    0 |    0 |
|  6 |   43 |    0 |    0 |
|  7 |   59 |    0 |    0 |
|  8 |   11 |    0 |    0 |
|  9 |   65 |    0 |    0 |
| 10 |  200 |    0 |    0 |
| 11 |   16 |    0 |    0 |
| 12 |    1 |    0 |    0 |
| 13 |  154 |   41 |    0 |
| 14 |    0 |   50 |    0 |
| 15 |    0 |   40 |    0 |
| 16 |    0 |  240 |    0 |
| 17 |    0 |  101 |    0 |
| 18 |    0 |   43 |    0 |
| 19 |    0 |   43 |    0 |
| 20 |    0 |   24 |    0 |
| 21 |    0 |   43 |    0 |
| 22 |    0 |   59 |    0 |
| 23 |    0 |   11 |    0 |
| 24 |    0 |   65 |    0 |
| 25 |    0 |  200 |    0 |
| 26 |    0 |   16 |    0 |
| 27 |    0 |    1 |    0 |
| 28 |    0 |   23 |  172 |
| 29 |    0 |    0 |   50 |
| 30 |    0 |    0 |   40 |
+----+------+------+------+