表結構與數據:https://github.com/XuePeng87/TSQLV4git
表的表達式(Table Expression)是一個命名的查詢表達式,MSSQL支持4種類型的表表達式:派生表、公用表表達式(CTE)、視圖和內嵌表值函數(TVF)。github
SELECT * FROM (SELECT custid, companyname FROM Sales.Customers WHERE country = N'USA') AS USACusts;
上面的例子是一個基本語法的簡單示例。因爲外部查詢沒有應用任何操做,派生表其實是不須要的。sql
有效定義任何類型表表達式的查詢必須知足3個要求:數據庫
使用表表達式的好處之一就是,在外部查詢的任何子句中,能夠引用內部查詢的SELECT子句中分配的列別名。例如,假設須要對Sales.Orders表編寫一個查詢,返回每一個訂單年度處理的非重複客戶數量。因爲GROUP BY子句引用了在SELECT子句中分配的列別名,而且GROUP BY子句是在SELECT子句以前處理的,因此下面的嘗試無效:編程
SELECT YEAR(orderdate) AS orderdate, COUNT(DISTINCT custid) AS numcusts FROM Sales.Orders GROUP BY orderyear; 消息 207,級別 16,狀態 1,第 5 行 列名 'orderyear' 無效。
這時,可使用內嵌別名形式的派生表查詢:安全
SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ) AS D GROUP BY orderyear;
通常來講,表表達式沒有正面或負面的性能影響。架構
DECLARE @empid AS INT = 3; SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders WHERE empid = @empid ) AS D GROUP BY orderyear;
若是定義派生表查詢須要引用另外一個派生表,這就是一個嵌套派生表。嵌套是經常使用的編程問題,因爲它的代碼複雜,下降了代碼的可讀性。函數
SELECT orderyear, numcusts FROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ) AS D1 GROUP BY orderyear ) AS D2 WHERE numcusts > 70
能夠定義基於相同查詢的多個派生表,但可讀性不高:post
SELECT Cur.orderyear, Cur.numcusts AS curnumcusts, Pre.numcusts AS prenumcusts, Cur.numcusts - Pre.numcusts AS growth FROM (SELECT YEAR(orderdate) AS orderyear, COUNT(DISTINCT custid) AS numcusts FROM Sales.Orders GROUP BY YEAR(orderdate)) AS Cur LEFT OUTER JOIN (SELECT YEAR(orderdate) AS orderyear, COUNT(DISTINCT custid) AS numcusts FROM Sales.Orders GROUP BY YEAR(orderdate)) AS Pre ON Cur.orderyear = Pre.orderyear + 1
公用表表達式(CTE)是表表達式的另外一種標準形式,與派生表很是相似。定義CTE的內部查詢必須遵循前面提到的定義表表達式的全部要求,例如:性能
WITH USACusts AS ( SELECT custid, companyname FROM Sales.Customers WHERE country = N'USA' ) SELECT * FROM USACusts;
CTE也支持兩種列別名的命名方式——內嵌方式和外部方式。
下面是內嵌方式的實例:
WITH C AS ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ) SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM C GROUP BY orderyear;
下面是外部方式的實例:
WITH C(orderyear, custid) AS ( SELECT YEAR(orderdate), custid FROM Sales.Orders ) SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM C GROUP BY orderyear;
DECLARE @empid AS INT = 3; WITH C(orderyear, custid) AS ( SELECT YEAR(orderdate), custid FROM Sales.Orders WHERE empid = @empid ) SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM C GROUP BY orderyear;
WITH C1(orderyear, custid) AS ( SELECT YEAR(orderdate), custid FROM Sales.Orders ), C2(orderyear, numcusts) AS ( SELECT orderyear, COUNT(DISTINCT custid) FROM C1 GROUP BY orderyear ) SELECT orderyear, numcusts FROM C2 WHERE numcusts > 70;
從技術上來看,不能嵌套CTE,也不能在派生表的括號內定義CTE,可是嵌套是一種較爲複雜的作法,所以,能夠說CTE的這種限制有助於代碼的清晰性。
WITH YearlyCount AS ( SELECT YEAR(orderdate) AS orderyear, COUNT(DISTINCT custid) AS numcusts FROM Sales.Orders GROUP BY YEAR(orderdate) ) SELECT Cur.orderyear, Cur.numcusts AS curmuncusts, Pre.numcusts AS prvnumcusts, Cur.numcusts - Pre.numcusts AS growth FROM YearlyCount AS Cur LEFT OUTER JOIN YearlyCount AS Pre ON Cur.orderyear = Pre.orderyear + 1
這段代碼比以前引用多個派生表的代碼清晰了很多,只定義了一次YearlyCount。
CTE具備遞歸能力,在表表達式之間CTE應是惟一的。遞歸CTE至少由兩個查詢定義,至少一個查詢做爲定位點成員,一個查詢做爲遞歸成員。舉個例子,使用遞歸CTE返回某個僱員和其各級下屬僱員:
WITH EmpsCTE AS ( SELECT empid, mgrid, firstname, lastname FROM HR.Employees WHERE empid = 2 UNION ALL SELECT C.empid, C.mgrid, C.firstname, C.lastname FROM EmpsCTE AS P JOIN HR.Employees AS C ON C.mgrid = P.empid ) SELECT empid, mgrid, firstname, lastname FROM EmpsCTE;
注意,放置無限遞歸的安全措施是,MSSQL默認狀況下限制遞歸成員能夠被調用的次數爲100,遞歸成員的第101次將會失敗,能夠在外部查詢的尾部指定OPTION(MAXRECURSION n)提示來更改默認的最大遞歸限制,n是一個0~32767範圍的證書,若是但願徹底取消限制,可讓n=0。若是取消了限制,工做表將會很快變得很大。若是tempdb不能增加了,例如,磁盤空間用完了,查詢將失敗。
派生表和CTE具備很是有限的範圍,只要查詢完成了,它們就消失了。
視圖和內嵌表值函數(內嵌TVF)是兩種可重複使用的表表達式類型,它們被存儲爲數據庫對象。建立以後這些對象是數據庫的永久部分,只有顯示刪除他們才從數據庫中移除。
IF OBJECT_ID('Sale.USACusts') IS NOT NULL DROP VIEW Sales.USACusts; GO CREATE VIEW Sales.USACusts AS SELECT custid, companyname, contactname, contacttitle, address,city, region, postalcode, country, phone, fax FROM Sales.Customers WHERE country = N'USA' GO
用於定義視圖的查詢必須知足在以前提到的派生表中的全部要求,視圖沒法保證行的順序,相反,你應該在對視圖的外部查詢中指定一個展現用的ORDER BY子句:
SELECT custid, companyname, region FROM Sales.USACusts ORDER BY region;
在建立或更改視圖、存儲過程、觸發器和用戶定義函數時,ENCRYPTION選項是可用的。它指示MSSQL在內部以代碼混淆方式存儲對象定義文本。代碼混淆文本對經過任何目錄對象的用戶不直接可見,僅對經過特定方法的特權用戶可見。例如未加ENCRYPTION的視圖能夠經過該SQL語句查詢到:
SELECT OBJECT_DEFINITION(OBJECT_ID('Sales.USACusts'));
下面修改一下Sales.USACusts視圖:
ALTER VIEW Sales.USACusts WITH ENCRYPTION AS SELECT custid, companyname, contactname, contacttitle, address,city, region, postalcode, country, phone, fax FROM Sales.Customers WHERE country = N'USA' GO
在使用OBJECT_DEFINITION就查詢不到該視圖了。
該選項對視圖和UDF可用,它將被引用對象的架構和列綁定到引用對象的架構中。指示不能刪除被引用對象,也不能刪除或修改被引用的列。
例如,把Sales.USACusts加入SCHEMABINDING選項:
ALTER VIEW Sales.USACusts WITH SCHEMABINDING AS SELECT custid, companyname, contactname, contacttitle, address,city, region, postalcode, country, phone, fax FROM Sales.Customers WHERE country = N'USA' GO
如今嘗試刪除Customers表中address列將會出現錯誤提示:
ALTER TABLE Sales.Customers DROP COLUMN address; 消息 5074,級別 16,狀態 1,第 1 行 對象'USACusts' 依賴於 列'address'。 消息 4922,級別 16,狀態 9,第 1 行 因爲一個或多個對象訪問此列,ALTER TABLE DROP COLUMN address 失敗。
使用SCHEMABINDING,要求查詢語句中不可以使用*,並且必須顯示地列出列名。此外,在引用對象時,必須使用架構限定的兩部分名稱。
CHECK OPTION的目的是防止出現視圖修改與視圖篩選的衝突,假設已存在一個視圖查詢定義,查詢USACusts,用於篩選國家爲USA的客戶,視圖當前沒有定義CHECK OPTION。這意味着你能夠經過視圖INSERT來自除美國之外的國家的客戶,以及能夠經過視圖更改現有客戶的國家爲美國之外的國家:
INSERT INTO Sales.USACusts(companyname, contactname, contacttitle, address,city, region, postalcode, country, phone, fax) VALUES (N'Customer ABCED', N'Contact ABCDE', N'Title ABCDE', N'Address ABCED', N'London', NULL, N'12345', N'UK', N'012-23456789', N'012-3456789') (1 行受影響)
經過修改視圖,在尾端加入WITH CHECK OPTION來作限制後,在執行INSERT就會提示錯誤了:
ALTER VIEW Sales.USACusts WITH SCHEMABINDING AS SELECT custid, companyname, contactname, contacttitle, address,city, region, postalcode, country, phone, fax FROM Sales.Customers WHERE country = N'USA' WITH CHECK OPTION GO
INSERT INTO Sales.USACusts(companyname, contactname, contacttitle, address,city, region, postalcode, country, phone, fax) VALUES (N'Customer ABCED', N'Contact ABCDE', N'Title ABCDE', N'Address ABCED', N'London', NULL, N'12345', N'UK', N'012-23456789', N'012-3456789') 消息 550,級別 16,狀態 1,第 1 行 試圖進行的插入或更新已失敗,緣由是目標視圖或者目標視圖所跨越的某一視圖指定了 WITH CHECK OPTION,而該操做的一個或多個結果行又不符合 CHECK OPTION 約束。 語句已終止。
內嵌TVF是支持輸入參數的可重複使用的表表達式,也能夠看做是能夠傳參查詢的視圖:
IF OBJECT_ID('') IS NOT NULL DROP FUNCTION dbo.GetCustOrders; GO CREATE FUNCTION dbo.GetCustOrders (@cid AS INT) RETURNS TABLE AS RETURN SELECT orderid, custid, empid, orderdate, requireddate, shippeddate, shipperid, freight, shipname, shipaddress, shipcity, shipregion, shippostalcode, shipcountry FROM Sales.Orders WHERE custid = @cid; GO
使用TVF查詢:
SELECT orderid, custid FROM dbo.GetCustOrders(1) AS O;
APPLY運算符是一個很是強大的表運算符。APPLY運算符支持的兩個類型是CROSS APPLY和OUTER APPLY。
CROSS APPLY僅實施一個邏輯查詢處理階段,而OUTER APPLY實施了兩個階段。
APPLY不是標準的,相對應的標準叫作LATERAL,可是此標準未在MSSQL中實現。
APPLY運算符對兩個輸入表進行操做,第二個表能夠是一個表表達式,右側的表能夠對來自左側表的每一行表示一個不一樣的行集,例如,下面代碼返回每一個客戶的3個最近訂單:
SELECT C.custid, A.orderid, A.orderdate FROM Sales.Customers AS C CROSS APPLY ( SELECT TOP(3) orderid, empid, orderdate, requireddate FROM Sales.Orders AS O WHERE O.custid = C.custid ORDER BY orderdate DESC, orderid DESC ) AS A;
OUTER APPLY運算符表示右側表爲空的行:
SELECT C.custid, A.orderid, A.orderdate FROM Sales.Customers AS C OUTER APPLY ( SELECT TOP(3) orderid, empid, orderdate, requireddate FROM Sales.Orders AS O WHERE O.custid = C.custid ORDER BY orderdate DESC, orderid DESC ) AS A;