获取 ROWS 作为 COLUMNS(SQL Server 动态 PIVOT 查询)

Posted

技术标签:

【中文标题】获取 ROWS 作为 COLUMNS(SQL Server 动态 PIVOT 查询)【英文标题】:Get ROWS as COLUMNS (SQL Server dynamic PIVOT query) 【发布时间】:2012-08-17 23:36:37 【问题描述】:

我使用的是 MS SQL 2008 R2,有三个具有以下架构的表:

表 1:包含每个工人的轮班信息

CREATE TABLE workshift (
[ws_id] [bigint] NOT NULL,
[start_date] [datetime] NOT NULL,
[end_date] [datetime] NOT NULL,
[worker_id] [bigint] NOT NULL
)

INSERT INTO workshift VALUES (1, '2012-08-20 08:30:00', '2012-08-20 14:30:00', 1)
INSERT INTO workshift VALUES (2, '2012-08-20 14:30:00', '2012-08-20 22:30:00', 2)

表 2:包含货币面额

CREATE TABLE currency_denom (
[cd_id] [decimal](7, 2) NOT NULL,
[name] [nchar](100) NOT NULL
)

INSERT INTO currency_denom VALUES (1, '100.00')
INSERT INTO currency_denom VALUES (2, '50.00')
INSERT INTO currency_denom VALUES (3, '20.00')
INSERT INTO currency_denom VALUES (4, '10.00')
INSERT INTO currency_denom VALUES (5, '5.00')
INSERT INTO currency_denom VALUES (6, '1.00')

表 3:包含工人在每个班次中收到的每种面额的数量

CREATE TABLE currency_by_workshift (
[cd_id] [decimal](7, 2) NOT NULL,
[ws_id] [bigint] NOT NULL,
[qty] [int] NOT NULL
)

INSERT INTO currency_by_workshift VALUES (1, 1, 1)
INSERT INTO currency_by_workshift VALUES (2, 1, 2)
INSERT INTO currency_by_workshift VALUES (3, 1, 2)
INSERT INTO currency_by_workshift VALUES (2, 2, 3)
INSERT INTO currency_by_workshift VALUES (4, 2, 4)
INSERT INTO currency_by_workshift VALUES (5, 2, 2)

我需要在列而不是行中获取 currency_by_workshift 值以及工作班次值,即:

workshift |     workshift       |     workshift       | 100.00 | 50.00 | 20.00 | 10.00 | 5.00 | 1.00 
  ws_id   |     start_date      |     end_date        |        |       |       |       |      | 

    1     | 2012-08-20 08:30:00 | 2012-08-20 14:30:00 |    1   |   2   |   2   |   0   |   0  |   0
    2     | 2012-08-20 14:30:00 | 2012-08-20 22:30:00 |    0   |   2   |   0   |   4   |   2  |   0

我无法使用案例来计算每种货币面额的数量,因为它们是可配置的,如果添加了新面额,则应修改查询。如果使用 PIVOT 函数也同样适用,还是我错了?

我怎样才能通过这种方式获取信息?

【问题讨论】:

尝试搜索动态支点 - 有效地,您使用动态 sql 创建支点 这个问题已经在 SO 上提出了几次。尝试搜索 PIVOT。你可以在这里找到类似的问题***.com/questions/10976585/… 【参考方案1】:

您尝试执行的操作称为PIVOT。有两种方法可以做到这一点,使用静态枢轴或动态枢轴。

Static Pivot - 您将在其中硬编码行的值以转换为列(请参阅SQL Fiddle with Demo):

select ws_id,
  start_date,
  end_date,
  IsNull([100.00], 0) [100.00],
  IsNull([50.00], 0) [50.00],
  IsNull([20.00], 0) [20.00],
  IsNull([10.00], 0) [10.00],
  IsNull([5.00], 0) [5.00],
  IsNull([1.00], 0) [1.00]
from 
(
  select ws.ws_id,
    ws.start_date,
    ws.end_date,
    cd.name,
    cbw.qty
  from workshift ws
  left join currency_by_workshift cbw
    on ws.ws_id = cbw.ws_id
  left join currency_denom cd
    on cbw.cd_id = cd.cd_id
) x
pivot
(
  sum(qty)
  for name in ([100.00], [50.00], [20.00], [10.00], [5.00], [1.00])
) p

动态轴是在运行时确定列的位置(请参阅SQL Fiddle with Demo):

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

select @colsPivot = 
  STUFF((SELECT ', IsNull(' + QUOTENAME(rtrim(name)) +', 0) as ['+ rtrim(name)+']' 
                    from currency_denom
                   GROUP BY name
                   ORDER BY cast(name as decimal(10, 2)) desc
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

select @cols = STUFF((SELECT distinct ', ' + QUOTENAME(name)
                    from currency_denom
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')


set @query 
      = 'SELECT ws_id, start_date, end_date,' + @colsPivot + ' from 
         (
            select ws.ws_id,
              ws.start_date,
              ws.end_date,
              cd.name,
              cbw.qty
            from workshift ws
            left join currency_by_workshift cbw
              on ws.ws_id = cbw.ws_id
            left join currency_denom cd
              on cbw.cd_id = cd.cd_id
         ) x
         pivot 
         (
            sum(qty)
            for name in (' + @cols + ')
         ) p '

execute(@query)

两个版本将产生相同的结果。

【讨论】:

+1 - 当我看到这个问题时我就知道你会回答它 @Lamak 我能说什么,我喜欢 PIVOT。 :) @bluefeet,我有几个 cmets:1)在数据透视表中,workshiftcurrency_by_workshift 表之间的连接是在不匹配的键之间(一个简单的类型-o) . 2) 创建@colsPivot 时,不要选择distinct,而是选择group byorder by,这样列的顺序正确(保持整洁)。 3)rtrim(name) 在动态创建的列列表中会处理所有额外的空白(再次,保持整洁)。 谢谢,只需更改 ws.ws_id = cbw.ws_id 以正确建立表关系,它就像一个魅力! @Alex 很乐意提供帮助,我最初发帖时没有看到那个错字。【参考方案2】:

@bluefeet 利用内置的PIVOT 功能提供了一个非常好的答案。但是,我经常发现 PIVOTUNPIVOT 命名法令人困惑,而且我还没有遇到过使用标准聚合无法获得相同结果的情况:

select w.ws_id, w.start_date, w.end_date,
    [100.00] = isnull(sum(case when c.name='100.00' then cw.qty else null end), 0),
    [50.00]  = isnull(sum(case when c.name='50.00'  then cw.qty else null end), 0),
    [20.00]  = isnull(sum(case when c.name='20.00'  then cw.qty else null end), 0),
    [10.00]  = isnull(sum(case when c.name='10.00'  then cw.qty else null end), 0),
    [5.00]   = isnull(sum(case when c.name='5.00'   then cw.qty else null end), 0),
    [1.00]   = isnull(sum(case when c.name='1.00'   then cw.qty else null end), 0)
from workshift w
    join currency_by_workshift cw on w.ws_id=cw.ws_id
    join currency_denom c on cw.cd_id=c.cd_id
group by w.ws_id, w.start_date, w.end_date

如果你想做一个动态的pivot,你只需要构建一个pivot列的字符串一次:

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

select @cols = 
stuff(( select replace(',[@name] = isnull(sum(case when c.name=''@name'' then cw.qty else null end), 0)'
                       , '@name', rtrim(name))
        from currency_denom
        order by cd_id
        for xml path(''), type
    ).value('.', 'nvarchar(max)')
    ,1,1,'')

select @query = '
select w.ws_id, w.start_date, w.end_date, '+@cols+'
from workshift w
    join currency_by_workshift cw on w.ws_id=cw.ws_id
    join currency_denom c on cw.cd_id=c.cd_id
group by w.ws_id, w.start_date, w.end_date
'

execute(@query)

【讨论】:

我非常同意 PIVOT 功能相对较新,乍一看确实令人困惑。但是,在我看来,使用 PIVOT 实现 动态 透视比使用分组 + 条件聚合更容易。 @AndriyM,我不同意。我已经更新了我的答案,以包括我对动态查询所做的事情,我发现它比等效的PIVOT 答案更清晰、更容易理解。但是,归根结底,两者都有效! 这不公平,你看起来很简单! :)

以上是关于获取 ROWS 作为 COLUMNS(SQL Server 动态 PIVOT 查询)的主要内容,如果未能解决你的问题,请参考以下文章

如何从 PHP cli 获取 linux 控制台 $COLUMNS 和 $ROWS?

如何从DataTable和LINQ获取分组的字段数组

网格系统container rows 与 columns

python pandas_columns_split_rows

获取动态二维数组元素的行/列

使用游标记录作为数组