SQL实例进阶-学习sql server2005 step by step(八)
1.SQL2005中row_number( )、rank( )、dense_rank( )、ntile( )函数的用法
(1).row_number( )
先来点数据,先建个表
1 SET NOCOUNT ON
2
3 CREATE TABLE Person(
4
5 FirstName VARCHAR(10),
6
7 Age INT,
8
9 Gender CHAR(1))
10
11 INSERT INTO Person VALUES ('Ted',23,'M')
12
13 INSERT INTO Person VALUES ('John',40,'M')
14
15 INSERT INTO Person VALUES ('George',6,'M')
16
17 INSERT INTO Person VALUES ('Mary',11,'F')
18
19 INSERT INTO Person VALUES ('Sam',17,'M')
20
21 INSERT INTO Person VALUES ('Doris',6,'F')
22
23 INSERT INTO Person VALUES ('Frank',38,'M')
24
25 INSERT INTO Person VALUES ('Larry',5,'M')
26
27 INSERT INTO Person VALUES ('Sue',29,'F')
28
29 INSERT INTO Person VALUES ('Sherry',11,'F')
30
31 INSERT INTO Person VALUES ('Marty',23,'F')
32
33
直接用例子说明问题:
SELECT ROW_NUMBER() OVER (ORDER BY Age) AS [Row Number by Age],FirstName,Age
FROM Person
出现的数据如下
Row Number by Age FirstName Age
-------------------------- ---------- --------
1 Larry 5
2
3 George 6
4 Mary 11
5 Sherry 11
6 Sam 17
7 Ted 23
8 Marty 23
9 Sue 29
10 Frank 38
11 John 40
可以观察到,是根据年龄升序排列了,并且row_number()是给出了序列号了,这个序列号被重命名为Row Number by Age,与sql server2000对比:如果在sql server2000中实现相对麻烦一些,我们可以利用IDENTITY()函数实现,但IDENTITY()函数只能用在sql server2000临时表中,因此需要将数据检索到临时表里。select identity(int,1,1) as [Row Number by Age],FirstName,Age into #A from Person order by Ageselect * from #Adrop table #a如果不想按年龄排序,可以这样写
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS [Row Number by Record Set],FirstName,Age
FROM Person另外一个例子SELECT ROW_NUMBER() OVER (PARTITION BY Gender ORDER BY Age) AS [Partition by Gender],FirstName,Age,Gender
FROM Person这里是按性别划分区间了,同一性别再按年龄来排序,输出结果如下
Partition by Gender FirstName Age Gender
-------------------- ---------- ----------- ------
1
2 Mary
3 Sherry
4 Sue
1 Larry
2 George
3 Sam
4 Ted
5 Marty
6 Frank
7 John
(2).RANK( )函数
先看例子
SELECT RANK() OVER (ORDER BY Age) AS [Rank by Age],FirstName,Age
FROM Person输出如下:Rank by Age FirstName Age
-------------------- ---------- -----------
1 Larry 5
2
2 George 6
4 Mary 11
4 Sherry 11
6 Sam 17
7 Ted 23
7 Marty 23
9 Sue 29
10 Frank 38
11 John 40
看到了么,同年岭的话,将有相同的顺序,顺序成1,2,2,4了。与sql server2000对比:出现了RANK()函数实在是方便,在sql server2000里实现排序并列的问题麻烦很多。
select [Rank by Age]=isnull((select count(*) from person where Age>A.Age),0)+1,FirstName,Age from Person A order
by [Rank by Age] SELECT RANK() OVER(PARTITION BY Gender ORDER BY Age) AS [Partition by Gender],FirstName, Age, Gender FROM Person
输出为
Partition by Gender FirstName Age Gender
-------------------- ---------- ----------- ------
1
2 Mary
2 Sherry
4 Sue
1 Larry
2 George
3 Sam
4 Ted
4 Marty
6 Frank
7 John
(3).DENSE_RANK( )函数
SELECT DENSE_RANK() OVER (ORDER BY Age) AS [Dense Rank by Age], FirstName, Age
FROM Person
输出结果为:
Dense Rank by Age FirstName Age
-------------------- ---------- -----------
1 Larry 5
2
2 George 6
3 Mary 11
3 Sherry 11
4 Sam 17
5 Ted 23
5 Marty 23
6 Sue 29
7 Frank 38
8 John 40
看到了么,和rank函数区别是,顺序始终是连续的,Doris 和George同年,都是排第2位,但之后的mary不象rank函数那样排第4,而是排第3位了
(4).ntile( )函数
SELECT FirstName,
Age,
NTILE(3) OVER (ORDER BY Age) AS [Age Groups]
FROM Person
输出结果:
FirstName Age Age Groups
---------- ----------- --------------------
Larry 5 1
George 6 1
Mary 11 1
Sherry 11 2
Sam 17 2
Ted 23 2
Marty 23 2
Sue 29 3
Frank 38 3
John 40 3
这个函数按照ntile(n)中的N,把记录强制分成多少段,11条记录现在分成3段了,lary到mary是第1段,sherry到maty是第2段,sue到john是第3段了。
2.SQLServer 2005 中的except/intersect和outer apply交并集计算
首先,建立两个表:
1 CREATE TABLE #a (ID INT)
2 INSERT INTO #a VALUES (1)
3 INSERT INTO #a VALUES (2)
4 INSERT INTO #a VALUES (null)
5
6 CREATE TABLE #b (ID INT)
7 INSERT INTO #b VALUES (1)
8 INSERT INTO #b VALUES (3)
9
10
我们的目的是从表#b中取出ID不在表#a的记录。
如果不看具体的insert的内容,单单看这个需求,可能很多朋友就会写出这个sql了:
select * from #b where id not in (select id from #a)
但是根据上述插入的记录,这个sql检索的结果不是我们期待的ID=3的记录,而是什么都没有返回。原因很简单:在子查询select id from #a中返回了null,而null是不能跟任何值比较的。
那么您肯定会有下面的多种写法了:
1 select * from #b where id not in (select id from #a where id is not null)
2 select * from #b b where b.id not in (select id from #a a where a.id=b.id)
3 select * from #b b where not exists (select 1 from #a a where a.id=b.id)
4
5
当然还有使用left join/right join/full join的几种写法,但是无一例外,都是比较冗长的。其实在SQL Server 2005增加了一种新的方法,可以帮助我们很简单、很简洁的完成任务:
select * from #b
except
select * from #a
我不知道在SQL Server 2008里还有没有什么更酷的方法,但是我想这个应该是最简洁的实现了。当然,在2005里还有一种方法可以实现:
1 select * from #b b
2 outer apply
3 (select id from #a a where a.id=b.id) k
4 where k.id is null
5
6
outer apply也可以完成这个任务。
如果我们要寻找两个表的交集呢?那么在2005就可以用intersect关键字:
select * from #b
intersect
select * from #a
ID
-----------
1
(1 row(s) affected)
0
3.使用coalesce和nullif的组合来减轻sql的工作
1 create table tbl (id int, type_a int)
2
3 insert into tbl values (1000,1000)
4 insert into tbl values (999,999)
5 insert into tbl values (998,998)
6 insert into tbl values (997,997)
7 insert into tbl values (996,996)
8 insert into tbl values (995,null)
9 insert into tbl values (994,null)
10 insert into tbl values (993,null)
11 insert into tbl values (992,null)
12 insert into tbl values (991,null)
13
14
逻辑非常简单:当type_a为997或null的时候,我们要让输出的type_a字段值为0。
OK,这个SQL语句当然有多种写法,朋友的sql是这样写的:
1 select
2 case
3 when (type_a is null or type_a=997) then 0
4 else type_a
5 end as type_a
6 from tbl
7
8
如果需要控制的字段一多,那这个及时已经使用了缩进的select也看起来很复杂了,时间久了想改动这个sp的逻辑就有些吃力了,我们常常在做计划时会说“半小时搞定这个问题”,但是往往在做的时候都会超过这个时间,原因就在于我们总有从一团乱麻中找到入手点。复杂的代码和逻辑往往是解决问题中难啃的骨头。那么有什么好办法优化一下吗?
select coalesce(nullif(type_a,997),0) as type_a from tbl
Well,上面写了6行的sql就被这1行所替代了。
nullif接受两个参数,如果两个参数相等,那么返回null,否则返回第一个参数
coalesce接受N个参数,返回第一个不为null的参数
So,当您遇到处理一个如下所示的计算工资的问题的时候,不妨这样来解决:
create table salary (e_id uniqueidentifier, byMonth int, byHalfYear int, byYear int)
insert into salary values (newid(),9000,null,null)
insert into salary values (newid(),null,60000,null)
insert into salary values (newid(),null,null,150000)
每个雇员有3种薪资计算方式(按月,按半年,按年)来发放工资,如果我们想统计每个员工的年薪,那这样一句就够了:
select e_id,coalesce(byMonth*12,byHalfYear*2,byYear) as salary_amount from salary
结果:
e_id salary_amount
------------------------------------ -------------
8935330D-2B73-4FEF
06B6B924-EAB2-4187-B733-EBB56B62E793 150000
参考:
COALESCE (Transact-SQL)
NULLIF (Transact-SQL)
4.递归子查询(父子关系的)
1 --测试数据
2
3 CREATE TABLE tb(ID char(3),PID char(3),Name nvarchar(10))
4
5 INSERT tb SELECT '001',NULL ,'山东省'
6
7 UNION ALL SELECT '002','001','烟台市'
8
9 UNION ALL SELECT '004','002','招远市'
10
11 UNION ALL SELECT '003','001','青岛市'
12
13 UNION ALL SELECT '005',NULL ,'四会市'
14
15 UNION ALL SELECT '006','005','清远市'
16
17 UNION ALL SELECT '007','006','小分市'
18
19 GO
20
21
22
23 --查询指定节点及其所有子节点的函数
24
25 CREATE FUNCTION f_Cid(@ID char(3))
26
27 RETURNS @t_Level TABLE(ID char(3),Level int)
28
29 AS
30
31 BEGIN
32
33 DECLARE @Level int
34
35 SET @Level=1
36
37 INSERT @t_Level SELECT @ID,@Level
38
39 WHILE @@ROWCOUNT>0
40
41 BEGIN
42
43 SET @Level=@Level+1
44
45 INSERT @t_Level SELECT a.ID,@Level
46
47 FROM tb a,@t_Level b
48
49 WHERE a.PID=b.ID
50
51 AND b.Level=@Level-1
52
53 END
54
55 RETURN
56
57 END
58
59 GO
60
61
62
63 --调用函数查询002及其所有子节点
64
65 SELECT a.*
66
67 FROM tb a,f_Cid('002') b
68
69 WHERE a.ID=b.ID
70
71 /*--结果
72
73 ID PID Name
74
75 ------ ------- ----------
76
77 002 001 烟台市
78
79 004 002 招远市
80
------SqlServer2005
在SQL Server 2005数据库中,递归查询对于同一个表父子关系的计算提供了很大的方便,下文中的示例使用了SQL server 2005中的递归查询,使用的表是CarParts,这个表存储了一辆汽车的所有零件以及结构,part是零件单位,subpart是子零件,Qty是数量。
示例如下:
1 */
2
3 CREATE table CarParts
4
5 (
6
7 CarID INT NOT NULL,
8
9 Part VARCHAR(15),
10
11 SubPart VARCHAR(15),
12
13 Qty INT
14
15 )
16
17 GO
18
19 INSERT CarParts VALUES (1, 'Body', 'Door', 4)
20
21 INSERT CarParts VALUES (1, 'Body', 'Trunk Lid', 1)
22
23 INSERT CarParts VALUES (1, 'Body', 'Car Hood', 1)
24
25 INSERT CarParts VALUES (1, 'Door', 'Handle', 1)
26
27 INSERT CarParts VALUES (1, 'Door', 'Lock', 1)
28
29 INSERT CarParts VALUES (1, 'Door', 'Window', 1)
30
31 INSERT CarParts VALUES (1, 'Body', 'Rivets', 1000)
32
33 INSERT CarParts VALUES (1, 'Door', 'Rivets', 100)
34
35 INSERT CarParts VALUES (1, 'Door', 'Mirror', 1)
36
37 INSERT CarParts VALUES (1, 'Mirror', 'small_Mirror', 4)
38
39 GO
40
41 SELECT * FROM CarParts
42
43 GO
44
45 /*
46
47 一辆汽车需要各个零件的数目
48
49 1个Body 需要4个Door
50
51 1个Door 需要1个Mirror
52
53 那么
54
55 1个body需要4个Mirror
56
57 结构很简单吧
58
59 */
60
61 WITH CarPartsCTE(SubPart, Qty)
62
63 AS
64
65 (
66
67 -- 固定成员 (AM):
68
69 -- SELECT查询无需参考CarPartsCTE
70
71 -- 递归从此处开始
72
73 SELECT SubPart, Qty
74
75 FROM CarParts
76
77 WHERE Part = 'Body'
78
79 UNION ALL
80
81 -- 递归成员 (RM):
82
83 -- SELECT查询参考CarPartsCTE
84
85 -- 使用现有数据往下一层展开
86
87 SELECT CarParts.SubPart, CarPartsCTE.Qty * CarParts.Qty
88
89 FROM CarPartsCTE
90
91 INNER JOIN CarParts ON CarPartsCTE.SubPart = CarParts.Part
92
93 WHERE CarParts.CarID = 1
94
95 )
96
97 SELECT SubPart,Qty AS TotalNUM
98
99 FROM CarPartsCTE
100
101 /*
102
103 注意看最下层的small_Mirror 位于 表最后的位置,
104
105 由此可以看出改递归不是开始就进行递归查询而是在1层完全展开后在根据该层展开下一层不是深度优先的递归
106
107 */
108
109 drop table CarParts
110
111 --------------------------------result---------------------------------------
112
113 CarID Part SubPart Qty
114
115 ----------- --------------- --------------- -----------
116
117 1 Body Door 4
118
119 1 Body Trunk Lid 1
120
121 1 Body Car Hood 1
122
123 1 Door Handle 1
124
125 1 Door Lock 1
126
127 1 Door Window 1
128
129 1 Body Rivets 1000
130
131 1 Door Rivets 100
132
133 1 Door Mirror 1
134
135 1 Mirror small_Mirror 4
136
137 (10 row(s) affected)
138
139 SubPart TotalNUM
140
141 --------------- -----------
142
143 Door 4
144
145 Trunk Lid 1
146
147 Car Hood 1
148
149 Rivets 1000
150
151 Handle 4
152
153 Lock 4
154
155 Window 4
156
157 Rivets 400
158
159 Mirror 4
160
161 small_Mirror 16
162
163 (10 row(s) affected)
164
165
示例:
以下示例显示经理以及向经理报告的雇员的层次列表。
1 WITH DirectReports(groupid, member, EmployeeLevel,type) AS
2
3 (
4
5 SELECT groupid, member, 0,type AS EmployeeLevel
6
7 FROM groupinfo
8
9 WHERE groupid = 'finance_company'
10
11 UNION ALL
12
13 SELECT e.groupid, e.member, EmployeeLevel + 1,e.type
14
15 FROM groupinfo e
16
17 INNER JOIN DirectReports d
18
19 ON e.groupid = d.member
20
21 )
22
23 SELECT b.nickname,groupid, member, EmployeeLevel,type
24
25 FROM DirectReports,userbasicinfo b
26
27 where DirectReports.member=b.id
28
29 and type = 1
30
31
最后顺带一个小提示:(很多人开始用的时候比较迷糊)
group by 有一个原则,就是 select 后面的所有列中,没有使用聚合函数的列,必须出现在 group by 后面
select n1,n2,n3,n4,max(n4) from table group by n1,n2,n3.