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 Doris 6
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 Doris 6 F
2 Mary 11 F
3 Sherry 11 F
4 Sue 29 F
1 Larry 5 M
2 George 6 M
3 Sam 17 M
4 Ted 23 M
5 Marty 23 M
6 Frank 38 M
7 John 40 M注意,姓名M開始,序號又從1,2,3開始了
(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 Doris 6
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 Doris 6 F
2 Mary 11 F
2 Sherry 11 F
4 Sue 29 F
1 Larry 5 M
2 George 6 M
3 Sam 17 M
4 Ted 23 M
4 Marty 23 M
6 Frank 38 M
7 John 40 M
可以看到,按性別分組了,每個性別分組裏,繼續是用了rank( )函數
(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 Doris 6
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
Doris 6 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-941A-768D7A8CCB6C 108000
52A3CE16-74FD-4D5D-BB4F-F5F67A1E9D2F 120000
06B6B924-EAB2-4187-B733-EBB56B62E793 150000