在編寫存儲過程的時候,大多數狀況都會使用公用表表達式,本文對公用表表達式(CTE)做簡單的介紹html
①準備工做
建立一個產品表T_Productsql
CREATE TABLE [dbo].[T_Product] ( [Id] [bigint] IDENTITY(1,1) NOT NULL,--自增字段Id [Category] [nvarchar](50) NOT NULL,--產品種類 [Name] [nvarchar](50) NOT NULL,--產品名 [Price] [decimal](18, 0) NOT NULL,--產品價格 )
隨便插入一些數據便可數據庫
②子表達式服務器
瞭解公用表表達式以前咱們要回顧一下SQL語句中的子查詢性能
以前在咱們在select語句中可使用子查詢,學習
例如:測試
select * from (select * from T_Product where Category='clothes' )T where T.Price>500`
這裏就是使用了一個子查詢:查詢出T_Product表中的全部的種類爲clothes的產品,子查詢的結果表定義別名爲T,咱們在T表中繼續查詢全部Price大於500的產品.net
③表變量code
子查詢雖然不復雜,可是使用多層的子查詢嵌套,則SQL語句會變得難以閱讀,server
固然,咱們可使用表變量,即:定義一個表變量,而後將子查詢的結果集存入表變量中,以供後續的使用。
例如:
--聲明一個表變量 declare @T table ( Id bigint not null , Category nvarchar(50) not null, Name nvarchar(50) not nul , Prince decimal not null ) --將子查詢的數據插入表變量中 insert into @T select * from T_Product where Category='clothes' --使用表變量 select * from @T where Price>500
④公用表表達式
將子查詢放在了表變量@T
中,這樣作將使SQL語句更容易維護,但又會帶來另外一個問題,就是性能的損失。
因爲表變量實際上使用了臨時表,從而增長了額外的I/O開銷,所以,表變量的方式並不太適合數據量大且頻繁查詢的狀況。
爲此,在SQL Server 2005中提供了另一種解決方案,這就是公用表表達式(CTE),使用CTE,可使SQL語句的可維護性,同時,CTE要比表變量的效率高得多。
這裏就先演示一下使用CTE,具體的語法細節後面作說明
--聲明一個CTE with T as ( select * from T_Product where Category='clothes' ) --使用CTE select * from T where Price >500
這裏咱們就是使用公用表表達式替換這裏的子查詢的結果集。
因此使用使用with&as定義的公用表表達式也稱爲子查詢部分(subquery factoring)
同時,公用表表達式在存儲過程當中能夠用於代替視圖的使用,在某個存儲過程當中須要使用是個結果集,咱們不必爲其建立一個視圖,可使用公用表表達式。
定義:公用表表達式(Common Table Expression) 是SQL Server2005版本的引入的一個特性。CTE能夠看組是一個臨時的結果集,能夠再接下來來的一個select
,insert
,update
,delete
,merge
語句中屢次引用。使用公用表達式CTE可讓語句更加清晰簡練。
簡而言之:公用表表達式就是用於臨時存儲結果集。
建立一個公用表表達式,並使用改公用表表達式的基本語法:
with subquery_name(column1,column2,column3……)--定義一個CTE as ( select column1,column2,column3 from table_name ) select * from subquery_name-- 引用CTE
注意事項:
公用表表達式只能使用在其定義後的第一個sql語句中,不然會報錯
定義CTE的時候,能夠省略列名,即最終的該CTE的列名就是該CTE中select查詢的結果,可是我如今以爲儘可能不要省略,能夠方便後續閱讀。
正確示例:
with temp as ( select * from T_Product where Category='外套' ) select * from temp as temp1 left join temp as temp2 on temp1.Id =temp2.Id
上面是聲明瞭一個公用表表達式temp,在其以後的第一句sql語句中使用到temp,徹底沒有問題。
錯誤示例:
with temp as ( select * from T_Product where Category='外套' ) select * from temp --公用表表達式temp以後的第一句sql語句 select * from temp as temp1 left join temp as temp2 on temp1.Id =temp2.Id--公用表表達式temp以後的第二句sql語句
結果是報錯:顯示第一句sql語句已執行,可是第二句sql語句報錯「對象名'temp'無效」
正是由於只有CTE以後的第一句sql語句可使用該CTE,因此若是CTE的表達式名稱與某個數據表或視圖重名,則緊跟在該CTE後面的SQL語句使用的仍然是CTE
CTE語法細節:
若是將 CTE 用在屬於批處理的一部分的語句中,那麼在CTE以前的語句必須以分號結尾。
【補充】:批處理是指從應用程序一次性地發送一組完整sql語句到sql server上執行,批處理的全部語句被當作一個總體,被成批地分析,編譯和執行,全部的批處理 指令以GO
做爲結束標誌。同時寫多個批處理,若是前面全部的批處理沒有問題,最後一個有錯誤那麼前面全部的批處理都不會執行
CTE中的sql語句是不能使用order by
語句,除非select語句中有top
(其實這裏我也沒有想明白,理論上子查詢使用order by
徹底是沒有問題的)
前面的with子句定義的查詢在後面的with子句中可使用。可是一個with子句內部不能嵌套with子句
這裏就要思考一個問題了,那就是如果在語句sql語句中須要多個公用表表達式,咱們能夠連續的聲明多個公用表表達式,可是注意只須要在第一個公用表表達式上使用with
,以後相連的則不需在使用with
with temp1 as ( select * from T_Product where Category='外套' ) ,temp2 as--注意這裏不在須要使用with ,可是不要忘記as ( select * from T_Product where Category='褲子' ) select * from temp1 ,temp2 where temp1.Price=temp2.Price
一次聲明的多個公用表表達式,後面的公用表表達式可使用前面的公用表表達式
例如:
with temp1 as ( select * from T_Product where Category='外套' ) ,temp2 as ( select * from temp1 where Price>200--在相連的第二個CTE中使用第一個CTE ) select * from temp2
遞歸查詢主要用於層次結構的查詢,從葉級(Leaf Level)向頂層(Root Level)查詢,或從頂層向葉級查詢,或遞歸的路徑(Path)。
遞歸 CTE 定義至少必須包含兩個 CTE 查詢定義,一個定位點成員和一個遞歸成員。能夠定義多個定位點成員和遞歸成員;但必須將全部定位點成員查詢定義置於第一個遞歸成員定義以前。
第一個子查詢稱做定點(Anchor)子查詢:定點查詢只是一個返回有效表的查詢,用於設置遞歸的初始值;
第二個子查詢稱做遞歸子查詢:該子查詢調用CTE名稱,觸發遞歸查詢,其實是遞歸子查詢調用遞歸子查詢;
兩個子查詢使用union all,求並集;
建立一個公司表,公司中的部門是分等級的,PId即該部門的上一級部門
CREATE TABLE [dbo].[Company] ( [Id] [bigint] IDENTITY(1,1) NOT NULL, [PId] [bigint] NOT NULL, [Name] [nvarchar](50) NOT NULL, )
插入數據,咱們使用行政等級模擬上下級部門:
Id PId Name --------- --------- ---------- 1 0 中國 2 1 江蘇省 3 2 蘇州市 4 3 吳中區 5 1 山東省 6 5 濟南市 7 5 青島市 8 5 煙臺市 9 2 南京市 11 9 玄武區
USE [ShanTest] GO with temp as ( select *,0 as Level from Company where Pid =0 union all select c.Id,c.Pid,c.Name,temp.Level+1 as Level from Company as c,temp where temp.Id=c.Pid ) select * from temp
運行測試結果:
Id PId Name Level --------- --------- ------------ ------------ 1 0 中國 0 2 1 江蘇省 1 5 1 山東省 1 6 5 濟南市 2 7 5 青島市 2 8 5 煙臺市 2 3 2 蘇州市 2 9 2 南京市 2 11 9 玄武區 3 4 3 吳中區 3
簡單的理一理這裏的遞歸:
首先:
select *,0 as Level from Company where Pid =0
結果是:
Id PId Name Level --------- --------- ------------ ------------ 1 0 中國 0
接着:
union all select c.Id,c.Pid,c.Name,temp.Level+1 as Level from Company as c,temp where c.Pid=temp .Id
第一次遞歸結果:
Id PId Name Level --------- --------- ------------ ------------ 1 0 中國 0 2 1 江蘇省 1 5 1 山東省 1
第二次遞歸結果:
Id PId Name Level --------- --------- ------------ ------------ 1 0 中國 0 2 1 江蘇省 1 5 1 山東省 1 6 5 濟南市 2 7 5 青島市 2 8 5 煙臺市 2 3 2 蘇州市 2 9 2 南京市 2
第三次遞歸結果:
Id PId Name Level --------- --------- ------------ ------------ 1 0 中國 0 2 1 江蘇省 1 5 1 山東省 1 6 5 濟南市 2 7 5 青島市 2 8 5 煙臺市 2 3 2 蘇州市 2 9 2 南京市 2 11 9 玄武區 3 4 3 吳中區 3
【注意】:
遞歸CTE可能會出現無限遞歸。從而大量消耗SQL Server的服務器資源.所以,SQL Server提供了OPTION選項,能夠設定最大的遞歸次數。
這個最大遞歸次數每每是根據數據所表明的具體業務相關的,好比這裏,咱們定義最大遞歸數是2:option(maxrecursion 2)
:
遞歸查詢沒有顯式的遞歸終止條件,只有當遞歸子查詢返回空結果集(沒有數據行返回)或是超出了遞歸次數的最大限制時,才中止遞歸。
USE [ShanTest] GO with temp as ( select *,0 as Level from Company where Pid =0 union all select c.Id,c.Pid,c.Name,temp.Level+1 as Level from Company as c,temp where c.Pid=temp .Id ) select * from temp option(maxrecursion 2)--設置最大遞歸數是2
則結果現實以下,即最大遞歸兩次則只能查詢到市一級,沒法查詢到區一級
Id PId Name Level --------- --------- ------------ ------------ 1 0 中國 0 2 1 江蘇省 1 5 1 山東省 1 6 5 濟南市 2 7 5 青島市 2 8 5 煙臺市 2 3 2 蘇州市 2 9 2 南京市 2 消息 530,級別 16,狀態 1,第 6 行 語句被終止。完成執行語句前已用完最大遞歸 2。
做爲層級結構,可使用自鏈接查詢每一個部門的上級部門:
--隱式內鏈接 select a.Id ,a.Pid ,a.Name ,b.Name as PName from Company a ,Company b where a.Pid=b.Id --顯式內鏈接: select a.Id ,a.Pid ,a.Name ,b.Name as PName from Company a inner join Company b on a.Pid =b.Id
查詢結果:
Id Pid Name PName -------- -------- --------- ---------- 2 1 江蘇省 中國 3 2 蘇州市 江蘇省 4 3 吳中區 蘇州市 5 1 山東省 中國 6 5 濟南市 山東省 7 5 青島市 山東省 8 5 煙臺市 山東省 9 2 南京市 江蘇省 11 9 玄武區 南京市
下面演示使用遞歸CTE實現,全部的子級匹配全部的父級
with subq as ( select Id ,Pid ,Name ,Name as PName from Company where Pid =0 union all select c.Id ,c.Pid,c.Name ,s.Name as PName from subq as s inner join Company as c on s.Id =c.Pid --from subq as s,Company as c where s.Id=c.Pid ) select * from subq
Id Pid Name PName -------- -------- ---------- --------- 1 0 中國 中國 2 1 江蘇省 中國 5 1 山東省 中國 6 5 濟南市 山東省 7 5 青島市 山東省 8 5 煙臺市 山東省 3 2 蘇州市 江蘇省 9 2 南京市 江蘇省 11 9 玄武區 南京市 4 3 吳中區 蘇州市
理解遞歸的方式就是,從頭理一理:
首先:
select Id ,Pid ,Name ,Name as PName from Company where Pid =0
結果是:
Id Pid Name PName -------- -------- ---------- --------- 1 0 中國 中國
接着
select c.Id ,c.Pid,c.Name ,s.Name as PName from subq as s inner join Company as c on s.Id =c.Pid
第一次遞歸結果:
Id Pid Name PName -------- -------- ---------- --------- 1 0 中國 中國 2 1 江蘇省 中國 5 1 山東省 中國
第二次遞歸:
Id Pid Name PName -------- -------- ---------- --------- 1 0 中國 中國 2 1 江蘇省 中國 5 1 山東省 中國 6 5 濟南市 山東省 7 5 青島市 山東省 8 5 煙臺市 山東省 3 2 蘇州市 江蘇省 9 2 南京市 江蘇省
第三次遞歸
Id Pid Name PName -------- -------- ---------- --------- 1 0 中國 中國 2 1 江蘇省 中國 5 1 山東省 中國 6 5 濟南市 山東省 7 5 青島市 山東省 8 5 煙臺市 山東省 3 2 蘇州市 江蘇省 9 2 南京市 江蘇省 11 9 玄武區 南京市 4 3 吳中區 蘇州市
好比說,這裏查詢表中全部江蘇省如下的行政區域
with temp as ( select * from Company where Id=2--江蘇省的Id是2,因此遞歸初始值就是2 union all select c.* from temp ,szmCompany as c where temp.Id =c.Pid ) select * from temp --option(maxrecursion 1)
查詢結果:
Id Pid Name ------ ------ ---------- 2 1 江蘇省 3 2 蘇州市 9 2 南京市 10 9 玄武區 4 3 吳中區
其實這裏,如果咱們只須要江蘇省的下一級(即:市級),而不須要下下級(即:區縣級)
則能夠設置遞歸的次數爲1便可:option(maxrecursion 1)
結果爲:
Id Pid Name ------ ------ ---------- 2 1 江蘇省 3 2 蘇州市 9 2 南京市 消息 530,級別 16,狀態 1,第 1 行 語句被終止。完成執行語句前已用完最大遞歸 1。
經過子部門查詢其父部門,好比查詢吳中區的上級行政區域
with temp as ( select * from Company where Id=4--吳中區Id union all select c.* from temp ,Company as c where temp.Pid =c.Id ) select * from temp --option(maxrecursion 1)
查詢結果:
Id Pid Name ----- ----- -------- 4 3 吳中區 3 2 蘇州市 2 1 江蘇省 1 0 中國
如果只須要查詢吳中區的直系上級行政區域,則只要限制最大遞歸次數爲1便可
固然,如果只須要查直系上級,咱們可使用以前的上下級匹配的結果集,篩選特定的記錄:
select * from ( select a.* ,b.Name as PName from Company as a ,Company as b where a.Pid=b.Id--全部的上下級匹配結果集 )X where X.Id =4--吳中區Id
查詢結果:
Id Pid Name PName ------ ------- --------- ---------- 4 3 吳中區 蘇州市
【待讀】:
想要找一本關於存儲過程的書籍,一直沒有找到,因此都是在網上的一些博文中學習相關的技巧和語法細節
感受不繫統,隱隱約約感受本身關於T-SQL以及存儲過程的使用還有許多不瞭解的地方!