SQLデータベースクエリ行と列の変換結果を実現するSQL文
行は列を回転し、列は私たちが開発の過程でよく出会う問題です.行転列は一般にCASE WHEN文によって実現されるが、SQL SERVER 2005に追加された演算子PIVOTによって実現される.伝統的な方法で、分かりやすいです.階層がはっきりしていて、慣れています.でもPIVOT、
UNPIVOTが提供する文法は一連の複雑なSELECTより...CASE文で指定されている構文は、より単純で読みやすいものです.以下に、いくつかの簡単な例を用いて、以下の転行、行転列の問題を紹介します.
まず、学生の成績表(以下簡略化)を通じて、下りの転列をイメージします.
CREATE
TABLE
[
StudentScores
]
(
[
UserName
]
NVARCHAR
(
20
),
--
学生の名前
[
Subject
]
NVARCHAR
(
30
),
--
科目
[
Score
]
FLOAT
,
--
成績
)
INSERT
INTO
[
StudentScores
]
SELECT
'
Nick
'
,
'
国語
'
,
80
INSERT
INTO
[
StudentScores
]
SELECT
'
Nick
'
,
'
数学
'
,
90
INSERT
INTO
[
StudentScores
]
SELECT
'
Nick
'
,
'
英語
'
,
70
INSERT
INTO
[
StudentScores
]
SELECT
'
Nick
'
,
'
せいぶつ
'
,
85
INSERT
INTO
[
StudentScores
]
SELECT
'
Kent
'
,
'
国語
'
,
80
INSERT
INTO
[
StudentScores
]
SELECT
'
Kent
'
,
'
数学
'
,
90
INSERT
INTO
[
StudentScores
]
SELECT
'
Kent
'
,
'
英語
'
,
70
INSERT
INTO
[
StudentScores
]
SELECT
'
Kent
'
,
'
せいぶつ
'
,
85
もし私がすべての学生の各科の成績を知りたいならば、その上すべての学生のすべての成績は1行に並んで、このように私が見て、統計して、データをエクスポートするのに便利です
SELECT
UserName,
MAX
(
CASE
Subject
WHEN
'
国語
'
THEN
Score
ELSE
0
END
)
AS
'
国語
'
,
MAX
(
CASE
Subject
WHEN
'
数学
'
THEN
Score
ELSE
0
END
)
AS
'
数学
'
,
MAX
(
CASE
Subject
WHEN
'
英語
'
THEN
Score
ELSE
0
END
)
AS
'
英語
'
,
MAX
(
CASE
Subject
WHEN
'
せいぶつ
'
THEN
Score
ELSE
0
END
)
AS
'
せいぶつ
'
FROM
dbo.
[
StudentScores
]
GROUP
BY
UserName
クエリーの結果は図のように、学生一人一人のすべての成績をよく知ることができます.
次に2番目の小さな列を見てみましょう.ゲームプレイヤーのチャージテーブル(説明のために挙げた小さな例)があり、
Code
[http://www.oeedu.com]
CREATE
TABLE
[
Inpours
]
(
[
ID
]
INT
IDENTITY
(
1
,
1
),
[
UserName
]
NVARCHAR
(
20
),
--
[
CreateTime
]
DATETIME
,
--
[
PayType
]
NVARCHAR
(
20
),
--
[
Money
]
DECIMAL
,
--
[
IsSuccess
]
BIT
,
--
1 , 0
CONSTRAINT
[
PK_Inpours_ID
]
PRIMARY
KEY
(ID)
)
INSERT
INTO
Inpours
SELECT
'
'
,
'
2010-05-01
'
,
'
'
,
50
,
1
INSERT
INTO
Inpours
SELECT
'
'
,
'
2010-06-14
'
,
'
'
,
50
,
1
INSERT
INTO
Inpours
SELECT
'
'
,
'
2010-06-14
'
,
'
'
,
100
,
1
INSERT
INTO
Inpours
SELECT
'
'
,
'
2010-06-14
'
,
'
'
,
100
,
1
INSERT
INTO
Inpours
SELECT
'
'
,
'
2010-07-14
'
,
'
'
,
100
,
1
INSERT
INTO
Inpours
SELECT
'
'
,
'
2010-07-14
'
,
'
'
,
100
,
1
INSERT
INTO
Inpours
SELECT
'
'
,
'
2010-07-14
'
,
'
'
,
100
,
1
以下に統計データの需要が来て、日付、支払い方式によってチャージ金額情報を統計することを要求します.これも典型的な行転列の例です.次のスクリプトで目的を達成できます
Code
[http://www.oeedu.com]
SELECT
CONVERT
(
VARCHAR
(
10
), CreateTime,
120
)
AS
CreateTime,
CASE
PayType
WHEN
'
'
THEN
SUM
(
Money
)
ELSE
0
END
AS
'
'
,
CASE
PayType
WHEN
'
'
THEN
SUM
(
Money
)
ELSE
0
END
AS
'
'
,
CASE
PayType
WHEN
'
'
THEN
SUM
(
Money
)
ELSE
0
END
AS
'
'
,
CASE
PayType
WHEN
'
'
THEN
SUM
(
Money
)
ELSE
0
END
AS
'
'
FROM
Inpours
GROUP
BY
CreateTime, PayType
図に示すように,我々はこのような出力結果を得ただけで,所望の結果を得るにはさらに処理が必要である.
SELECT CreateTime,ISNULL(SUM([支付宝]),0)AS[支付宝],ISNULL(SUM([携帯メール]),0)AS[携帯メール],ISNULL(SUM([工商銀行カード]),0)AS[工商銀行カード],ISNULL(SUM([建設銀行カード]),0)AS[建設銀行カード]FROM(SELECT CONVERT(VARCHR(10),CreateTime,120)AS CreateTime,CASE PayType WHIN'支付宝'THEN SUM(Money)ELSE 0 END AS'支付宝',CASE PayType WHIN'携帯メール'THEN SUM(Money)ELSE 0 END AS'携帯メール',CASE PayType WHIN'工商銀行カード'THEN SUM(Money)ELSE 0 END AS'工商銀行カード',CASE PayType WHEN'建設銀行カード'THEN SUM(Money)ELSE 0 END AS'建設銀行カード'FROM Inpours GROUP BY CreateTime,PayType)TGROUPBY CreateTime
それは転列を実行して、肝心なのは論理を整理して、しかもグループ(Group by)の概念に対して比較的にはっきりしています.上の2つの列は基本的に行転列のタイプです.しかし、質問が来ました.上は私が説明するために作った簡単な列です.実際には、支払い方法が特に多く、論理も複雑で、為替レートや手数料などにかかわる可能性があります(かつてはこのようなものを作っていました)、支払い方法が特に多ければ、私たちのCASE WHENはたくさん出てきて、確かに怒っています.そして、支払い方法が追加されました.スクリプトを修正しなければなりません.上のスクリプトを動的SQLで書き換えると、私たちはこの問題を簡単に解決することができます.
Code
[http://www.oeedu.com]
DECLARE
@cmdText
VARCHAR
(
8000
);
DECLARE
@tmpSql
VARCHAR
(
8000
);
SET
@cmdText
=
'
SELECT CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,
'
CHAR
(
10
);
SELECT
@cmdText
=
@cmdText
'
CASE PayType WHEN
'''
PayType
'''
THEN SUM(Money) ELSE 0 END AS
'''
PayType
'''
,
'
CHAR
(
10
)
FROM
(
SELECT
DISTINCT
PayType
FROM
Inpours ) T
SET
@cmdText
=
LEFT
(
@cmdText
,
LEN
(
@cmdText
)
-
2
)
--
, CHAR(10) LEFT(@cmdText, LEN(@cmdText) -1)
SET
@cmdText
=
@cmdText
'
FROM Inpours GROUP BY CreateTime, PayType
'
;
SET
@tmpSql
=
'
SELECT CreateTime,
'
CHAR
(
10
);
SELECT
@tmpSql
=
@tmpSql
'
ISNULL(SUM(
'
PayType
'
), 0) AS
'''
PayType
'''
,
'
CHAR
(
10
)
FROM
(
SELECT
DISTINCT
PayType
FROM
Inpours ) T
SET
@tmpSql
=
LEFT
(
@tmpSql
,
LEN
(
@tmpSql
)
-
2
)
'
FROM (
'
CHAR
(
10
);
SET
@cmdText
=
@tmpSql
@cmdText
'
) T GROUP BY CreateTime
'
;
PRINT
@cmdText
EXECUTE
(
@cmdText
);
以下はPIVOTによる行転列の使い方ですが、比較してみると、確かに簡単で読みやすいです(ほほほ、習慣の前提の下で)
Code
[http://www.oeedu.com]
SELECT
CreateTime,
[
]
,
[
]
,
[
]
,
[
]
FROM
(
SELECT
CONVERT
(
VARCHAR
(
10
), CreateTime,
120
)
AS
CreateTime,PayType,
Money
FROM
Inpours
) P
PIVOT (
SUM
(
Money
)
FOR
PayType
IN
(
[
]
,
[
]
,
[
]
,
[
]
)
)
AS
T
ORDER
BY
CreateTime
このようなエラーが発生することがあります.
メッセージ325、レベル15、ステータス1、9行目
「PIVOT」付近に文法エラーがあります.この機能を有効にするには、現在のデータベースの互換性レベルをより高い値に設定する必要があります.ストレージ・プロシージャsp_についてdbcmptlevelの詳細については、ヘルプを参照してください.
これは、SQL Server 2005以降にアップグレードされたデータベースでPIVOTとUNPIVOTを使用する場合、データベースの互換性レベルを90以上に設定する必要があるためです.データベース互換性レベルの設定方法については、sp_を参照してください.dbcmptlevel (Transact-SQL). たとえば、上記のスクリプトを実行する前にEXEC sp_を付けるだけです.dbcmptlevel Test, 90; OKです.Testは所在するデータベースの名前です.
次に,列転行を見てみると,主にユニオンALL,MAXによって実現される.次のような時計があれば
コード#コード#
CREATE
TABLE
ProgrectDetail(ProgrectName
NVARCHAR
(
20
),
--
プロジェクト名
OverseaSupply
INT
,
--
海外仕入先供給数量
NativeSupply
INT
,
--
国内仕入先供給数量
SouthSupply
INT
,
--
南方サプライヤー供給数量
NorthSupply
INT
--
北方仕入先供給数量
)
INSERT
INTO
ProgrectDetail
SELECT
'
A
'
,
100
,
200
,
50
,
50
UNION
ALL
SELECT
'
B
'
,
200
,
300
,
150
,
150
UNION
ALL
SELECT
'
C
'
,
159
,
400
,
20
,
320
UNION
ALL
SELECT
'
D
'
,
250
,
30
,
15
,
15
次のスクリプトで実現できます.クエリーの結果は下図のようになります.
コード#コード#
SELECT
ProgrectName,
'
OverseaSupply
'
AS
Supplier,
MAX
(OverseaSupply)
AS
'
SupplyNum
'
FROM
ProgrectDetail
GROUP
BY
ProgrectName
UNION
ALL
SELECT
ProgrectName,
'
NativeSupply
'
AS
Supplier,
MAX
(NativeSupply)
AS
'
SupplyNum
'
FROM
ProgrectDetail
GROUP
BY
ProgrectName
UNION
ALL
SELECT
ProgrectName,
'
SouthSupply
'
AS
Supplier,
MAX
(SouthSupply)
AS
'
SupplyNum
'
FROM
ProgrectDetail
GROUP
BY
ProgrectName
UNION
ALL
SELECT
ProgrectName,
'
NorthSupply
'
AS
Supplier,
MAX
(NorthSupply)
AS
'
SupplyNum
'
FROM
ProgrectDetail
GROUP
BY
ProgrectName
UNPIVOTでは次のように実現されます.
コード#コード#
SELECT
ProgrectName,Supplier,SupplyNum
FROM
(
SELECT
ProgrectName, OverseaSupply, NativeSupply,SouthSupply, NorthSupply
FROM
ProgrectDetail)TUNPIVOT (SupplyNum
FOR
Supplier
IN
(OverseaSupply, NativeSupply, SouthSupply, NorthSupply )) P