【问题标题】:How can I use an SQL Pivot for this?我怎样才能为此使用 SQL Pivot?
【发布时间】:2008-11-21 14:20:22
【问题描述】:

我有一个按以下方式组织的数据集:

Timestamp|A0001|A0002|A0003|A0004|B0001|B0002|B0003|B0004 ...
---------+-----+-----+-----+-----+-----+-----+-----+-----
2008-1-1 |  1  |  2  | 10  |   6 |  20 |  35 | 300 |  8
2008-1-2 |  5  |  2  |  9  |   3 |  50 |  38 | 290 |  2    
2008-1-4 |  7  |  7  | 11  |   0 |  30 |  87 | 350 |  0
2008-1-5 |  1  |  9  |  1  |   0 |  25 | 100 |  10 |  0
...

其中 A0001 是项目 #1 的值 A,B0001 是项目 #1 的值 B。一个表可以有60多个不同的项目,每个项目有一个A值列和一个B值列,即表中总共有120多个列。

我想得到一个 3 列结果(项目索引、A 值、B 值),它对每个项目的 A 和 B 值求和:

Index | A Value | B Value
------+---------+--------
 0001 |   14    |   125
 0002 |   20    |   260
 0003 |   31    |   950
 0004 |    9    |    10
 .... 

当我从列转到行时,我希望解决方案中有一个支点,但我不确定如何充实它。部分问题是如何去除 A 和 B 以形成索引列的值。另一部分是我以前从未使用过 Pivot,所以我也在基本语法上磕磕绊绊。

我认为最终我需要一个多步骤解决方案,首先将总和构建为:

ColName | Value
--------+------
A0001   |  14
A0002   |  20
A0003   |  31
A0004   |   9
B0001   | 125
B0002   | 260
B0003   | 950
B0004   |  10

然后修改 ColName 数据,去掉索引:

ColName | Value | Index | Aspect
--------+-------+-------+-------
A0001   |  14   | 0001  |  A
A0002   |  20   | 0002  |  A
A0003   |  31   | 0003  |  A
A0004   |   9   | 0004  |  A
B0001   | 125   | 0001  |  B
B0002   | 260   | 0002  |  B
B0003   | 950   | 0003  |  B
B0004   |  10   | 0004  |  B

最后自连接将 B 值上移到 A 值旁边。

要得到我想要的东西,这似乎是一个漫长的过程。因此,我正在寻求关于我是否走在正确道路上的建议,或者是否有另一种我忽略的方法可以让我的生活变得更加轻松。

注意 1) 解决方案必须在 MSSQL 2005 上的 T-SQL 中。

注2)表格的格式不能更改。

编辑我考虑过的另一种方法是在每列上使用 UNION 和单独的 SUM():

SELECT '0001' as Index, SUM(A0001) as A, SUM(B0001) as B FROM TABLE
UNION
SELECT '0002' as Index, SUM(A0002) as A, SUM(B0002) as B FROM TABLE
UNION
SELECT '0003' as Index, SUM(A0003) as A, SUM(B0003) as B FROM TABLE
UNION
SELECT '0004' as Index, SUM(A0004) as A, SUM(B0004) as B FROM TABLE
UNION
...

但是这种方法看起来也不是很好

编辑 到目前为止,有 2 个很好的回应。但我想在查询中再添加两个条件:-)

1) 我需要根据一系列时间戳(minv

2) 我还需要有条件地选择处理时间戳的 UDF 上的行

使用 Brettski 的表名,上述内容是否会转换为:

...
(SELECT A0001, A0002, A0003, B0001, B0002, B0003 
 FROM ptest 
 WHERE timestamp>minv AND timestamp<maxv AND fn(timestamp)=fnv) p
unpivot
(val for item in (A0001, A0002, A0003, B0001, B0002, B0003)) as unpvt
...

鉴于我有条件地添加了 fn() 要求,我认为我还需要按照 Jonathon 的建议走动态 SQL 路径。特别是因为我必须为 12 个不同的表构建相同的查询 - 都是相同的样式。

【问题讨论】:

    标签: sql-server sql-server-2005 tsql pivot


    【解决方案1】:

    同样的答案,这很有趣:

    -- Get column names from system table
    DECLARE @phCols NVARCHAR(2000)
    SELECT @phCols = COALESCE(@phCols + ',[' + name + ']', '[' + name + ']') 
        FROM syscolumns WHERE id = (select id from sysobjects where name = 'Test' and type='U')
    
    -- Get rid of the column we don't want
    SELECT @phCols = REPLACE(@phCols, '[Timestamp],', '')
    
    -- Query & sum using the dynamic column names
    DECLARE @exec nvarchar(2000)
    SELECT @exec =
    '
        select
            SUBSTRING([Value], 2, LEN([Value]) - 1) as [Index],
            SUM(CASE WHEN (LEFT([Value], 1) = ''A'') THEN Cols ELSE 0 END) as AValue, 
            SUM(CASE WHEN (LEFT([Value], 1) = ''B'') THEN Cols ELSE 0 END) as BValue
        FROM
        (
            select *
            from (select ' + @phCols + ' from Test) as t
            unpivot (Cols FOR [Value] in (' + @phCols + ')) as p
        ) _temp
        GROUP BY SUBSTRING([Value], 2, LEN([Value]) - 1)
    '
    EXECUTE(@exec)
    

    您无需在此列中硬编码列名。

    【讨论】:

    • 谢谢乔纳森。我在想那种方法来消除列的硬编码,但没有把它放在一起。我讨厌列出 160 多个列。 :)
    • 您也可以将时间戳 where 子句添加到此查询中,只需将 [[ from (select ' + @phCols + ' from Test) as t ]] 更改为 [[ from (select ' + @ phCols + ' from Test WHERE timestamp>minv AND timestamp
    • 它实际上比这更糟糕.. 更糟糕。 12 组表,每组有 123 个唯一项名称,总共有 1476 个 *items 或 2952 个列名。维护的噩梦!
    • 当然,当我在我的实际表上运行解决方案时,我达到了传递给 exec 的 4000 个字符的限制!所以我不得不将解决方案分成两部分。
    【解决方案2】:

    好的,我想出了一个可以帮助您入门的解决方案。组装起来可能需要一些时间,但会表现良好。如果我们不必按名称列出所有列,那就太好了。

    基本上,这是使用 UNPIVOT 并将该产品放入临时表中,然后将其查询到您的最终数据集中。当我把它放在一起时,我将我的表命名为 ptest,这是包含所有 A0001 等列的表。

    -- Create the temp table
    CREATE TABLE #s (item nvarchar(10), val int)
    
    -- Insert UNPIVOT product into the temp table
    INSERT INTO  #s (item, val)
    SELECT item, val
    FROM
    (SELECT A0001, A0002, A0003, B0001, B0002, B0003
    FROM ptest) p
    unpivot
    (val for item in (A0001, A0002, A0003, B0001, B0002, B0003)) as unpvt
    
    -- Query the temp table to get final data set
    SELECT RIGHT(item, 4) as item1,
    Sum(CASE WHEN LEFT(item, 1) = 'A' THEN val ELSE 0 END) as A,
    Sum(CASE WHEN LEFT(item, 1) = 'B' THEN val ELSE 0 END) as B
    from #s
    GROUP BY RIGHT(item, 4)
    
    -- Delete temp table 
    drop table #s
    

    顺便说一句,谢谢你的问题,这是我第一次使用 UNPIVOT。一直想要,只是从来没有需要。

    【讨论】:

    • 谢谢。我可以看到你来自哪里,但它让你头疼。我确实有一个与 SELECT FROM ptest 相关的小褶边,我认为我应该提到它。我需要根据一系列时间戳选择初始行(这不是问题)
    • 但我还需要根据时间戳的功能进行选择。那么我会有: (SELECT fn(timestamp) as fnv, A001 .. FROM ptest WHERE timestamp
    • 是的,这有点复杂,不是吗。
    猜你喜欢
    • 1970-01-01
    • 2016-05-01
    • 2022-11-28
    • 1970-01-01
    • 2018-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-28
    相关资源
    最近更新 更多