神奇的 SQL 之層級 → 為什麼 GROUP BY 之後不能直接引用原表中的列

来源:https://www.cnblogs.com/youzhibing/archive/2019/09/16/11516154.html
-Advertisement-
Play Games

前言 開心一刻 感覺不妙呀,弟弟舔它! 不該舔的,舔到懷疑人生了...... GROUP BY 後 SELECT 列的限制 標準 SQL 規定,在對錶進行聚合查詢的時候,只能在 SELECT 子句中寫下麵 3 種內容:通過 GROUP BY 子句指定的聚合鍵、聚合函數(SUM 、AVG 等)、常量。 ...


前言

  開心一刻

感覺不妙呀,弟弟舔它! 不該舔的,舔到懷疑人生了......

GROUP BY 後 SELECT 列的限制

  標準 SQL 規定,在對錶進行聚合查詢的時候,只能在 SELECT 子句中寫下麵 3 種內容:通過 GROUP BY 子句指定的聚合鍵、聚合函數(SUM 、AVG 等)、常量。我們來看個例子

  我們有 學生班級表(tbl_student_class) 以及 數據如下 :

DROP TABLE IF EXISTS tbl_student_class;
CREATE TABLE tbl_student_class (
  id int(8) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
  sno varchar(12) NOT NULL COMMENT '學號',
  cno varchar(5) NOT NULL COMMENT '班級號',
  cname varchar(20) NOT NULL COMMENT '班級名',
  PRIMARY KEY (id)
) COMMENT='學生班級表';

-- ----------------------------
-- Records of tbl_student_class
-- ----------------------------
INSERT INTO tbl_student_class VALUES ('1', '20190607001', '0607', '影視7班');
INSERT INTO tbl_student_class VALUES ('2', '20190607002', '0607', '影視7班');
INSERT INTO tbl_student_class VALUES ('3', '20190608003', '0608', '影視8班');
INSERT INTO tbl_student_class VALUES ('4', '20190608004', '0608', '影視8班');
INSERT INTO tbl_student_class VALUES ('5', '20190609005', '0609', '影視9班');
INSERT INTO tbl_student_class VALUES ('6', '20190609006', '0609', '影視9班');

  我們想統計各個班(班級號、班級名)一個有多少人、以及最大的學號,我們該怎麼寫這個查詢 SQL ? 我想大家應該都會

SELECT cno,cname,count(sno),MAX(sno) 
FROM tbl_student_class
GROUP BY cno,cname;

  可是有人會想了,cno 和 cname 本來就是一對一,cno 一旦確定,cname 也就確定了,那 SQL 是不是可以這麼寫 ?

SELECT cno,cname,count(sno),MAX(sno) 
FROM tbl_student_class
GROUP BY cno;

  執行報錯了:

[Err] 1055 - Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'test.tbl_student_class.cname' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

  提示信息:SELECT 列表中的第二個表達式(cname)不在 GROUP BY 的子句中,同時它也不是聚合函數;這與 sql 模式:ONLY_FULL_GROUP_BY 不相容。

  為什麼 GROUP BY 之後不能直接引用原表(不在 GROUP BY 子句)中的列 莫急,我們慢慢往下看。

SQL 模式

  MySQL 伺服器可以在不同的 SQL 模式下運行,並且可以針對不同的客戶端以不同的方式應用這些模式,具體取決於 sql_mode 系統變數的值。 DBA 可以設置全局SQL模式以匹配站點伺服器操作要求,並且每個應用程式可以將其會話 SQL 模式設置為其自己的要求。模式會影響 MySQL 支持的 SQL 語法以及它執行的 數據驗證檢查,這使得在不同環境中使用MySQL以及將MySQL與其他資料庫伺服器一起使用變得更加容易。更多詳情請查閱官網:Server SQL Modes。MySQL 版本不同,內容會略有不同(包括預設值),查閱的時候註意與自身的 MySQL 版本保持一致。

  SQL 模式主要分兩類:語法支持類和數據檢查類,常用的如下

  語法支持類    

    ONLY_FULL_GROUP_BY

      對於 GROUP BY 聚合操作,如果在 SELECT 中的列、HAVING 或者 ORDER BY 子句的列,沒有在GROUP BY中出現,那麼這個SQL是不合法的

    ANSI_QUOTES

      啟用 ANSI_QUOTES 後,不能用雙引號來引用字元串,因為它被解釋為識別符,作用與 ` 一樣。設置它以後,update t set f1="" ...,會報 Unknown column ‘’ in field list 這樣的語法錯誤

    PIPES_AS_CONCAT

      將 || 視為字元串的連接操作符而非 或 運算符,這和Oracle資料庫是一樣的,也和字元串的拼接函數 CONCAT() 相類似

    NO_TABLE_OPTIONS

      使用 SHOW CREATE TABLE 時不會輸出MySQL特有的語法部分,如 ENGINE ,這個在使用 mysqldump 跨DB種類遷移的時候需要考慮

    NO_AUTO_CREATE_USER

      字面意思不自動創建用戶。在給MySQL用戶授權時,我們習慣使用 GRANT ... ON ... TO dbuser 順道一起創建用戶。設置該選項後就與oracle操作類似,授權之前必須先建立用戶

  數據檢查類   

    NO_ZERO_DATE

      認為日期 ‘0000-00-00’ 非法,與是否設置後面的嚴格模式有關

      1、如果設置了嚴格模式,則 NO_ZERO_DATE 自然滿足。但如果是 INSERT IGNORE 或 UPDATE IGNORE,’0000-00-00’依然允許且只顯示warning;

      2、如果在非嚴格模式下,設置了NO_ZERO_DATE,效果與上面一樣,’0000-00-00’ 允許但顯示warning;如果沒有設置NO_ZERO_DATE,no warning,當做完全合法的值;

      3、NO_ZERO_IN_DATE情況與上面類似,不同的是控制日期和天,是否可為 0 ,即 2010-01-00 是否合法;

    NO_ENGINE_SUBSTITUTION

      使用 ALTER TABLE 或 CREATE TABLE 指定 ENGINE 時, 需要的存儲引擎被禁用或未編譯,該如何處理。啟用 NO_ENGINE_SUBSTITUTION 時,那麼直接拋出錯誤;不設置此值時,CREATE用預設的存儲引擎替代,ATLER不進行更改,並拋出一個 warning

    STRICT_TRANS_TABLES

      設置它,表示啟用嚴格模式。註意 STRICT_TRANS_TABLES 不是幾種策略的組合,單獨指 INSERT、UPDATE 出現少值或無效值該如何處理:

      1、前面提到的把 ‘’ 傳給int,嚴格模式下非法,若啟用非嚴格模式則變成 0,產生一個warning;

      2、Out Of Range,變成插入最大邊界值;

      3、當要插入的新行中,不包含其定義中沒有顯式DEFAULT子句的非NULL列的值時,該列缺少值;

  預設模式

    當我們沒有修改配置文件的情況下,MySQL 是有自己的預設模式的;版本不同,預設模式也不同

-- 查看 MySQL 版本
SELECT VERSION();

-- 查看 sql_mode
SELECT @@sql_mode;

     我們可以看到,5.7.21 的預設模式包含:

ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

    而第一個:ONLY_FULL_GROUP_BY 就會約束:當我們進行聚合查詢的時候,SELECT 的列不能直接包含非 GROUP BY 子句中的列。那如果我們去掉該模式(從“嚴格模式”到“寬鬆模式”)呢 ?

    我們發現,上述報錯的 SQL

-- 寬鬆模式下 可以執行
SELECT cno,cname,count(sno),MAX(sno) 
FROM tbl_student_class
GROUP BY cno;
View Code

    能正常執行了,但是一般情況下不推薦這樣配置,線上環境往往是“嚴格模式”,而不是“寬鬆模式”;雖然案例中,無論是“嚴格模式”,還是“寬鬆模式”,結果都是對的,那是因為 cno 與 cname 唯一對應的,如果 cno 與 cname 不是唯一對應,那麼在“寬鬆模式下” cname 的值是隨機的,這就會造成難以排查的問題,有興趣的可以去試試。那為什麼會有 ONLY_FULL_GROUP_BY 模式呢 我們繼續往下看

  階(order)是用來區分集合或謂詞的階數的概念。謂詞邏輯中,根據輸入值的階數對謂詞進行分類。= 或者 BETWEEEN 等輸入值為一行的謂詞叫作"一階謂詞",而像 EXISTS 這樣輸入值為行的集合的謂詞叫作"二階謂詞"(HAVING 的輸入值也是集合,但它不是謂詞)。以此類推,三階謂詞=輸入值為"集合的集合"的謂詞,四階謂詞=輸入值為"集合的集合的集合"的謂詞,但是 SQL 里並不會出現三階
以上的情況,所以不用太在意。簡單點如下圖

  談到了階,就不得不談下集合論;集合論是 SQL 語言的根基,因為它的這個特性,SQL 也被稱為面向集合語言。只有從集合的角度來思考,才能明白 SQL 的強大威力。通過上圖,相信大家也都能看到,這裡不做更深入的講解了,有興趣的可以去查相關資料。

為什麼聚合後不能再引用原表中的列

  很多人都知道聚合查詢的限制,但是很少有人能正確地理解為什麼會有這樣的約束。表 tbl_student_class 中的 cname 存儲的是每位學生的班級信息,但需要註意的是,這裡的 cname 只是每個學生的屬性,並不是小組的屬性,而 GROUP BY 又是聚合操作,操作的對象就是由多個學生組成的小組,因此,小組的屬性只能是平均或者總和等統計性質的屬性,如下圖

  詢問每個學生的 cname 是可以的,但是詢問由多個學生組成的小組的 cname 就沒有意義了。對於小組來說,只有"一共多少學生"或者"最大學號是多少?"這樣的問法才是有意義的。強行將適用於個體的屬性套用於團體之上,純粹是一種分類錯誤;而 GROUP BY 的作用是將一個個元素劃分成若幹個子集,使用 GROUP BY 聚合之後,SQL 的操作對象便由 0 階的"行"變為了 1 階的"行的集合",此時,行的屬性便不能使用了。SQL 的世界其實是層級分明的等級社會,將低階概念的屬性用在高階概念上會導致秩序的混亂,這是不允許的。此時我相信大家都明白:為什麼聚合後不能再引用原表中的列 。

單元素集合也是集合

  現在的集合論認為單元素集合是一種正常的集合。單元素集合和空集一樣,主要是為了保持理論的完整性而定義的。因此對於以集合論為基礎的 SQL 來說,當然也需要嚴格地區分元素和單元素集合。因此,元素 a 和集合 {a} 之間存在著非常醒目的層級差別。

a ≠ {a}

  這兩個層級的區別分別對應著 SQL 中的 WHERE 子句和 HAVING 子句的區別。WHERE 子句用於處理"行"這種 0 階的對象,而 HAVING 子句用來處理"集合"這種 1 階的對象。

總結

  1、SQL 嚴格區分層級,包括謂詞邏輯中的層級(EXISTS),也包括集合論中的層級(GROUP BY);

  2、有了層級區分,那麼適用於個體上的屬性就不適用於團體了,這也就是為什麼聚合查詢的 SELECT 子句中不能直接引用原表中的列的原因;

  3、一般來說,單元素集合的屬性和其唯一元素的屬性是一樣的。這種只包含一個元素的集合讓人覺得似乎沒有必要特意地當成集合來看待,但是為了保持理論的完整性,我們還是要嚴格區分元素和單元素集合;

參考

  《SQL基礎教程》

  《SQL進階教程》


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 講redolog和binlog之前,先要講一下一條mysql語句的執行過程。 1、client的寫請求到達連接器,連接器負責管理連接、驗證許可權; 2、然後是分析器,負責複習語法,如果這條語句有執行過,在緩存內,那麼就從緩存去寫; 3、緩存沒有的話,那就到了優化器部分。負責優化sql讀寫,選擇索引; ...
  • 一 介紹 存儲引擎決定了表的類型,而表記憶體放的數據也要有不同的類型,每種數據類型都有自己的寬度,但寬度是可選的 詳細參考: http://www.runoob.com/mysql/mysql data types.html http://dev.mysql.com/doc/refman/5.7/en ...
  • 待補 https://www.cnblogs.com/luwanying/p/10369776.html ...
  • 待補 ...
  • 用兩個表(a_table、b_table),關聯欄位a_table.a_id和b_table.b_id來演示一下MySQL的內連接、外連接( 左(外)連接、右(外)連接、全(外)連接)。 MySQL版本:Server version: 5.6.31 MySQL Community Server (G ...
  • --存儲過程 DELIMITER | DROP PROCEDURE IF EXISTS update_tatus | CREATE PROCEDURE update_status() BEGIN IF exists (select id from status_his where status = ... ...
  • 1. 在主機Macbook上設置HOST 前文書已經把虛擬機的靜態IP地址設置好,以後可以通過ip地址登錄了。不過為了方便,還是設置一下,首先在Mac下修改hosts文件,這樣在ssh時就不用輸入ip地址了。 sudo vim /etc/hosts 或者 sudo vim /private/etc/ ...
  • 1、集群管理 前臺啟動broker Ctrl + C 關閉 後臺啟動broker 關閉broker 2、Topic管理 創建topic 刪除topic 查詢topic列表 查詢topic詳情 修改topic 3、Consumer Groups管理 查詢消費者組 查詢消費者組詳情 重設消費者組位移 刪 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...