需要注意的是FROM的含义,其表示从经PIVOT关系运算符得到的pvt结果集中检索数据,而不是从Orders或派生表O1中检索数据。
在SQL Server2005之前,要进行行列转换比较烦琐,你需要考虑源表中行与结果集中行的关系,属性列中的每个唯一值在结果集中都需要一个列。像上面表9-7中的Orders表由于包含3个月份的数据,因此在SELECT列表中需要包含3个表达式,分别用于提取3个月份中的数据。下面语句的查询结果与表9-9相同,请读者自己分析下面的语句。
SELECT ProductID,
SUM(CASE WHEN OrderMonth = 1 THEN SubTotal END) AS Jan,
SUM(CASE WHEN OrderMonth = 2 THEN SubTotal END) AS Feb,
SUM(CASE WHEN OrderMonth = 3 THEN SubTotal END) AS Mar
FROM (SELECT ProductID,
MONTH(OrderDate) AS OrderMonth,
SubTotal AS SubTotal
FROMOrders) AS O1
GROUP BY ProductID;
9.4.2 UNPIVOT运算符
UNPIVOT与PIVOT执行几乎完全相反的操作,将列转换为行。但是,UNPIVOT并不完全是PIVOT的逆操作,由于在执行PIVOT过程中,数据已经被进行了分组汇总,所以使用UNPIVOT并不会重现原始表值表达式的结果。假设表9-9所示的结果集存储在一个名为MyPvt的表中,现在需要将列Jan、Feb和Mar转换到对应于相应产品ID的行值(即返回到表9-10所示的格式)。这意味着必须另外标识两个列,一个用于存储月份,一个用于存储销售额。为了便于理解,仍旧分别将这两个列命名为OrderMonth和SubTotal。
下面的语句首先创建MyPvt表,然后将查询数据插入到表中。
CREATE TABLE MyPvt
(
ProductID int NOT NULL,
Jan money,
Feb money,
Mar money
);
INSERT INTO MyPvt(ProductID, Jan, Feb, Mar)
SELECT ProductID,
[1] AS Jan,
[2] AS Feb,
[3] AS Mar
FROM (SELECT ProductID,MONTH(OrderDate) AS OrderMonth, SubTotal
FROM Orders) AS O1
PIVOT
(
SUM(SubTotal)
FOR OrderMonth IN ([1], [2], [3])
) AS Pvt
ORDER BY ProductID;
下面的语句执行UNPIVOT,将得到表9-12所示的查询结果。
SELECT ProductID,OrderMonth, SubTotal
FROM MyPvt
UNPIVOT
(
SubTotal FOR OrderMonth IN (Jan, Feb, Mar)
)AS UnPvt;
表9-12 UNPIVOT得到的查询结果
ProductID |
OrderMonth |
SubTotal |
1 |
Jan |
300.00 |
1 |
Feb |
100.00 |
1 |
Mar |
100.00 |
2 |
Jan |
100.00 |
2 |
Mar |
100.00 |
3 |
Feb |
200.00 |
3 |
Mar |
100.00 |
4 |
Mar |
100.00 |
上面的语句将按下面的步骤获得输出结果集:
首先建立一个临时结果集的结构,该结构中包含MyPvt表中除IN (Jan, Feb, Mar)之外的列,以及SubTotalFOR OrderMonth中指定的属性值列(SubTotal)和属性列(OrderMonth)。
然后将在MyPvt中逐行检索数据,将表的列名称放入OrderMonth列中,将相应的值放入到SubTotal列中。
由于在PIVOT时为列指定了别名,所以在UNPIVOT后,OrderMonth列中的月份使用的是英文简称,而不是表9-10所示的格式。要得到表9-10所示的格式,可以在查询语句中使用CASE表达式来解决这个问题,参考下面的语句:
SELECT ProductID,
CAST(CASE
WHEN OrderMonth ='Jan' THEN '1'
WHEN OrderMonth ='Feb' THEN '2'
WHEN OrderMonth ='Mar' THEN '3'
END AS int) AS OrderMonth,
SubTotal
FROM MyPvt
UNPIVOT
(
SubTotalFOR OrderMonth IN (Jan, Feb, Mar)
)AS UnPvt;
在SQL Server2005之前,则应当使用下面的语句:
SELECT * FROM
(SELECTProductID,1 AS OrderMonth, Jan AS SubTotal
FROMMyPvt
UNION ALL
SELECTProductID,2 AS OrderMonth, Feb
FROMMyPvt
UNION ALL
SELECT ProductID,3 AS OrderMonth, Mar
FROMMyPvt) AS O
WHERE SubTotal IS NOT NULL;
9.4.3 CASE表达式在9.4节我们介绍了使用PIVOT运算符进行表行列转换的方法,有效解决了表的数据存储和方便阅读的矛盾问题,本节将提供另外的一种转换方式。这是在做一个考试系统时遇到的问题,下面是是创建示例表的语句。表中存放着学生每次各科的考试成绩。
CREATE TABLE exams
(stu_name char(10) NOT NULL,
exam_datedate NOT NULL,
exam_subchar(10) NOT NULL,
exam_scoreint);
INSERT INTO exams
VALUES ('张三', '2009-06-20', '语文', 90),
('张三', '2009-06-20', '数学', 95),
('张三', '2009-06-20', '英语', 100),
('张三', '2009-09-20', '语文', 85),
('张三', '2009-09-20', '数学', 90),
('张三', '2009-09-20', '英语', 98),
('李四', '2009-06-20', '语文', 80),
('李四', '2009-06-20', '数学', 85),
('李四', '2009-06-20', '英语', 90),
('李四', '2009-09-20', '语文', 75),
('李四', '2009-09-20', '数学', 80),
('李四', '2009-09-20', '英语', 88);
现在要获得如表12-23所示的格式,每个学生按考试日期在表中占一行。
表12-23 转换后结果
stu_name |
exam_date |
语文 |
数学 |
英语 |
张三 |
2009-06-20 |
90 |
95 |
100 |
张三 |
2009-09-20 |
85 |
90 |
98 |
李四 |
2009-06-20 |
80 |
85 |
90 |
李四 |
2009-09-20 |
75 |
80 |
88 |
在许多情况下,都可以使用CASE表达式将表的行转换为列,这是一个非常有用的技巧。参考下面的语