1. 程式人生 > >T-SQL基礎(四)之集合運算

T-SQL基礎(四)之集合運算

三個運算子

T-SQL支援三個集合運算子:UNION、INTERSECT、EXCEPT。

集合運算子查詢的一般形式如下:

Query1
<set_operator>
Query2
-- 這裡,ORDER BY子句對最終結果集進行排序
[ORDER BY...]

ORDER BY

在邏輯查詢處理方面,集合運算子應用於兩個查詢結果集,且外部的ORDER BY子句(如果有的話)應用於集合運算所得到的結果集

每個獨立的查詢可以使用除了ORDER BY之外的所有邏輯查詢處理階段,原因如下:

  1. ORDER BY會對查詢結果集進行排序

  2. 排序後的結果集不在表示一個集合而是遊標

  3. 集合運算子只能用於集合間運算

因此,每個獨立的查詢語句中不能使用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 AS
U1 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運算子有兩種形式:UNIONUNION 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

返回結果如下:

從上面兩個結果集中可以看到,UNIONUNION ALL的區別是:UNION會去除結果集中的重複元素,而UNION ALL不會,從效能上來講,UNION ALL優於UNION。嚴格來講,UNION ALL運算結果集不能稱為集合,因為集合不存在重複元素。

INTERSECT

INTERSECT用於獲取兩個集合的交集,分為:INTERSECTINTERSECT 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 ALLALL的含義是返回所有重複行。與之類似,INTERSECT ALLALL的含義是不刪除交集中的重複項。換個角度看,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用於獲取兩個集合的差集,與UNIONINTERSECT類似,EXCEPT也分為兩種形式:EXCEPTEXCEPT ALL。同樣,SQL Server2014也不支援EXCEPT ALL特性。

Query1
EXCEPT
Query2

EXCEPT

UNIONINTERSECT不同,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 ALLEXCEPT的差異在於,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支援三個集合運算子:UNIONINTERSECTEXCEPT,每個運算子均支援兩種行為:去重(不帶ALL關鍵字)和保留重複項(帶上ALL關鍵字)。

T-SQL未提供對INTERSECT ALLEXCEPT ALL的支援,我們可以通過開窗函式ROW_NUMBER()來實現。

另外需要注意一點,集合運算子認為兩個NULL是相等的。

推薦閱讀

T-SQL基礎(三)之子查詢與表表達式