SQL Pivot具有动态生成的列和聚合函数

时间:2014-10-01 09:58:32

标签: sql sql-server tsql pivot unpivot

我有一张桌子

  CREATE TABLE [dbo].[newTable](
    [EBELN] [nvarchar](20) NOT NULL,
    [EBELP] [nvarchar](10) NOT NULL,
    [VGABE] [nvarchar](2) NOT NULL,
    [MENGE] [numeric](15, 3) NULL,
    [DMBTR] [numeric](15, 2) NULL

)

它有这些记录

insert into dbo.newTable(EBELN, EBELP, VGABE, MENGE , DMBTR) values('3000000004', '0001', '1', 1 , 27.95 )
Go
insert into dbo.newTable(EBELN, EBELP, VGABE, MENGE , DMBTR) values('3000000004', '0001', '2', 1 , 27.95 )
Go
insert into dbo.newTable(EBELN, EBELP, VGABE, MENGE , DMBTR) values('3000000004', '0002', '1', 1 , 10.95 )
Go
insert into dbo.newTable(EBELN, EBELP, VGABE, MENGE , DMBTR) values('3000000004', '0002', '2', 1 , 10.95 )
Go
insert into dbo.newTable(EBELN, EBELP, VGABE, MENGE , DMBTR) values('3000000010', '0001', '1', 1 , 22.95 )
Go
insert into dbo.newTable(EBELN, EBELP, VGABE, MENGE , DMBTR) values('3000000010', '0001', '2', 1 , 22.95 )
Go
insert into dbo.newTable(EBELN, EBELP, VGABE, MENGE , DMBTR) values('3000000010', '0002', '1', 1 , 32.95 )
Go
insert into dbo.newTable(EBELN, EBELP, VGABE, MENGE , DMBTR) values('3000000010', '0002', '2', 1 , 32.95 )
Go
insert into dbo.newTable(EBELN, EBELP, VGABE, MENGE , DMBTR) values('4151516119', '0001', '1', 1 , 400.00 )
Go
insert into dbo.newTable(EBELN, EBELP, VGABE, MENGE , DMBTR) values('4151516119', '0001', '1', 1 , 400.00 )
Go
insert into dbo.newTable(EBELN, EBELP, VGABE, MENGE , DMBTR) values('4151516119', '0001', '2', 1 , 400.00 )
Go
insert into dbo.newTable(EBELN, EBELP, VGABE, MENGE , DMBTR) values('4151516119', '0002', '1', 1 , 200.00 )
Go
insert into dbo.newTable(EBELN, EBELP, VGABE, MENGE , DMBTR) values('4151516119', '0002', '2', 1 , 200.00 )
Go

这就是SELECT *

EBELN                EBELP      VGABE MENGE                                   DMBTR
-------------------- ---------- ----- -------------- ---------------------------------------
3000000004           0001       1     1.000                                   27.95
3000000004           0001       2     1.000                                   27.95
3000000004           0002       1     1.000                                   10.95
3000000004           0002       2     1.000                                   10.95
3000000010           0001       1     1.000                                   22.95
3000000010           0001       2     1.000                                   22.95
3000000010           0002       1     1.000                                   32.95
3000000010           0002       2     1.000                                   32.95
4151516119           0001       1     1.000                                   400.00
4151516119           0001       1     1.000                                   400.00
4151516119           0001       2     1.000                                   400.00
4151516119           0002       1     1.000                                   200.00
4151516119           0002       2     1.000                                   200.00
3000000004           0001       2     1.000                                   27.95
3000000004           0002       1     1.000                                   10.95
3000000004           0002       2     1.000                                   10.95
3000000010           0001       1     1.000                                   22.95
3000000010           0001       2     1.000                                   22.95
3000000010           0002       1     1.000                                   32.95
3000000010           0002       2     1.000                                   32.95
4151516119           0001       1     1.000                                   400.00
4151516119           0001       1     1.000                                   400.00
4151516119           0001       2     1.000                                   400.00
4151516119           0002       1     1.000                                   200.00
4151516119           0002       2     1.000                                   200.00
4151516177           0002       6     1.000                                   111.00
4151516177           0002       8     1.000                                   111.00

我需要和想要的是一个生成此结果的动态轴

+------------+-------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
|   EBELN    | EBELP | c_DMBTR_1 | c_MENGE_1 | c_DMBTR_2 | c_MENGE_2 | c_DMBTR_6 | c_MENGE_6 | c_DMBTR_8 | c_MENGE_8 |
+------------+-------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
| 3000000004 |  0001 | 27.95     | 1         | 27.95     | 1         | NULL      | NULL      | NULL      | NULL      |
| 3000000004 |  0002 | 10.95     | 1         | 10.95     | 1         | NULL      | NULL      | NULL      | NULL      |
| [...]      |       |           |           |           |           |           |           |           |           |
| 4151516119 |  0001 | 800.00    | 1         | 400.00    | 1         | NULL      | NULL      | NULL      | NULL      |
| 4151516177 |  0002 | NULL      | NULL      | NULL      | NULL      | 111.00    | 1         | 111.00    | 1         |
+------------+-------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+

我尝试了几种解决方案,但没有人得到我想要的结果。

我需要的是VGABE中要(1,2,6,7,8,9,P,R)连接到c_DMBTRc_MENGE的字符串名称的值。但是可能的是,例如P或7没有被使用,所以我不希望有这样的列。我认为让它变得动态是唯一可行的方法。

来自VGABE的值应添加到列名称'c_MENGE_'+VGABE中,并且必须按顺序排列,这意味着它必须以VGABE中包含所有列的最低值开头然后是下一个值,直到vgabe的每个使用值。

如果有一个位置(EBELP)来自VGABE的多个值,就像POS 0001的两倍1,正如您在EBELN 4151516119所看到的那样我必须此位置的SUM(DMBTR)和VGABE值。我和MENGE一样。

是否可以在一个查询或存储过程中执行。我不知道如何得到这个结果,我现在被卡住了。或者还有另一种我不知道的方法吗?

1 个答案:

答案 0 :(得分:2)

为了获得您想要的最终结果,您首先必须unpivot DMBTRMENGE列,然后应用PIVOT函数进行转换行成列。

你没有提到你正在使用哪个版本的SQL Server我会猜测SQL Server 2005+。从SQL Server 2005开始,PIVOT功能已经可用,但对于UNPIVOT,你也可以使用CROSS APPLY - 我认为这在这里会更容易。

在深入研究动态SQL版本之前,我总是希望编写一个静态版本来获取逻辑,然后将其转换为动态SQL。第一步是将DMBTRMENGE列转换为行:

select 
  t.ebeln,
  t.ebelp,
  new_col = c.orig_col + '_' + vgabe,
  c.value
from dbo.newTable t
cross apply
(
  select 'c_MENGE', menge union all
  select 'c_DMBTR', dmbtr
) c (orig_col, value);

SQL Fiddle with Demo。这会将您的数据转换为:

|      EBELN | EBELP |   NEW_COL | VALUE |
|------------|-------|-----------|-------|
| 3000000004 |  0001 | c_MENGE_1 |     1 |
| 3000000004 |  0001 | c_DMBTR_1 | 27.95 |
| 3000000004 |  0001 | c_MENGE_2 |     1 |
| 3000000004 |  0001 | c_DMBTR_2 | 27.95 |
| 3000000004 |  0002 | c_MENGE_1 |     1 |
| 3000000004 |  0002 | c_DMBTR_1 | 10.95 |

正如您所看到的,您现在有多行,以及new_col的连接值c_MENGE_1等等,这将是您的最终列。

获得此结果后,您可以应用PIVOT功能:

select ebeln,
  ebelp,
  c_DMBTR_1, c_MENGE_1, c_DMBTR_2, c_MENGE_2,
  c_DMBTR_6, c_MENGE_6, c_DMBTR_8, c_MENGE_8
from
(
  select 
    t.ebeln,
    t.ebelp,
    new_col = c.orig_col + '_' + vgabe,
    c.value
  from dbo.newTable t
  cross apply
  (
    select 'c_MENGE', menge union all
    select 'c_DMBTR', dmbtr
  ) c (orig_col, value)
) d
pivot
(
  sum(value)
  for new_col in (c_DMBTR_1, c_MENGE_1, c_DMBTR_2, c_MENGE_2,
                  c_DMBTR_6, c_MENGE_6, c_DMBTR_8, c_MENGE_8)
) piv
order by ebeln, ebelp;

SQL Fiddle with Demo

现在你已经有了正确的逻辑,你需要将它转换为动态SQL。这将首先创建新列名的sql字符串。为此,您将使用FOR XML PATH

select @cols = STUFF((SELECT ',' + QUOTENAME(col + '_' + vgabe) 
                    from dbo.NewTable t
                    cross apply
                    (
                      select 'c_DMBTR', 1 union all
                      select 'c_MENGE', 2
                    ) c (col, so)
                    group by col, so, vgabe
                    order by vgabe, so
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

这类似于我在静态版本中使用的代码。它获取vgabe值的列表,并将其连接到您想要PIVOT(DMBTRMENGE)的2列的名称。我还为这些列提供了排序顺序,因此您可以根据需要订购它们。完整的动态SQL代码将是:

DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT ',' + QUOTENAME(col + '_' + vgabe) 
                    from dbo.NewTable t
                    cross apply
                    (
                      select 'c_DMBTR', 1 union all
                      select 'c_MENGE', 2
                    ) c (col, so)
                    group by col, so, vgabe
                    order by vgabe, so
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query 
  = 'SELECT ebeln, ebelp,' + @cols + N' 
     from 
     (
      select 
        t.ebeln,
        t.ebelp,
        new_col = c.orig_col + ''_'' + vgabe,
        c.value
      from dbo.newTable t
      cross apply
      (
        select ''c_MENGE'', menge union all
        select ''c_DMBTR'', dmbtr
      ) c (orig_col, value)
     ) x
     pivot 
     (
       sum(value)
       for new_col in (' + @cols + N')
     ) p 
     order by ebeln, ebelp'

exec sp_executesql @query;

SQL Fiddle with Demo。这给出了一个结果:

|      EBELN | EBELP | C_DMBTR_1 | C_MENGE_1 | C_DMBTR_2 | C_MENGE_2 | C_DMBTR_6 | C_MENGE_6 | C_DMBTR_8 | C_MENGE_8 |
|------------|-------|-----------|-----------|-----------|-----------|-----------|-----------|-----------|-----------|
| 3000000004 |  0001 |     27.95 |         1 |     27.95 |         1 |    (null) |    (null) |    (null) |    (null) |
| 3000000004 |  0002 |     10.95 |         1 |     10.95 |         1 |    (null) |    (null) |    (null) |    (null) |
| 3000000010 |  0001 |     22.95 |         1 |     22.95 |         1 |    (null) |    (null) |    (null) |    (null) |
| 3000000010 |  0002 |     32.95 |         1 |     32.95 |         1 |    (null) |    (null) |    (null) |    (null) |
| 4151516119 |  0001 |       800 |         2 |       400 |         1 |    (null) |    (null) |    (null) |    (null) |
| 4151516119 |  0002 |       200 |         1 |       200 |         1 |       111 |         1 |       111 |         1 |