每天10分钟学习T-SQL语言基础(Part 3)

【数据库技术】作者 / Edison Zhou

Microsoft SQL Server 2008技术内幕:T-SQL语言基础》是一本关于T-SQL方面的好书,可能现在我们在京东上都买不到了,我也是在2014年在淘宝上淘的。看完之后,我总结了一些精华笔记,现将其分成一个系列的笔记文章分享与你,每篇预计阅读时间为10分钟左右。上一篇介绍了SQL Server的表表达式及集合运算,本篇会介绍透视、逆透视、分组。

透视

所谓透视(Pivoting)就是把数据从行的状态旋转为列的状态的处理。其处理步骤为:

相信很多人在笔试或面试的时候被问到如何通过SQL实现行转列或列转行的问题,可能很多人当时懵逼了,没关系,下面我们通过例子来理解。

  (1)准备数据

--1.0 准备数据

USE tempdb;

IF OBJECT_ID('dbo.Orders', 'U') IS NOT NULL

DROP TABLE dbo.Orders;

GO

CREATE TABLE dbo.Orders

(

orderid  INT        NOT NULL,

orderdate DATE      NOT NULL, -- prior to SQL Server 2008 use DATETIME

empid    INT        NOT NULL,

custid    VARCHAR(5) NOT NULL,

qty      INT        NOT NULL,

CONSTRAINT PK_Orders PRIMARY KEY(orderid)

);

INSERT INTO dbo.Orders

(orderid, orderdate, empid, custid, qty)

VALUES

(30001, '20070802', 3, 'A', 10),

(10001, '20071224', 2, 'A', 12),

(10005, '20071224', 1, 'B', 20),

(40001, '20080109', 2, 'A', 40),

(10006, '20080118', 1, 'C', 14),

(20001, '20080212', 2, 'B', 12),

(40005, '20090212', 3, 'A', 10),

(20002, '20090216', 1, 'C', 20),

(30003, '20090418', 2, 'B', 15),

(30004, '20070418', 3, 'C', 22),

(30007, '20090907', 3, 'D', 30);

SELECT * FROM dbo.Orders;

  这里使用了MS SQL2008的VALUES子句格式语法,这时2008版本的新特性。如果你使用的是2005及以下版本,你需要多个INSERT语句。最后的执行结果如下图所示:

  (2)需求说明

  假设我们要生成一个报表,包含每个员工和客户组合之间的总订货量。用以下简单的分组查询可以解决这个问题:

select

empid,custid,SUM(qty) as sumqty

from dbo.Ordersgroup by empid,custid;

  该查询的执行结果如下:

  不过,假设现在要求要按下表所示的的格式来生成输出结果:

  这时,我们就需要进行透视转换了!

  (3)使用标准SQL进行透视转换

  Step1.分组:GROUP BY empid;

  Step2.扩展:CASE WHEN custid='A' THEN qty END;

  Step3.聚合:SUM(CASE WHEN custid='A' THEN qty END);

--1.1标准SQL透视转换

select empid,

SUM(case when custid='A' then qty end) as A,

SUM(case when custid='B' then qty end) as B,

SUM(case when custid='C' then qty end) as C,

SUM(case when custid='D' then qty end) as D

from dbo.Orders

group by empid;

  执行结果如下图所示:

  (4)使用T-SQL PIVOT运算符进行透视转换

  自SQL Server 2005开始引入了一个T-SQL独有的表运算符-PIVOT,它可以对某个源表或表表达式进行操作、透视数据,再返回一个结果表。

  PIVOT运算符同样涉及前面介绍的三个逻辑处理阶段(分组、扩展和聚合)以及同样的透视转换元素,但使用的是不同的、SQL Server原生的语法。

  下面是使用PIVOT运算符实现上面一样的效果:

select

empid,A,B,C,D

from

(

select

empid,custid,qty

from dbo.Orders) as D

pivot (sum(qty) for custid in (A,B,C,D)

) as P;

  其中,PIVOT运算符的圆括号内要指定聚合函数(本例中SUM)、聚合元素(本例中的qty)、扩展元素(custid)以及目标列名称的列表(本例中的A、B、C、D)。在PIVOT运算符的圆括号后面,可以为结果表制定一个别名。

Tip:使用PIVOT运算符一般不直接把它应用到源表(本例中的Orders表),而是将其应用到一个表表达式(该表表达式只包含透视转换需要的3种元素,不包含其他属性。)此外,不需要为它显式地指定分组元素,也就不需要再查询中使用GROUP BY子句。

逆透视

所谓逆透视(Unpivoting)转换是一种把数据从列的状态旋转为行的状态的技术,它将来自单个记录中多个列的值扩展为单个列中具有相同值得多个记录。换句话说,将透视表中的每个源行潜在地转换成多个行,每行代表源透视表的一个指定的列值。

还是通过一个栗子来理解:

  (1)首先还是准备一下数据:

USE tempdb;

IF OBJECT_ID('dbo.EmpCustOrders', 'U') IS NOT NULL

DROP TABLE dbo.EmpCustOrders;

SELECT

empid, A, B, C, DINTO dbo.EmpCustOrders

FROM

(

SELECT

empid, custid, qty

FROM dbo.Orders) AS D

PIVOT(SUM(qty) FOR custid IN(A, B, C, D)

) AS P;

SELECT * FROM dbo.EmpCustOrders;

  下面是对这个表EmpCustOrders的查询结果:

  (2)需求说明

  要求执行你透视转换,为每个员工和客户组合返回一行记录,其中包含这一组合的订货量。期望的输出结果如下图所示:

  (3)标准SQL进行逆透视转换

  Step1.生成副本:CROSS JOIN 交叉联接生成多个副本

  Step2.提取元素:通过CASE语句生成qty数据列

  Step3.删除不相关的交叉:过滤掉NULL值

select

*

from

(

select

empid, custid,

case custid

when 'A' then A

when 'B' then B

when 'C' then C

when 'D' then D

end as qty

from dbo.EmpCustOrders

cross join ( VALUES('A'),('B'),('C'),('D') ) as Custs(custid)

) as D

where qty is not null;

  执行结果如下图所示:

  (4)T-SQL UNPIVOT运算符进行逆透视转换

  和PIVOT类似,在SQL Server 2005引入了一个UNPIVOT运算符,它的作用刚好和PIVOT运算符相反,即我们可以拿来做逆透视转换工作。UNPIVOT同样会经历我们上面提到的三个阶段。继续上面的栗子,我们使用UNPIVOT来进行逆透视转换:

select

empid, custid, qty

from dbo.EmpCustOrders

unpivot (qty for custid in (A,B,C,D)) as U;

  其中,UNPIVOT运算符后边的括号内包括:用于保存源表列值的目标列明(这里是qty),用于保存源表列名的目标列名(这里是custid),以及源表列名列表(A、B、C、D)。同样,在UNPIVOT括号后面也可以跟一个别名。

Tip:对经过透视转换所得的表再进行逆透视转换,并不能得到原来的表。因为你透视转换只是把经过透视转换的值再旋转岛另一种新的格式。

分组

首先了解一下分组集:分组集就是分组(GROUP BY子句)使用的一组属性(或列名)。在传统SQL中,一个聚合查询只能定义一个分组集。为了灵活而有效地处理分组集,SQL Server 2008引入了几个重要的新功能(他们都是GROUP BY的从属子句,需要依赖于GROUP BY子句):

  (1)GROUPING SETS从属子句

  使用该子句,可以方便地在同一个查询中定义多个分组集。例如下面,我们定义了4个分组集:(empid,custid),(empid),(custid)和():

--3.1 GROUPING SETS从属子句

select

empid,custid,SUM(qty) as sumqty

from dbo.Orders

group by

GROUPING SETS  (    (empid,custid),    (empid),    (custid),    ()  );

  这个查询相当于执行了四个group by查询的并集。

  (2)CUBE从属子句

  CUBE子句为定义多个分组集提供了一种更简略的方法,可以把CUBE子句看作是用于生成分组的幂集。例如:CUBE(a,b,c)等价于GROUPING SETS[(a,b,c),(a,b),(a,c),(b,c),(a),(b),(c),()]。下面我们用CUBE来实现上面的例子:

--3.2 CUEE从属子句

select

empid,custid,SUM(qty) as sumqty

from dbo.Orders

group by cube(empid,custid);

  (3)ROLLUP从属子句

  ROLLUP子句也是一种简略的方法,只不过它与CUBE不同,它强调输入成员之间存在一定的层次关系,从而生成让这种层次关系有意义的所有分组集。例如:CUBE(a,b,c)会生成8个可能的分组集,而ROLLUP则认为3个输入成员存在a>b>c的层次关系,所以只会生成4个分组集:(a,b,c),(a,b),(a),()。

  下面我们假设想要按时间层次关系:订单年份>订单月份>订单日,以这样的关系来定义所有分组集,并未每个分组集返回其总订货量。可能我们用GROUPING SETS需要4行,然后使用ROLLUP却只需要一行:group by rollup(YEAR(orderdate),MONTH(orderdate),DAY(orderdate));

  完整SQL查询如下:

--3.3 ROLLUP从属子句

select

YEAR(orderdate) as orderyear,

MONTH(orderdate) as ordermonth,

DAY(orderdate) as orderday,

SUM(qty) as sumqty

from dbo.Orders

group by rollup(YEAR(orderdate),

MONTH(orderdate),DAY(orderdate));

  执行结果如下图所示:

  (4)GROUPING_ID函数

  如果一个查询定义了多个分组集,还想把结果行和分组集关联起来,也就是说,为每个结果行标注它是和哪个分组集关联的。SQL Server 2008中引入了一个GROUPING_ID函数,简化了关联结果行和分组集的处理,可以容易地计算出每一行和哪个分组集相关联。

  例如,继续上面的例子,我们想要将empid,custid作为输入:

select

grouping_id(empid,custid) as groupingset,

empid, custid, SUM(qty) as sumqty

from dbo.Ordersgroup by cube(empid,custid);

  执行结果中会出现groupingset为0,1,2,3,分别代表了empid,custid的4个可能的分组集((empid,custid),(empid),(custid),())。

小结

本文介绍了MS SQL Server 2008的透视、逆透视 及 分组,下一篇会介绍数据修改。

参考资料

[美] Itzik Ben-Gan 著,成保栋 译,《Microsoft SQL Server 2008技术内幕:T-SQL语言基础》

考虑到很多人买了这本书,却下载不了这本书的配套源代码和示例数据库,特意上传到了百度云盘中,下载链接:https://pan.baidu.com/s/1jIryBUA

强烈建议大家阅读完每一章节后,练习一下课后习题,相信或多或少都会有一些收获。

The End

收藏


举报

条评论

你可能感兴趣的:(每天10分钟学习T-SQL语言基础(Part 3))