SQL Server溫故系列(4):SQL 查詢之集合運算 & 聚合函數


1、集合運算

在數學中,不僅可以對指定的數字個體做四則運算,還可以對指定的集合整體做交並補運算。類似的,在數據庫中也是不僅可以對具體的數據行進行增刪改查,還可以對查詢結果集進行集合運算。SQL Server 中的集合運算有並集運算、差集運算和交集運算三種,本節將逐一講述。

1.1、並集運算 UNION

並集運算符 UNION 的作用是將兩個或多個查詢的結果集合並為單個結果集。在 UNION 運算中,需要確保各個子結果集的字段數相同、字段的順序相同、字段的數據類型兼容。合並結果集的列名始終會取第一個子結果集中的列名,因此,如果要對合並結果集排序,則需要用第一個子結果集的字段名。

參數 ALL 表示將全部行並入結果集中,換句話說合並結果集可能會包含重復行;相反,如果未指定該參數,則會刪除重復行。需要注意的是,在做重復判斷時,UNION 會把兩個 NULL 值被視為相等的。因為 UNION ALL 不需要刪除重復行,所以性能比 UNION 要好。因此,除非必須要刪除重復行,否則建議一律使用 UNION ALL。

1.1.1、簡單 UNION ALL。查詢 1 班的男生和 2 班的女生。示例如下:

SELECT * FROM T_Students t1 WHERE t1.ClassId = 1 AND t1.Gender = 1
UNION ALL
SELECT * FROM T_Students t2 WHERE t2.ClassId = 2 AND t2.Gender = 0;

1.1.2、簡單 UNION ALL。查詢 1 班的男生和該班的男生人數。示例如下:

SELECT 1 cnt,t1.Code,t1.Name FROM T_Students t1 WHERE t1.ClassId = 1 AND t1.Gender = 1
UNION ALL
SELECT COUNT(1),NULL,NULL FROM T_Students t2 WHERE t2.ClassId = 1 AND t2.Gender = 1;

查詢結果如下:

cnt         Code                           Name
----------- ------------------------------ ------------------------------
1           S330102001                     鄭強
1           S330102002                     肖俊生
1           S330300007                     錢波
1           S330104009                     金橋
4           NULL                           NULL

1.1.3、UNION 與 ORDER BY。查詢 1 班的男生和 2 班的女生,並且將最終結果集按年齡從大到小排序。示例如下:

SELECT * FROM T_Students t1 WHERE t1.ClassId = 1 AND t1.Gender = 1
UNION ALL
SELECT * FROM T_Students t2 WHERE t2.ClassId = 2 AND t2.Gender = 0
ORDER BY t1.Birthday;

1.1.4、UNION 與 SELECT INTO。創建一個活動學生表,並將 1 班的男生和 2 班的女生加入其中。示例如下:

SELECT t1.Code,t1.Name,t1.Gender,t1.Birthday 
INTO T_ActivityStudents 
FROM T_Students t1 
WHERE t1.ClassId = 1 AND t1.Gender=1 
UNION ALL
SELECT t2.Code,t2.Name,t2.Gender,t2.Birthday 
FROM T_Students t2 
WHERE t2.ClassId = 2 AND t2.Gender=0;

1.1.5、通過括號來改變 UNION 的運算順序。示例如下:

WITH t AS(
    SELECT TOP(3) * FROM T_Students t WHERE t.ClassId = 1 ORDER BY t.Birthday
)
SELECT * FROM t
UNION ALL(
    SELECT * FROM t
    UNION
    SELECT * FROM t
);

簡單說明一下:首先,上例會返回 6 條數據。其次,很明顯 CTE 中包含了年齡最大的 3 個學生。然后,括號會提升運算符的優先級,而且 UNION 會刪除重復行,所以括號中會返回 3 條數據。最后,UNION ALL 不會刪除重復行,所以最終返回的 6 條數據是第一個查詢返回的 3 條數據加上括號中兩個查詢共同產生的 3 條數據。

1.2、差集運算 EXCEPT

差集運算符 EXCEPT 的作用是比較兩個查詢的結果集,然后返回左側結果集包含但右側結果集不包含的行,且不包含重復行。與 UNION 運算相同,EXCEPT 運算也要求各個子結果集的字段數相同、順序相同、類型兼容。且 EXCEPT 也會把兩個 NULL 值被視為相等的。示例如下:
SELECT * FROM T_GoodStudents t1 WHERE t1.Gender = 0
EXCEPT
SELECT * FROM T_GoodStudents t2 WHERE t2.Birthday < '2000-01-01';

注意:上例是用女生這個結果集減去非 00 后學生結果集,但得到的結果集並不一定是 00 后女生,因為女生的出生日期可能是 NULL,所以最終的結果集是 00 后女生加上出生日期為 NULL 的女生。

類似於 UNION:EXCEPT 也可以與 ORDER BY 連用來給差集(最終的結果集)排序,而且差集的列名也跟第一個子結果集的列名相同。EXCEPT 還可以與 SELECT INTO 連用將差集拷貝到一個新表中。當有多個 EXCEPT 時也可以通過括號來改變運算順序。具體用法可參考 UNION 的示例。

1.3、交集運算 INTERSECT

交集運算符 INTERSECT 的作用是比較兩個查詢的結果集,然后返回左側結果集和右側結果集都包含的行,且不包含重復行。與 UNION 運算相同,INTERSECT 運算也要求各個子結果集的字段數相同、順序相同、類型兼容。且 INTERSECT 也會把兩個 NULL 值被視為相等的。示例如下:
SELECT * FROM T_GoodStudents t1 WHERE t1.Gender = 0
INTERSECT
SELECT * FROM T_GoodStudents t2 WHERE t2.Birthday < '2000-01-01';

注意:上例是取女生這個結果集和非 00 后學生結果集的交集,但得到的結果集並不一定是非 00 后女生,因為女生的出生日期可能是 NULL,所以最終的結果集是非 00 后女生加上出生日期為 NULL 的女生。

類似於 UNION:INTERSECT 也可以與 ORDER BY 連用來給交集(最終的結果集)排序,而且交集的列名也跟第一個子結果集的列名相同。INTERSECT 還可以與 SELECT INTO 連用將交集拷貝到一個新表中。當有多個 INTERSECT 時也可以通過括號來改變運算順序。具體用法可參考 UNION 的示例。

1.4、集合運算小結

上文逐一講述了各個集合運算符的語法和用途。其實這些集合運算符不僅可以單獨使用,還可以結合起來使用,示例如下:
SELECT t1.Id,t1.Name,t1.Gender,t1.Birthday FROM T_Students t1 WHERE t1.ClassId = 1
UNION ALL
SELECT t2.Id,t2.Name,t2.Gender,t2.Birthday FROM T_Students t2 WHERE t2.ClassId = 3
EXCEPT
SELECT * FROM T_GoodStudents t3 WHERE t3.Birthday < '2000-01-01'
INTERSECT
SELECT * FROM T_GoodStudents t4 WHERE t4.Gender = 1;

注意:上例的運算順序並不是先 UNION ALL,然后 EXCEPT,再 INTERSECT。因為 INTERSECT 比 EXCEPT 和 UNION 的優先級要高,而 EXCEPT 與 UNION 的優先級相同,所以上例的實際運算順序是先 INTERSECT,然后 UNION ALL,最后再用 UNION ALL 的結果集減 INTERSECT 的結果集,即最后進行 EXCEPT 運算。

2、聚合函數

聚合函數的作用是對一組值執行計算,並返回單個結果值。聚合函數只能在 SELECT 子句或 HAVING 子句中作為表達式來用。求行數函數 COUNT、求和函數 SUM、求最大值函數 MAX、求最小值函數 MIN、求平均值函數 AVG,這 5 個函數是最常用的聚合函數,主流的關系型數據庫也都支持它們。

其實常見的關系型數據庫都支持很多聚合函數,但其中大部分都是非標准的,各個數據庫之間的差別也比較大,而且這些函數也都不常用。個人建議實際工作中不要用這些函數,以免跟某個具體的數據庫綁死。

2.1、求行數函數 COUNT

求行數函數 COUNT 會返回查詢結果集的行數。COUNT 函數中可以是一個具體的列,也可以是代表所有列的星號,還可以是一個具體的常量或變量。示例如下:
SELECT COUNT(t.Id) result FROM T_Students t; -- result:32 rows
SELECT COUNT(*) result FROM T_Students t;    -- result:32 rows
SELECT COUNT(1) result FROM T_Students t;    -- result:32 rows

COUNT 是所有聚合函數中唯一不會忽略 NULL 值的函數,但如果被計算列本身含有 NULL 值是會被忽略的。示例如下:

SELECT COUNT(Remark) result FROM T_Students t; -- result:28 rows

在 COUNT 函數中添加 DISTINCT 參數,表示會先去除重復行,然后統計剩下的非 NULL 且唯一的值的個數。示例如下:

SELECT COUNT(DISTINCT Remark) result FROM T_Students t; -- result:26 rows

注意:COUNT 函數的返回值一定是大於或等於 0 的整數,證明如下:

SELECT COUNT(*) result;             -- result:1 rows
SELECT COUNT(*) result WHERE 1 = 2; -- result:0 rows

另外,從 SQL Server 2008 開始,官方在增加了一個COUNT_BIG函數,它的用法和用途與 COUNT 完全相同,唯一不同的就是返回值的類型。COUNT 函數總是返回一個 INT 類型的整數,而COUNT_BIG函數總是返回一個 BIGINT 類型的整數。示例如下:

SELECT COUNT_BIG(1) result FROM T_Students t;               -- result:32 rows
SELECT COUNT_BIG(Remark) result FROM T_Students t;          -- result:28 rows
SELECT COUNT_BIG(DISTINCT Remark) result FROM T_Students t; -- result:26 rows

2.2、求和函數 SUM

求和函數 SUM 會返回表達式中所有值的和。SUM 函數會忽略所有 NULL 值,且只能應用於數字類型的字段。例如要查詢學生 1 第 1 次考試的總分,示例如下:
SELECT SUM(t.Scores) FROM T_ExamResults t WHERE t.StudentId = 1 AND t.Counts = 1;

在 SUM 函數中添加 DISTINCT 參數,表示會先去除重復行,然后統計剩下的非 NULL 且唯一的數值之和。示例如下:

SELECT SUM(t.Scores) result FROM T_ExamResults t WHERE t.StudentId = 6;          -- result:2202.5
SELECT SUM(DISTINCT t.Scores) result FROM T_ExamResults t WHERE t.StudentId = 6; -- result:1857.5

注意:SUM 函數的返回值有可能會是 NULL 值,證明如下:

SELECT SUM(1) result;             -- result:1
SELECT SUM(1) result WHERE 1 = 2; -- result:NULL

2.3、求最大值函數 MAX

求最大值函數 MAX 會返回表達式中的最大值。MAX 函數會忽略所有 NULL 值。例如要查詢學生 1 課程 1 的歷次考試最高分,示例如下:
SELECT MAX(t.Scores) FROM T_ExamResults t WHERE t.StudentId = 1 AND t.Counts = 1;

MAX 函數還可以作用於日期類型或字符類型,此時 MAX 將按照日期數值或字符排序順序來確定最大值。示例如下:

SELECT MAX(t.Birthday) FROM T_Students t; -- 數值最大的出生日期,年齡最小
SELECT MAX(t.Name) FROM T_Students t;     -- 字符排序最靠后的姓名

注意:MAX 函數的返回值有可能會是 NULL 值,證明如下:

SELECT MAX(1) result;             -- result:1
SELECT MAX(1) result WHERE 1 = 2; -- result:NULL

2.4、求最小值函數 MIN

求最小值函數 MIN 會返回表達式中的最小值。MIN 函數會忽略所有 NULL 值。例如要查詢學生 1 課程 1 的歷次考試最低分,示例如下:
SELECT MIN(t.Scores) FROM T_ExamResults t WHERE t.StudentId = 1 AND t.Counts = 1;

MIN 函數還可以作用於日期類型或字符類型,此時 MAX 將按照日期數值或字符排序順序來確定最小值。示例如下:

SELECT MIN(t.Birthday) FROM T_Students t; -- 數值最小的出生日期,年齡最大
SELECT MIN(t.Name) FROM T_Students t;     -- 字符排序最靠前的姓名

注意:MIN 函數的返回值有可能會是 NULL 值,證明如下:

SELECT MIN(1) result;             -- result:1
SELECT MIN(1) result WHERE 1 = 2; -- result:NULL

2.5、求平均值函數 AVG

求平均值函數 AVG 會返回表達式中所有值的平均值。AVG 函數會忽略所有 NULL 值,且只能應用於數字類型的字段。例如要查詢學生 1 第 1 次考試的平均分,示例如下:
SELECT AVG(t.Scores) FROM T_ExamResults t WHERE t.StudentId = 1 AND t.Counts = 1;

在 AVG 函數中添加 DISTINCT 參數,表示會先去除重復行,然后統計剩下的非 NULL 且唯一的數值之和。示例如下:

SELECT AVG(t.Scores) result FROM T_ExamResults t WHERE t.StudentId = 6;          -- result:73.416666
SELECT AVG(DISTINCT t.Scores) result FROM T_ExamResults t WHERE t.StudentId = 6; -- result:74.300000

注意:AVG 函數的返回值有可能會是 NULL 值,證明如下:

SELECT AVG(1) result;             -- result:1
SELECT AVG(1) result WHERE 1 = 2; -- result:NULL

2.6、聚合函數小結

上文逐一講述了常見五大聚合函數的基本語法和用途。其實這些聚合函數不僅可以單獨使用,還可以結合起來使用。

示例一、查詢第 1 次課程 2 考試成績的統計結果:

SELECT COUNT(1) 參與人數,
    SUM(t.Scores) 總分,MAX(t.Scores) 最高分,MIN(t.Scores) 最低分,AVG(t.Scores) 平均分
FROM T_ExamResults t 
WHERE t.Counts = 1 AND t.CourseId = 2;

示例二、查詢 1 班的學生總數及年齡統計結果:

WITH t AS(
    SELECT t.Code,t.Name,DATEDIFF(YEAR,t.Birthday,GETDATE()) Age 
    FROM T_Students t 
    WHERE t.ClassId = 1
)
SELECT COUNT(1) 學生個數,MAX(t.Age) 最大年齡,MIN(t.Age) 最小年齡,AVG(t.Age) 平均年齡 
FROM t;

注意:本文所有關於聚合函數的示例,查詢選擇列表中包含的都是聚合函數表達式,沒有一個字段,因為不允許,不過倒是可以包含與表字段無關的常量或變量。其實聚合函數通常與 GROUP BY 子句一起使用,而且也只有包含在 GROUP BY 子句中的字段才能出現在查詢選擇列表中,具體原因將在下一篇博文中具體講述。

3、本文小結

本文主要講述了 SQL Server 中的集合運算和聚合函數,以及它們的基本語法和用途。在集合運算中,UNION 和 UNION ALL 是比較常用的。而常見的 5 個聚合函數都比較常用。

本文參考鏈接:

去導航目錄篇下載創建本系列博文通用庫表及數據的 SQL 語句

本文鏈接http://www.cnblogs.com/hanzongze/p/tsql-aggregate.html
版權聲明:本文為博客園博主 韓宗澤 原創,作者保留署名權!歡迎通過轉載、演繹或其它傳播方式來使用本文,但必須在明顯位置給出作者署名和本文鏈接!個人博客,能力有限,若有不當之處,敬請批評指正,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM