总结SQL Server窗口函数的简单使用
生活随笔
收集整理的這篇文章主要介紹了
总结SQL Server窗口函数的简单使用
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
前言:我一直十分喜歡使用SQL Server2005/2008的窗口函數(shù),排名函數(shù)ROW_NUMBER()尤甚。今天晚上我在查看SQL Server開發(fā)的相關(guān)文檔,整理收藏夾發(fā)現(xiàn)了兩篇收藏已久的好文,后知后覺,讀后又有點(diǎn)收獲,順便再總結(jié)一下。
窗口函數(shù)使用OVER函數(shù)實(shí)現(xiàn),OVER函數(shù)分帶參和不帶參兩種。其中可選參數(shù)PARTITION BY用于將數(shù)據(jù)按照特定字段分組。 3、簡(jiǎn)單示例 查詢學(xué)生成績(jī)表的基本列以及所有班級(jí)所有學(xué)生的語(yǔ)文平均分: SELECT--Id,--CreateDate,StudentId,ClassId,CourseId,Score,CAST(AVG(Score) OVER() AS decimal(5,2) )AS '語(yǔ)文平均分' FROMStudentScoreWHERE CourseId=2 結(jié)果如下: 4、PARTITION BY 如果我們需要查詢每一個(gè)班級(jí)的語(yǔ)文平均分,可以根據(jù)PARTION BY來進(jìn)行分組: SELECTId,CreateDate,StudentId,ClassId,CourseId,Score,CAST(AVG(Score) OVER(PARTITION BY ClassId ) AS decimal(5,2) )AS '語(yǔ)文平均分' FROMStudentScoreWHERE CourseId=2 查詢結(jié)果如下: 圖可能不清楚,三個(gè)班級(jí)的語(yǔ)文平均分是不同的。 到這里,其實(shí)你可能已經(jīng)體會(huì)到使用OVER函數(shù)的好處了: a、OVER子句的優(yōu)點(diǎn)就是能夠在返回基本列的同時(shí),在同一行對(duì)它們進(jìn)行聚合
b、可以在表達(dá)式中混合使用基本列和聚合列 如果我們使用傳統(tǒng)的GROUP BY分組查詢,直接獲取基本列和聚合列就不是這么簡(jiǎn)單一句SQL了。 如你所知,我們知道的很多聚合函數(shù),如SUM,AVG,MAX,MIN等聚合函數(shù)都支持窗口函數(shù)的運(yùn)算。
SELECTId, -- CreateDate,ROW_NUMBER() OVER(ORDER BY Score DESC) AS '序號(hào)',StudentId,ClassId,CourseId,Score FROMStudentScoreWHERE CourseId=8 結(jié)果如下: 據(jù)我所知,此函數(shù)在SQL Server分頁(yè)查詢中幾乎已經(jīng)普及應(yīng)用。Good job。 2、RANK()和DENSE_RANK() (1)、RANK()函數(shù) 返回結(jié)果集的分區(qū)內(nèi)每行的排名。行的排名是相關(guān)行之前的排名數(shù)加一。如果兩個(gè)或多個(gè)行與一個(gè)排名關(guān)聯(lián),則每個(gè)關(guān)聯(lián)行將得到相同的排名。 SELECTId, -- CreateDate,RANK() OVER(ORDER BY Score DESC) AS '序號(hào)',StudentId,ClassId,CourseId,Score FROMStudentScoreWHERE CourseId=8 結(jié)果如下: 注意,它和ROW_NUMBER()的異同點(diǎn),您應(yīng)該已經(jīng)知道了: a、RANK函數(shù)和ROW_NUMBER函數(shù)類似,它們都是用來對(duì)結(jié)果進(jìn)行排序。
b、不同的是,ROW_NUMBER函數(shù)為每一個(gè)值生成唯一的序號(hào),而RANK函數(shù)為相同的值生成相同的序號(hào)。
上圖中,兩個(gè)86分的學(xué)生對(duì)應(yīng)的序號(hào)都是3,而接著排在它們下面的序號(hào)直接變成了5。 (2)、DENSE_RANK()函數(shù) 返回結(jié)果集分區(qū)中行的排名,在排名中沒有任何間斷。行的排名等于所討論行之前的所有排名數(shù)加一。如果有兩個(gè)或多個(gè)行受同一個(gè)分區(qū)中排名的約束,則每個(gè)約束行將接收相同的排名。 SELECTId, -- CreateDate,DENSE_RANK() OVER(ORDER BY Score DESC) AS '序號(hào)',StudentId,ClassId,CourseId,Score FROMStudentScoreWHERE CourseId=8 查詢結(jié)果如下: 上圖中,兩個(gè)86分的學(xué)生對(duì)應(yīng)的序號(hào)都是3,而接著排在它們下面的序號(hào)是4(也就是說DENSE_RANK()函數(shù)查詢的序號(hào)是類似ROW_NUMBER()那樣連續(xù)的,但是對(duì)于相同值的行生成相同的序號(hào),從這一點(diǎn)上來說,對(duì)于相同查詢條件和排序的查詢,ROW_NUMBER()函數(shù)查詢的結(jié)果集是DENSE_RANK()函數(shù)查詢的結(jié)果的子集)。這也是我們可以總結(jié)出的RANK和DENSE_RANK()這兩個(gè)函數(shù)的最大的不同點(diǎn)。 3、NTILE() NTILE函數(shù)把結(jié)果中的行關(guān)聯(lián)到組,并為每一行分配一個(gè)所屬的組的編號(hào),編號(hào)從一開始。對(duì)于每一個(gè)行,NTILE 將返回此行所屬的組的編號(hào)。
如果分區(qū)的行數(shù)不能被 integer_expression 整除,則將導(dǎo)致一個(gè)成員有兩種大小不同的組。按照 OVER 子句指定的順序,較大的組排在較小的組前面。 SELECTId, -- CreateDate,NTILE(6) OVER(ORDER BY ClassId DESC) AS '組編號(hào)',StudentId,ClassId,CourseId,Score FROMStudentScoreWHERE CourseId=8 查詢的結(jié)果如下: 本文的介紹和示例都很基礎(chǔ),但是通過窗口函數(shù),確實(shí)可以幫我們優(yōu)化很多復(fù)雜查詢。上面的SQL語(yǔ)句看上去每一個(gè)都很簡(jiǎn)單,但是現(xiàn)在的簡(jiǎn)單都隱藏著背后的復(fù)雜。需要提醒的是,分組概念雖然基礎(chǔ)卻很重要,你必須掌握;而熟練應(yīng)用了窗口函數(shù),你的SQL查詢就如虎添翼更上層樓了。 最后,我一直擔(dān)心對(duì)于海量數(shù)據(jù),SQL Server的性能問題。因?yàn)榻诘拈_發(fā)碰巧遇到海量數(shù)據(jù)的查詢,最多的過億,數(shù)據(jù)量最少的一個(gè)表,也過5000萬,不知道用了分區(qū)表性能有沒有明顯提升。
參考文章: http://msdn.microsoft.com/zh-cn/library/ms189461.aspx http://www.cnblogs.com/aierong/archive/2008/08/26/1273890.html http://www.cnblogs.com/aierong/archive/2008/08/18/1269407.html
一、從一個(gè)熟悉的示例說起
我們熟知的數(shù)據(jù)庫(kù)分頁(yè)查詢,以這一篇介紹過的為例吧。分頁(yè)查詢Person表中的人,可以這么寫SQL語(yǔ)句: WITH Record AS (SELECTRow_Number() OVER (ORDER BY Id DESC) AS RecordNumber,Id,FirstName,LastName,Height,WeightFROMPerson (NOLOCK) )SELECT RecordNumber,(SELECT COUNT(0) FROM Record) AS TotalCount,Id,FirstName,LastName,Height,WeightFROM RecordWHERE RecordNumber BETWEEN 1 AND 10 其中,ROW_NUMBER()是排名函數(shù),而緊隨其后的 OVER()函數(shù)就是窗口函數(shù)。 你還在用二次top方式的分頁(yè)查詢嗎?可以考慮嘗試使用排名函數(shù)配合CTE實(shí)現(xiàn)分頁(yè)。二、窗口函數(shù)
本文介紹窗口函數(shù),以下面的學(xué)生成績(jī)表為例: CREATE TABLE [StudentScore]([Id] [int] IDENTITY(1,1) NOT NULL,[StudentId] [int] NOT NULL CONSTRAINT [DF_StudentScore_StudentId] DEFAULT ((0)),[ClassId] [int] NOT NULL CONSTRAINT [DF_StudentScore_ClassId] DEFAULT ((0)),[CourseId] [int] NOT NULL CONSTRAINT [DF_StudentScore_CourseId] DEFAULT ((0)),[Score] [float] NOT NULL CONSTRAINT [DF_StudentScore_Score] DEFAULT ((0)),[CreateDate] [datetime] NOT NULL CONSTRAINT [DF_StudentScore_CreateDate] DEFAULT (getdate()) ) ON [PRIMARY] 其中,Id是自增Id,CreateDate是錄入時(shí)間,StudentId 學(xué)生,ClassId 班級(jí),CourseId? 課程 ,Score? 分?jǐn)?shù)。 錄入一些測(cè)試數(shù)據(jù)如下: --CourseId 2:語(yǔ)文 4:數(shù)學(xué) 8:英語(yǔ)--1班學(xué)生成績(jī) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (1,1,2,85) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (2,1,2,95.5) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (3,1,2,90)INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (1,1,4,90) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (2,1,4,98) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (3,1,4,89)INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (1,1,8,80) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (2,1,8,75.5) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (3,1,8,77)--2班學(xué)生成績(jī) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (1,2,2,90) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (2,2,2,77) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (3,2,2,78) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (4,2,2,83)INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (1,2,4,98) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (2,2,4,95) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (3,2,4,78) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (4,2,4,100)INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (1,2,8,85) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (2,2,8,90) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (3,2,8,86) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (4,2,8,78.5)--3班學(xué)生成績(jī) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (1,3,2,82) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (2,3,2,78) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (3,3,2,91)INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (1,3,4,83) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (2,3,4,78) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (3,3,4,99)INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (1,3,8,86) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (2,3,8,78) INSERT INTO StudentScore(StudentId,ClassId,CourseId,Score)VALUES (3,3,8,97) 窗口函數(shù)是SQL Server2005新增的函數(shù)。下面就談?wù)勊幕靖拍?#xff1a; 1、窗口函數(shù)的作用 窗口函數(shù)是對(duì)一組值進(jìn)行操作,不需要使用GROUP BY 子句對(duì)數(shù)據(jù)進(jìn)行分組,還能夠在同一行中同時(shí)返回基礎(chǔ)行的列和聚合列。舉例來說,我們要得到一個(gè)年級(jí)所有班級(jí)所有學(xué)生的平均分,按照傳統(tǒng)的寫法,我們肯定是通過AVG聚合函數(shù)來實(shí)現(xiàn)求平均分。這樣帶來的”壞處“是我們不能輕松地返回基礎(chǔ)行的列(班級(jí),學(xué)生等列),而只能得到聚合列。因?yàn)榫酆虾瘮?shù)的要點(diǎn)就是對(duì)一組值進(jìn)行聚合,以GROUP BY 查詢作為操作的上下文,由于GROUP BY 操作對(duì)數(shù)據(jù)進(jìn)行分組后,查詢?yōu)槊總€(gè)組只返回一行數(shù)據(jù),因此,要限制所有表達(dá)式為每個(gè)組只返回一個(gè)值。而通過窗口函數(shù),基礎(chǔ)列和聚合列的查詢都輕而易舉。 2、基本語(yǔ)法 OVER([PARTITION BY value_expression,..[n] ] <ORDER BY BY_Clause>)窗口函數(shù)使用OVER函數(shù)實(shí)現(xiàn),OVER函數(shù)分帶參和不帶參兩種。其中可選參數(shù)PARTITION BY用于將數(shù)據(jù)按照特定字段分組。 3、簡(jiǎn)單示例 查詢學(xué)生成績(jī)表的基本列以及所有班級(jí)所有學(xué)生的語(yǔ)文平均分: SELECT--Id,--CreateDate,StudentId,ClassId,CourseId,Score,CAST(AVG(Score) OVER() AS decimal(5,2) )AS '語(yǔ)文平均分' FROMStudentScoreWHERE CourseId=2 結(jié)果如下: 4、PARTITION BY 如果我們需要查詢每一個(gè)班級(jí)的語(yǔ)文平均分,可以根據(jù)PARTION BY來進(jìn)行分組: SELECTId,CreateDate,StudentId,ClassId,CourseId,Score,CAST(AVG(Score) OVER(PARTITION BY ClassId ) AS decimal(5,2) )AS '語(yǔ)文平均分' FROMStudentScoreWHERE CourseId=2 查詢結(jié)果如下: 圖可能不清楚,三個(gè)班級(jí)的語(yǔ)文平均分是不同的。 到這里,其實(shí)你可能已經(jīng)體會(huì)到使用OVER函數(shù)的好處了: a、OVER子句的優(yōu)點(diǎn)就是能夠在返回基本列的同時(shí),在同一行對(duì)它們進(jìn)行聚合
b、可以在表達(dá)式中混合使用基本列和聚合列 如果我們使用傳統(tǒng)的GROUP BY分組查詢,直接獲取基本列和聚合列就不是這么簡(jiǎn)單一句SQL了。 如你所知,我們知道的很多聚合函數(shù),如SUM,AVG,MAX,MIN等聚合函數(shù)都支持窗口函數(shù)的運(yùn)算。
二、讓人愛不釋手的排名函數(shù)
SQL Server提供了4個(gè)排名函數(shù):ROW_NUMBER(), RANK(),DENSE_RANK()和NTILE()。下面通過示例重點(diǎn)談?wù)勥@四個(gè)函數(shù)的使用。 1、ROW_NUMBER() 返回結(jié)果集分區(qū)內(nèi)行的序列號(hào),每個(gè)分區(qū)的第一行從 1 開始。ORDER BY 子句可確定在特定分區(qū)中為行分配唯一 ROW_NUMBER 的順序。 下面的查詢按照數(shù)學(xué)成績(jī)逆序排列:SELECTId, -- CreateDate,ROW_NUMBER() OVER(ORDER BY Score DESC) AS '序號(hào)',StudentId,ClassId,CourseId,Score FROMStudentScoreWHERE CourseId=8 結(jié)果如下: 據(jù)我所知,此函數(shù)在SQL Server分頁(yè)查詢中幾乎已經(jīng)普及應(yīng)用。Good job。 2、RANK()和DENSE_RANK() (1)、RANK()函數(shù) 返回結(jié)果集的分區(qū)內(nèi)每行的排名。行的排名是相關(guān)行之前的排名數(shù)加一。如果兩個(gè)或多個(gè)行與一個(gè)排名關(guān)聯(lián),則每個(gè)關(guān)聯(lián)行將得到相同的排名。 SELECTId, -- CreateDate,RANK() OVER(ORDER BY Score DESC) AS '序號(hào)',StudentId,ClassId,CourseId,Score FROMStudentScoreWHERE CourseId=8 結(jié)果如下: 注意,它和ROW_NUMBER()的異同點(diǎn),您應(yīng)該已經(jīng)知道了: a、RANK函數(shù)和ROW_NUMBER函數(shù)類似,它們都是用來對(duì)結(jié)果進(jìn)行排序。
b、不同的是,ROW_NUMBER函數(shù)為每一個(gè)值生成唯一的序號(hào),而RANK函數(shù)為相同的值生成相同的序號(hào)。
上圖中,兩個(gè)86分的學(xué)生對(duì)應(yīng)的序號(hào)都是3,而接著排在它們下面的序號(hào)直接變成了5。 (2)、DENSE_RANK()函數(shù) 返回結(jié)果集分區(qū)中行的排名,在排名中沒有任何間斷。行的排名等于所討論行之前的所有排名數(shù)加一。如果有兩個(gè)或多個(gè)行受同一個(gè)分區(qū)中排名的約束,則每個(gè)約束行將接收相同的排名。 SELECTId, -- CreateDate,DENSE_RANK() OVER(ORDER BY Score DESC) AS '序號(hào)',StudentId,ClassId,CourseId,Score FROMStudentScoreWHERE CourseId=8 查詢結(jié)果如下: 上圖中,兩個(gè)86分的學(xué)生對(duì)應(yīng)的序號(hào)都是3,而接著排在它們下面的序號(hào)是4(也就是說DENSE_RANK()函數(shù)查詢的序號(hào)是類似ROW_NUMBER()那樣連續(xù)的,但是對(duì)于相同值的行生成相同的序號(hào),從這一點(diǎn)上來說,對(duì)于相同查詢條件和排序的查詢,ROW_NUMBER()函數(shù)查詢的結(jié)果集是DENSE_RANK()函數(shù)查詢的結(jié)果的子集)。這也是我們可以總結(jié)出的RANK和DENSE_RANK()這兩個(gè)函數(shù)的最大的不同點(diǎn)。 3、NTILE() NTILE函數(shù)把結(jié)果中的行關(guān)聯(lián)到組,并為每一行分配一個(gè)所屬的組的編號(hào),編號(hào)從一開始。對(duì)于每一個(gè)行,NTILE 將返回此行所屬的組的編號(hào)。
如果分區(qū)的行數(shù)不能被 integer_expression 整除,則將導(dǎo)致一個(gè)成員有兩種大小不同的組。按照 OVER 子句指定的順序,較大的組排在較小的組前面。 SELECTId, -- CreateDate,NTILE(6) OVER(ORDER BY ClassId DESC) AS '組編號(hào)',StudentId,ClassId,CourseId,Score FROMStudentScoreWHERE CourseId=8 查詢的結(jié)果如下: 本文的介紹和示例都很基礎(chǔ),但是通過窗口函數(shù),確實(shí)可以幫我們優(yōu)化很多復(fù)雜查詢。上面的SQL語(yǔ)句看上去每一個(gè)都很簡(jiǎn)單,但是現(xiàn)在的簡(jiǎn)單都隱藏著背后的復(fù)雜。需要提醒的是,分組概念雖然基礎(chǔ)卻很重要,你必須掌握;而熟練應(yīng)用了窗口函數(shù),你的SQL查詢就如虎添翼更上層樓了。 最后,我一直擔(dān)心對(duì)于海量數(shù)據(jù),SQL Server的性能問題。因?yàn)榻诘拈_發(fā)碰巧遇到海量數(shù)據(jù)的查詢,最多的過億,數(shù)據(jù)量最少的一個(gè)表,也過5000萬,不知道用了分區(qū)表性能有沒有明顯提升。
參考文章: http://msdn.microsoft.com/zh-cn/library/ms189461.aspx http://www.cnblogs.com/aierong/archive/2008/08/26/1273890.html http://www.cnblogs.com/aierong/archive/2008/08/18/1269407.html
總結(jié)
以上是生活随笔為你收集整理的总结SQL Server窗口函数的简单使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DataTable 转 Entity
- 下一篇: 最后关于nginx+passenger+