T-SQL基礎(四)之集合運算
T-SQL支援三個集合運算子:UNION、INTERSECT、EXCEPT。
集合運算子查詢的一般形式如下:
Query1 <set_operator> Query2 -- 這裡,ORDER BY子句對最終結果集進行排序 [ORDER BY...]
ORDER BY
在邏輯查詢處理方面,集合運算子應用於兩個查詢結果集,且外部的ORDER BY
子句(如果有的話)應用於集合運算所得到的結果集。
-
ORDER BY
會對查詢結果集進行排序 -
排序後的結果集不在表示一個集合而是遊標
-
集合運算子只能用於集合間運算
因此,每個獨立的查詢語句中不能使用ORDER BY
子句。
其它查詢邏輯
對集合運算結果集使用除ORDER BY
之外的查詢邏輯則易引發邏輯錯誤:
USE WJChi; SELECT Name AS 姓名,Age FROM dbo.UserInfo AS U1 UNION ALL SELECT Name,Age AS 年齡 FROM dbo.UserInfo AS U2 WHERE Age>26; -- 上述寫法等價於(注意WHERE條件) SELECT Name AS 姓名,Age FROM dbo.UserInfo ASU1 UNION ALL SELECT Name,Age AS 年齡 FROM dbo.UserInfo AS U2 WHERE U2.Age>26;
可以藉助表表達式對集合運算子運算結果集使用ORDER BY
之外的查詢邏輯:
USE WJChi; SELECT * FROM ( SELECT Name AS 姓名,Age FROM dbo.UserInfo AS U1 UNION ALL SELECT Name,Age AS 年齡 FROM dbo.UserInfo AS U2 ) AS T WHERE T.Age>26;
上述查詢也可使用派生表之外的表表達式,如:CTE。
集合的列
用於集合運算子的兩個查詢必須返回相同列數且對應列資料型別相互相容的結果集。在進行比較運算時,集合運算子會認為兩個NULL值是相等的。
集合運算子返回結果集中的列名是第一個查詢中的列名:
USE WJChi; SELECT Name AS 姓名,Age FROM dbo.UserInfo AS U1 UNION SELECT Name,Age AS 年齡 FROM dbo.UserInfo AS U2 ORDER BY Age
返回結果如下:
UNION
UNION
用於獲取兩個集合的並集。
UNION
運算子有兩種形式:UNION
、UNION ALL
:
UNION
USE WJChi; SELECT Name AS 姓名,Age FROM dbo.UserInfo AS U1 UNION SELECT Name,Age AS 年齡 FROM dbo.UserInfo AS U2 ORDER BY Age
返回結果如下:
UNION ALL
USE WJChi; SELECT Name AS 姓名,Age FROM dbo.UserInfo AS U1 UNION ALL SELECT Name,Age AS 年齡 FROM dbo.UserInfo AS U2 ORDER BY Age
返回結果如下:
從上面兩個結果集中可以看到,UNION
與UNION ALL
的區別是:UNION
會去除結果集中的重複元素,而UNION ALL
不會,從效能上來講,UNION ALL
優於UNION
。嚴格來講,UNION ALL
運算結果集不能稱為集合,因為集合不存在重複元素。
INTERSECT
INTERSECT
用於獲取兩個集合的交集,分為:INTERSECT
和INTERSECT ALL
兩種形式,二者區別同UNION
運算子。
INTERSECT
可以使用內聯接或者EXSITS
謂詞來替代INTERSECT
,但在比較運算時,INTERSECT
將兩個NULL值視為相等,而替代方案不會。
INTERSECT
只關注行的內容是否相同,不關注行出現的次數:
USE WJChi; SELECT Name AS 姓名,Age FROM dbo.UserInfo AS U1 INTERSECT SELECT Name,Age AS 年齡 FROM dbo.UserInfo AS U2 ORDER BY Age;
INTERSECT ALL
SQL標準中包含INTERSECT ALL
,但在SQL Server2014中未實現該特性,在SQL Server2014中使用INTERSECT ALL
會報錯:
不支援 INTERSECT 運算子的 'ALL' 版本。
UNION ALL
中ALL
的含義是返回所有重複行。與之類似,INTERSECT ALL
中ALL
的含義是不刪除交集中的重複項。換個角度看,INTERSECT ALL
不僅關心兩側存在的行,還關心每一側行出現的次數,即:
如果某一資料在第一個輸入中出現了a次,在第二個輸入中出現了b次,那麼在運算結果中該行出現min(a,b)次。
下面,我們藉助開窗函式ROW_NUMBER()
實現了INTERSECT ALL
的效果:
USE WJChi; SELECT ROW_NUMBER() OVER(PARTITION BY Name,Age ORDER BY Age) AS RowNumber, Name,Age FROM dbo.UserInfo;
經過開窗函式ROW_NUMBER()
的處理後,原本相同的資料被視為不同。
USE WJChi; -- 實現INTERSECT ALL效果 SELECT T.Name,T.Age FROM ( SELECT ROW_NUMBER() OVER(PARTITION BY Name,Age ORDER BY Age) AS RowNumber, Name,Age FROM dbo.UserInfo INTERSECT SELECT ROW_NUMBER() OVER(PARTITION BY Name,Age ORDER BY Age) AS RowNumber, Name,Age FROM dbo.UserInfo ) AS T ORDER BY T.Age;
查詢結果如下:
EXCEPT
EXCEPT
用於獲取兩個集合的差集,與UNION
與INTERSECT
類似,EXCEPT
也分為兩種形式:EXCEPT
和EXCEPT ALL
。同樣,SQL Server2014也不支援EXCEPT ALL
特性。
Query1 EXCEPT Query2
EXCEPT
與UNION
、INTERSECT
不同,EXCEPT
運算子對於兩個查詢的先後順序有要求:EXCEPT
返回存在於Query1中出現且不在Query2中出現的行,EXCEPT
只關注行是否重複,而不關注行出現的次數。
可以使用外聯接或者NOT EXISTS
來替代EXCEPT
,但在比較運算時,EXCEPT
將兩個NULL值視為相等,而替代方案不會。
準備如下資料:
USE WJChi; SELECT Name,Age FROM #temp; SELECT Name,Age FROM dbo.UserInfo;
那麼,下面兩條SQL的運算結果集均不包含任何資料:
SELECT Name ,Age FROM #temp EXCEPT SELECT Name,Age FROM dbo.UserInfo ORDER BY Age; SELECT Name ,Age FROM dbo.UserInfo EXCEPT SELECT Name,Age FROM #temp ORDER BY Age;
EXCEPT ALL
EXCEPT ALL
與EXCEPT
的差異在於,EXCEPT ALL
不止考慮行是否重複,還會考慮行出現的次數:
如果某一資料在第一個輸入中出現了a次,在第二個輸入中出現了b次,那麼在運算結果中該行出現a-b次。若a<b則運算結果中不包含該行。
同樣,我們藉助開窗函式ROW_NUMBER()
來實現EXCEPT ALL
效果:
USE WJChi; SELECT T.Name,T.Age FROM ( SELECT ROW_NUMBER() OVER(PARTITION BY Name,Age ORDER BY Age) AS RowNumber, Name,Age FROM #temp EXCEPT SELECT ROW_NUMBER() OVER(PARTITION BY Name,Age ORDER BY Age) AS RowNumber, Name,Age FROM dbo.UserInfo ) AS T ORDER BY T.Age;
小結
標準SQL支援三個集合運算子:UNION
、INTERSECT
、EXCEPT
,每個運算子均支援兩種行為:去重(不帶ALL關鍵字)和保留重複項(帶上ALL關鍵字)。
T-SQL未提供對INTERSECT ALL
與EXCEPT ALL
的支援,我們可以通過開窗函式ROW_NUMBER()
來實現。
另外需要注意一點,集合運算子認為兩個NULL
是相等的。
推薦閱讀