このためにSQLピボットを使用するにはどうすればよいですか?
-
08-07-2019 - |
質問
次のように編成されたデータセットがあります:
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以上の列があります。
取得したいのは、各項目のA値とB値を合計する3列の結果(項目インデックス、A値、B値)です:
Index | A Value | B Value
------+---------+--------
0001 | 14 | 125
0002 | 20 | 260
0003 | 31 | 950
0004 | 9 | 10
....
列から行に進むと、ソリューションにピボットが必要になりますが、具体化する方法がわかりません。問題の一部は、AとBを削除して、インデックス列の値を形成する方法です。もう1つの部分は、これまでにピボットを使用したことがないため、基本的な構文にもつまずいています。
最終的には、次のように合計を作成するマルチステップソリューションが必要だと思います:
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)テーブルの形式は変更できません。
編集私が考えたもう1つの方法は、各列で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つの素晴らしい回答があります。しかし、クエリにさらに2つの条件を追加したいと思います:-)
1)タイムスタンプの範囲に基づいて行を選択する必要があります(minv <!> lt; timestamp <!> lt; maxv)。
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の異なるテーブルに同じクエリを作成する必要があるため、すべて同じスタイルです。
解決
ここでも同じ答えで、楽しかったです:
-- 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)
この中に列名をハードコーディングする必要はありません。
他のヒント
OK、私はあなたを始めるべき一つの解決策を思いつきました。組み立てにはおそらく時間がかかりますが、うまく機能します。すべての列を名前でリストする必要がなかったらいいと思います。
基本的に、これは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を使用するのはこれが初めてでした。常に欲しかった、ただ必要はなかった。