瑞丽的SQL-SQL Server的表旋转(行列转换)
所谓表旋转,就是将表的行转换为列,或是将表的列转换为行,这是从SQL Server 2005開始提供的新技术。因此,如果希望使用此功能,须要将数据库的兼容级别设置为90。表旋转在某些方面也是攻克了表的数据存储和实际须要之间的矛盾。比如。图9-4所看到的的是一个典型的产品销售统计表,这样的格式尽管便于阅读,可是在进行数据表存储的时候却并不easy管理,产品销售数据表通常须要设计成图9-5所看到的的结构。
这样就带来一个问题,用户既希望数据easy管理。又希望能够生成一种能够easy阅读的表格数据。这时候就能够使用表旋转技术。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdob25nanU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
图9-4 产品销售表
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmdob25nanU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
图9-5 数据表结构
9.4.1 PIVOT运算符
PIVOT运算符用于将表的行转换为列,并能同一时候对行运行聚合运算。其语法格式例如以下:
SELECT <非旋转列>,
[第一个旋转列] AS <列名>,
[第二个旋转列] AS <列名>,
...
[最后的旋转列] AS <列名>
FROM
(<SELECT 生成数据的查询>) AS <为源查询结果指定的别名>
PIVOT
(
<聚合函数>(<被聚合的列>)
FOR
[<包括将被转换为列标头的值的列>]
IN ( [第一个旋转后的列], [第二个旋转后的列],
... [最后一个旋转后的列])
) AS <为旋转表指定的别名>
<可选的 ORDER BY 子句>;
为了实现行的旋转。源查询获得的结果应当具备三列。才干够实现旋转。第1列是不进行旋转的列,属于标志列;第2列是属性列。也称为透视列,当中的值会被旋转列名;第3列是属性值列,这些值将作为新列的值。
使用以下的语句创建一个演示样例表Orders,内容如表9-7所看到的。
CREATE TABLE Orders
(
ProductID int NOT NULL,
OrderDate datetime NOT NULL,
ShipTo char(20) NOT NULL,
SubTotal money NOT NULL
);
INSERT INTO Orders
VALUES (1,CAST('20090102' AS datetime), 'Shanghai', 100.00),
(1, CAST('20090105' AS datetime), 'Shanghai',100.00),
(1, CAST('20090123' AS datetime),'Jinan', 100.00),
(2, CAST('20090125' AS datetime),'Shanghai', 100.00),
(1, CAST('20090205' AS datetime),'Jinan', 100.00),
(3, CAST('20090213' AS datetime),'Shanghai', 100.00),
(3, CAST('20090219' AS datetime),'Shanghai', 100.00),
(4, CAST('20090309' AS datetime),'Beijing', 100.00),
(1, CAST('20090311' AS datetime),'Dalian', 100.00),
(2, CAST('20090324' AS datetime),'Shanghai', 100.00),
(3, CAST('20090326' AS datetime),'Wuhan', 100.00);
表9-7 Orders表的内容
|
ProductID |
OrderDate |
ShipTo |
SubTotal |
|
1 |
2009-01-02 00:00:00.000 |
Shanghai |
100.00 |
|
1 |
2009-01-05 00:00:00.000 |
Shanghai |
100.00 |
|
1 |
2009-01-23 00:00:00.000 |
Jinan |
100.00 |
|
2 |
2009-01-25 00:00:00.000 |
Shanghai |
100.00 |
|
1 |
2009-02-05 00:00:00.000 |
Jinan |
100.00 |
|
3 |
2009-02-13 00:00:00.000 |
Shanghai |
100.00 |
|
3 |
2009-02-19 00:00:00.000 |
Shanghai |
100.00 |
|
4 |
2009-03-09 00:00:00.000 |
Beijing |
100.00 |
|
1 |
2009-03-11 00:00:00.000 |
Dalian |
100.00 |
|
2 |
2009-03-24 00:00:00.000 |
Shanghai |
100.00 |
|
3 |
2009-03-26 00:00:00.000 |
Wuhan |
100.00 |
Orders表中包括了3个月的产品销售数据,如今如果要获得像图9-4所看到的的销售表,则对源表的查询首先须要获得上面讲的三列,參考以下的语句:
SELECT ProductID,
MONTH(OrderDate) AS OrderMonth,
SubTotal
FROM Orders;
查询结果如表9-8所看到的。当中ProductID为标志列,OrderMonth为属性列,当中的月份要转变为列的名称,SubTotal为属性值列,这些值将成为新列的值。
表9-8 获取到的三列内容
|
ProductID |
OrderMonth |
SubTotal |
|
1 |
1 |
100.00 |
|
1 |
1 |
100.00 |
|
1 |
1 |
100.00 |
|
2 |
1 |
100.00 |
|
1 |
2 |
100.00 |
|
3 |
2 |
100.00 |
|
3 |
2 |
100.00 |
|
4 |
3 |
100.00 |
|
1 |
3 |
100.00 |
|
2 |
3 |
100.00 |
|
3 |
3 |
100.00 |
完整的旋转查询语句例如以下。
查询结果如表9-9所看到的。
SELECT ProductID,
[1]AS Jan,
[2]AS Feb,
[3]AS Mar
FROM (SELECT ProductID, MONTH(OrderDate) ASOrderMonth, SubTotal
FROMOrders) AS O1
PIVOT
(
SUM(SubTotal)
FOROrderMonth IN ([1], [2], [3])
) AS Pvt
ORDER BY ProductID;
表9-9 旋转后输出的内容
|
ProductID |
Jan |
Feb |
Mar |
|
1 |
300.00 |
100.00 |
100.00 |
|
2 |
100.00 |
NULL |
100.00 |
|
3 |
NULL |
200.00 |
100.00 |
|
4 |
NULL |
NULL |
100.00 |
上面的查询语句将按以下的步骤来获取表9-9所看到的的结果集:
· PIVOT首先按属性值列之外的列(ProductID和OrderMonth)对输入表Sales.Orders进行分组汇总。相似运行以下的语句,得到一个如表9-10所看到的的中间结果集。
SELECT ProductID,
OrderMonth,
SUM(SubTotal) AS SubTotal
FROM (SELECT ProductID,MONTH(OrderDate) AS OrderMonth, SubTotal
FROM Orders) AS O1
GROUP BY ProductID,OrderMonth;
表9-10 Orders经分组汇总后的结果
|
ProductID |
OrderMonth |
SubTotal |
|
1 |
1 |
300.00 |
|
1 |
2 |
100.00 |
|
1 |
3 |
100.00 |
|
2 |
1 |
100.00 |
|
2 |
3 |
100.00 |
|
3 |
2 |
200.00 |
|
3 |
3 |
100.00 |
|
4 |
3 |
100.00 |
· PIVOT依据FOR OrderMonth IN指定的值1、2、3。首先在结果集中建立名为1、2、3的列。然后从表9-10所看到的的中间结果中取出SubTotal列中取出相符合的值,分别放置到1、2、3列中。
此时得到的结果集的别名为pvt(见语句中AS pvt的指定)。结果集的内容如表9-11所看到的。
表9-11 使用FOR OrderMonth IN ([1], [2], [3])后得到的结果集
|
ProductID |
1 |
2 |
3 |
|
1 |
300.00 |
100.00 |
100.00 |
|
2 |
100.00 |
NULL |
100.00 |
|
3 |
NULL |
200.00 |
100.00 |
|
4 |
NULL |
NULL |
100.00 |
· 最后依据SELECT ProductID, [1] AS Jan,[2] AS Feb, [3] AS Mar FROM的指定。从别名pvt结果集中检索数据,并分别将名为1、2、3的列在终于结果集中又一次命名为Jan、Feb、Mar。得到表9-9所看到的的结果集。
这里须要注意的是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表达式将表的行转换为列,这是一个很实用的技巧。參考以下的语句:
SELECT stu_name, exam_date,
CASEWHEN exam_sub = '语文' THENexam_score
ELSE NULL
ENDAS 语文,
CASEWHEN exam_sub = '数学' THENexam_score
ELSE NULL
ENDAS 数学,
CASEWHEN exam_sub = '英语' THENexam_score
ELSE NULL
ENDAS 英语
FROM exams;
上面语句将得到如表12-24所看到的的结果。
表12-24 使用CASE表达式得到的结果
|
stu_name |
exam_date |
语文 |
数学 |
英语 |
|
张三 |
2009-06-20 |
90 |
NULL |
NULL |
|
张三 |
2009-06-20 |
NULL |
95 |
NULL |
|
张三 |
2009-06-20 |
NULL |
NULL |
100 |
|
张三 |
2009-09-20 |
85 |
NULL |
NULL |
|
张三 |
2009-09-20 |
NULL |
90 |
NULL |
|
张三 |
2009-09-20 |
NULL |
NULL |
98 |
|
李四 |
2009-06-20 |
80 |
NULL |
NULL |
|
李四 |
2009-06-20 |
NULL |
85 |
NULL |
|
李四 |
2009-06-20 |
NULL |
NULL |
90 |
|
李四 |
2009-09-20 |
75 |
NULL |
NULL |
|
李四 |
2009-09-20 |
NULL |
80 |
NULL |
|
李四 |
2009-09-20 |
NULL |
NULL |
88 |
由上表能够看出,仅仅要按stu_name、exam_date分组计算最大值,就能够得到表12-23所要求的计算结果。參考以下的语句:
SELECT stu_name, exam_date,
MAX(CASE WHEN exam_sub = '语文' THEN exam_score
ELSE NULL
END) AS 语文,
MAX(CASE WHEN exam_sub = '数学' THEN exam_score
ELSE NULL
END) AS 数学,
MAX(CASE WHEN exam_sub = '英语' THEN exam_score
ELSE NULL
END) AS 英语
FROM exams
GROUP BY stu_name, exam_date
ORDER BY stu_name, exam_date;
瑞丽的SQL-SQL Server的表旋转(行列转换)的更多相关文章
- sql 行专列 列转行 普通行列转换
转载:http://www.cnblogs.com/newwind521/archive/2010/11/25/1887203.html sql 行专列 列转行 普通行列转换 /* 标题:普通行列转换 ...
- (sql server)玩转-数据库行列转换
虽然开发过程中没用过行列转换,但是听说面试时常常会遇到这个问题,以前在网上也看到过大神的例子,今天自己仔细的玩了下,希望和大家分享一下了. 注意:列转行的方法可能是我独创的了,呵呵,因为在网上找不到哦 ...
- ABAP 内表的行列转换-发货通知单-打印到Excel里-NEW-(以运单号为单位显示ALV然后保存输出)
*********************************************************************** * Title : ZSDF003 ...
- ABAP 内表的行列转换-发货通知单-打印到Excel里-NEW
*********************************************************************** * Title : ZSDF002 ...
- ABAP 内表的行列转换-发货通知单-打印到Excel里
需要传入数据到Excel里的模板如上图所示 ********************** * 设计主要逻辑与原理说明 ...
- ABAP 内表的行列转换-发货通知单2
*&---------------------------------------------------------------------* *& Report Z_TEST_C ...
- ABAP 内表的行列转换-发货通知单-SLIS
REPORT Z_TEST_COL_TO_ROW. TYPE-POOLS: slis. TABLES: VTTP,LIPS,LIKP,KNA1 ,VTTK. DATA: gd_fieldcat TYP ...
- ABAP 内表的行列转换-NEW
REPORT Z_TEST_COL_TO_ROW. TYPE-POOLS: slis. TABLES: mseg,mkpf. DATA: gd_fieldcat TYPE slis_t_fieldca ...
- ABAP 内表的行列转换
http://www.cnblogs.com/qlp1982/p/3370591.html
随机推荐
- 打印class文件的Java编译器内部的版本号
当改变了jdk版本时,在编译java时,会遇到Unsupported major.minor version错误.错误信息如下 : Unsupported major.minor version 50 ...
- 14.4.3.4 Configuring InnoDB Buffer Pool Prefetching (Read-Ahead) 配置InnoDB Buffer pool 预读
14.4.3.4 Configuring InnoDB Buffer Pool Prefetching (Read-Ahead) 配置InnoDB Buffer pool 预读 一个预读请求 是一个I ...
- 使用URLConnection提交请求
URL的openConnection()方法将返回一个URLConnection对象,该对象表示应用程序和URL之间的通信连接.程序可以通过URLConnection实例向该URL发送请求,读取URL ...
- 不断摸索发现用 andy 模拟器很不错,感觉跟真机差不多
嗯,今天也遇到了模拟的问题.那个慢啊,好几分钟才能开机,加载程序总共差不多十几分钟.当时想如果真做android开发必须换电脑啊.后来不断摸索发现用 andy 模拟器很不错,感觉跟真机差不多. 还是真 ...
- POJ 3076 Sudoku (dancing links)
题目大意: 16*16的数独. 思路分析: 多说无益. 想说的就是dancing links 的行是依照 第一行第一列填 1 第一行第二列填 2 -- 第一行第十五列填15 第一行第二列填 1 -- ...
- C++primer原书中的一个错误(派生类using声明对基类权限的影响)
在C++primer 第4版的 15章 15.2.5中有以下这样一段提示: "注解:派生类能够恢复继承成员的訪问级别,但不能使訪问级别比基类中原来指定的更严格或者更宽松." 在vs ...
- Linux ssh密钥自动登录(转)
在开发中,经常需要从一台主机ssh登陆到另一台主机去,每次都需要输一次login/Password,很繁琐.使用密钥登陆就可以不用输入用户名和密码了 实现从主机A免密码登陆到主机B,需要以下几个步骤: ...
- java Process在windows的使用汇总(转)
最常用的是ant(java工程中流行),maven,及通用的exec(只要有shell脚本如.sh,.bat,.exe,.cmd等).而其实前两者不容易出错,后者却遇到了以下问题:Caused by: ...
- 使用mysql-mmm实现MySQL高可用集群
背景:之前实现的mysql同步复制功能(见笔者之前文章http://blog.csdn.net/kingofworld/article/details/39210937)仅仅是双机热备功能,还不能做到 ...
- 百度map android sdk3.5实现定位 并跳转的指定坐标,加入标记
前几天又下载了新的百度地图sdk,3.5版本号.发现百度地图api有了较大变化 定位和3.0版本号差点儿相同 可是设置地图中心和加入maker标记有较大变化 设置地图中心点 // 定义地图状态zoom ...