詳細分析SQL語句邏輯執行過程和相關語法

来源:https://www.cnblogs.com/f-ck-need-u/archive/2018/03/27/8656828.html
-Advertisement-
Play Games

本文目錄: 1.SQL語句的邏輯處理順序 1.2 各資料庫系統的語句邏輯處理順序 1.2.1 SQL Server和Oracle的邏輯執行順序 1.2.2 MariaDB的邏輯執行順序 1.2.3 MySQL的邏輯執行順序 1.3 關於表表達式和虛擬表 1.4 關於表別名和列別名 1.5 關於數據無 ...


本文目錄:
1.SQL語句的邏輯處理順序
1.2 各資料庫系統的語句邏輯處理順序
 1.2.1 SQL Server和Oracle的邏輯執行順序
 1.2.2 MariaDB的邏輯執行順序
 1.2.3 MySQL的邏輯執行順序
1.3 關於表表達式和虛擬表
1.4 關於表別名和列別名
1.5 關於數據無序性和ORDER BY
1.6 關於TOP(或LIMIT)和ORDER BY
1.7 關於DISTINCT和GROUP BY
1.8 關於DISTINCT和ORDER BY
1.9 關於標準SQL的GROUP BY
1.10 關於MySQL/MariaDB的GROUP BY
1.11 關於OVER( )
1.12 總結

1.1 SQL語句的邏輯處理順序

SQL語句的邏輯處理順序,指的是SQL語句按照一定的規則,一整條語句應該如何執行,每一個關鍵字、子句部分在什麼時刻執行。

除了邏輯順序,還有物理執行順序。物理順序是SQL語句真正被執行時的順序(執行計劃),它是由各資料庫系統的關係引擎中的語句分析器、優化器等等組件經過大量計算、分析決定的。

很多時候因為優化的關係,使得語句最終的物理執行順序和邏輯順序不同。按照邏輯順序,有些應該先執行的過程,可能優化器會指定它後執行。但不管是邏輯順序還是物理順序,設計了一條SQL語句,語句最後返回的結果不會也不能因為物理順序改變了邏輯順序而改變。

其實,邏輯順序只是為我們編寫、理解SQL語句提供些幫助,除此之外,它毫無用處。而且,是不是真的存在一條嚴格且完整的執行順序規則都是不確定的事情。雖然某些書上、網上給出了一些順序(我個人所知道的比較權威的,是SQL Server的"聖書"技術內幕里介紹過),但在任何一種資料庫系統的官方手冊上都沒有關於這方面的介紹文檔。

SQL Server和Oracle在語句的邏輯處理順序上是一致的,在這方面,它們嚴格遵守了標準SQL的要求,任何一個步驟都遵循了關係型資料庫的範式要求。因為遵循了一些範式要求,導致標準SQL不允許使用某些語法。但是MySQL、MariaDB和它們小有不同,它們對標準SQL進行擴展,標準SQL中不允許使用的語法,在MySQL、MariaDB中可能可以使用,但很多時候這會違反關係模型的範式要求。

雖然本文的最初目的是介紹MariaDB/MySQL語句的邏輯處理順序,但在篇幅上對標準SQL介紹的更多,因為它們符合規範。理解這些規範,實際上是在理解關係模型和集合模型。本文也在多處通過這兩個模型來分析為什麼標準SQL不允許某些語法,以及為什麼MySQL可以支持這些"不標準"的語法

1.2 各資料庫系統的語句邏輯處理順序

以SELECT語句為例。

1.2.1 SQL Server和Oracle的邏輯執行順序

如下圖:

關於本圖需要說明的是,雖然圖中給出的順序是DISTINCT比ORDER BY先執行,這也是網上流傳的版本。但其實,在DISTINCT和ORDER BY之間的順序沒有嚴格的界限,甚至ORDER BY的順序要優先於DISTINCT後文會分析為什麼。而且剛剛去翻了下sql server技術內幕中關於邏輯處理順序的內容,發現它沒有對DISTINCT執行位置進行排序,只是在介紹ORDER BY時提了下DISTINCT,我想也是因為DISTINCT和ORDER BY之間沒有嚴格的順序。

後面關於MySQL和mariadb的兩張邏輯順序圖中,將會把DISTINCT和ORDER BY的順序調換過來。

以下是對上述邏輯執行順序的描述:

(1).首先從FROM語句中獲取要操作的表並計算笛卡爾積。如果有要聯接的表,則還獲取聯接表。對它們計算笛卡爾積,笛卡爾積的結果形成一張虛擬表vt1。

這裡就體現了物理順序和邏輯順序的一個不同點:按照邏輯順序,在執行SQL語句之初總是會進行笛卡爾積的計算,如果是兩張或多張非常大的表,計算笛卡爾積是非常低效的行為,這是不能容忍的。所以物理順序會進行一些優化決定,比如使用索引跳過一部分或整個笛卡爾積讓計算變得很小。

(2).對虛擬表vt1執行ON篩選語句,得到虛擬表vt2。

(3).根據聯接類型,將保留表的外部行添加到vt2中得到虛擬表vt3。

(4).對vt3執行where條件篩選,得到虛擬表vt4。

(5).執行分組,得到虛擬表vt5。

註意,分組之後,整個SQL的操作上下文就變成了分組列,而不再是表中的每一列,後續的一切操作都是圍繞所分的組作為操作對象進行操作的。也就是說,不在分組列表中的列不能在後續步驟中使用。例如,使用"group by a"對a列分組,那麼後續的select列表中就不能使用b列,除非是對b列進行分組聚合運算。SQL Server、Oracle和MariaDB、Mysql最大的區別就在於此步,後兩者可以引用分組列以外的列。

(6).對vt5執行集合操作cube或者rollup,得到虛擬表vt6。

(7).對分組的最終結果vt6執行having篩選,得到虛擬表vt7。

(8).根據給定的選擇列列表,將vt7中的選擇列插入到虛擬表vt8中。

註意,選擇列是"同時性操作",在選擇列中不能使用列別名來引用列表中的其他列。例如 select col1+1 as a,a+1 as b from t1 是錯誤的,因為"col1+1"和"a+1"之間沒有執行上的先後順序,所以它認為"a+1"中的a列是不存在的。

(9).對vt8進行視窗分組相關的計算,得到虛擬表vt9。

(10).對vt9按照指定的列去除重覆行,得到虛擬表vt10。

這一步是將數據複製到記憶體中相同的臨時表結構中進行的,不過該臨時表多出了一個唯一性索引列用來做重覆消除。

(11).對vt10進行排序,排序後的表為虛擬表vt11。

(12).從vt11中根據top條件挑出其中滿足的行,得到虛擬表vt12。

如果沒有應用order by,則記錄是無序的集合,top挑出的行可能是隨機的。也因此top一般要和order by字句一起使用。

(13).將vt12從服務端返回給客戶端作為最終查詢結果。

1.2.2 MariaDB的邏輯執行順序

如下圖:

MariaDB中,使用的是LIMIT子句實現和TOP子句一樣的功能:限制輸出行數。且它不支持"WITH CUBE"(直接忽略該關鍵詞)。

但和SQL Server、Oracle最大的不同是對SELECT列表的處理。在MS SQL和Oracle中,select_list是在group by和having子句之後才進行的,這意味著group by分組後,不能在select_list中指定非分組列(除非聚合運算),反過來看,在group by中不能使用select_list中的別名列。

但在MariaDB和MySQL中,select_list是在group by之前進行的。在group by中能夠引用select_list中的列,在select_list中也能指定非分組列。

mariadb和mysql在這一點上實際上是"不規範"的,因為它違背了資料庫的設計範式。詳細內容在後文分析。

1.2.3 MySQL的邏輯執行順序

如下圖:

和MariaDB之間並沒有什麼區別,僅僅只是MySQL不支持開窗函數over()。

但是註意,從mysql 5.7.5開始,已經預設設置了sql_mode=ONLY_FULL_GROUP_BY,這意味著MySQL預設也將遵循SQL規範,對於那些非分組列又沒有進行聚合的列,都不允許出現在select_list中,除非select_list中使用的列是主鍵或者唯一索引列,之所以允許這樣的行為,是因為有功能依賴性決定了它可以這樣做,由此保證"規範性"。同樣的,為何不規範的問題見後文。

1.3 關於表表達式和虛擬表

派生表、CTE(公用表表達式,有的資料庫系統支持)、視圖和表函數都是表,我們常稱之為"表表達式",只不過它們是虛擬表(這裡的虛擬表和上面邏輯執行過程中產生的虛擬表vt不是同一個概念)。它們都必須滿足成為表的條件,這也是為什麼定義表表達式的時候有些語法不能使用。

從關係模型上去分析。表對應的是關係模型中的關係,表中的列對應的是關係模型中的元素。

一方面,關係和元素都需要有唯一標識的名稱,因此表和列也要有名稱,即使表表達式也如此。像派生表是嵌套在語句中的,無法在外部給它指定表明,因此必須為它指定一個表別名。同理,表表達式中的別名也一樣,必須唯一且必須要有。

另一方面,關係中的元素是無序的,因此表和表表達式中的數據也應當是無序的。雖然有些表表達式中可以使用ORDER BY子句,但這時候的ORDER BY只是為了讓TOP/LIMIT子句來挑選指定數量的行,並不是真的會對結果排序。也就是說表表達式挑選出來的行就像表一樣,其內數據行仍然是無序的,以後訪問它們的時候是按照物理存儲順序進行訪問的,即使表表達式的定義語句中使用了ORDER BY子句。

關於數據的無序性和隨機性,見下文。

這裡還請區分表表達式(虛擬表)和邏輯執行過程中我們想象出來的虛擬表。表表達式是實實在在符合關係模型的表,即使它可能只是一條或幾條語句,也不會將相關數據行進行物理的存儲,但在關係引擎看來,它就是表。而邏輯執行過程中我們想象出來的虛擬表,只是為了方便理解而描述出來的,實際上不會有這樣的表,它們只是按一定規則存放在記憶體中的一些數據行,雖然某些步驟中可能也會使用系統自建的臨時表存放中途的數據,但它們不是表。

1.4 關於表別名和列別名

在SQL語句中,我們避免不了要對錶、列使用別名進行引用。關於別名,需要註意兩點:

(1).定義了表別名後,在語句中對該表的引用都必須使用別名,而不能使用原表名。

(2).引用別名時,註意查詢的邏輯處理過程。在某一階段只能引用該階段前面階段定義的別名,使用該階段後才定義的別名將報錯。

例如下麵的兩個查詢語句,第一個錯誤原因是不能引用原表名,第二個錯誤是因為WHERE階段不能引用SELECT階段定義的欄位別名。

SELECT Student.Name FROM Student AS 學生表
SELECT Name,Sex AS 性別 FROM Student WHERE 性別 = ''

下麵是正確的寫法。

SELECT 學生表.Name FROM Student AS 學生表
SELECT Name,Sex AS性別 FROM Student WHERE Sex = ''

1.5 關於數據無序性和ORDER BY

在關係型資料庫中,必須時刻都銘記在心的是"集合元素是無序"的,體現在資料庫中就是"表中數據行是無序的",除非建立了相關索引。

出於集合模型的考慮,像我們平時看到的有行、有列的二維表數據(下圖左邊),更應該看作是下圖右邊的結合結構,因為集合是無序的。

由於數據無序,導致檢索數據時都是按照存儲時的物理順序進行訪問,如此檢索得到的數據行都是隨機而不保證任何順序的,除非指定了ORDER BY子句。而使用ORDER BY查詢得到的結果,它因為有序而不滿足集合的概念。實際上ORDER BY生成的是一個游標結果。瞭解SQL的人,都知道能不用游標就儘量不用游標,因為它的效率相比符合集合概念的SQL語句來說,要慢很多個數量級。但也不能一棍子將其打死,因為有時候使用游標確實能比較容易達到查詢目標。

SQL中沒有使用ORDER BY時,有不少子句的返回結果(虛擬表)都是隨機的,因為實在沒辦法去保證順序,但卻又要求返回數據。例如直接進行 SELECT * from t; ,再例如TOP/LIMIT子句。

縱觀整個SQL的各個環節,不難發現很多時候獲取隨機行數據是不應該的,因為這種不確定性,讓我們操作數據時顯得更出乎意料、更危險因此,除非不得不顯示隨機數據,標準SQL都會通過一些手段讓獲取隨機數據的行為失敗,而且在可能獲取隨機數據的時候,一般都會給出相關的建議和提示。

MySQL、mariadb之所以和sql server、oracle的語法相差那麼大,歸根結底就是對待關係型資料庫的範式要求和隨機數據的態度不同。MySQL、mariadb總是"偷姦耍滑",在本無法滿足關係型資料庫範式的時候,它們總是挑選一個隨機單行數據出來,讓返回結果滿足範式要求,最典型的就是group by的處理方式不過MySQL從5.7.5版本開始,已經逐漸走向規範化了。

這裡並非是要否認mysql、mariadb的設計模式,正所謂每個資料庫系統都有自己對標準SQL的擴展方式,MySQL只是走了一條和標準SQL不同的路而已。而且關係模型的範式本就是人為定義的,為何不能違反呢?甚至可以說,表所滿足的範式越強,檢索表時的性能越低,nosql就沒有關係模型的範式要求。

在後文,將在多處分析標準SQL為什麼不允許某些語法,同時還會提到MySQL和mariadb是如何"偷姦耍滑"的。

1.6 關於TOP(或LIMIT)和ORDER BY

TOP和LIMIT是限制輸出行數量,它們挑選數據行時是隨機的(根據物理訪問順序),所以得到的結果也是隨機的。因此,建議TOP/LIMIT和ORDER BY一起使用。但即使如此,仍是不安全的。例如,ORDER BY的列中有重覆值,那麼TOP/LIMIT的時候如何決定獲取哪些行呢?見如下LIMIT的示例(TOP也一樣):

MariaDB [test]> select * from Student order by age;
+------+----------+------+--------+
| sid  | name     | age  | class  |
+------+----------+------+--------+
|    6 | zhaoliu  |   19 | Java   |
|    4 | lisi     |   20 | C#     |
|    8 | sunba    |   20 | C++    |
|    3 | zhangsan |   21 | Java   |
|    5 | wangwu   |   21 | Python |
|    1 | chenyi   |   22 | Java   |
|    7 | qianqi   |   22 | C      |
|    2 | huanger  |   23 | Python |
|    9 | yangjiu  |   24 | Java   |
+------+----------+------+--------+
 
MariaDB [test]> select * from Student order by age limit 9;
+------+----------+------+--------+
| sid  | name     | age  | class  |
+------+----------+------+--------+
|    6 | zhaoliu  |   19 | Java   |
|    4 | lisi     |   20 | C#     |
|    8 | sunba    |   20 | C++    |
|    3 | zhangsan |   21 | Java   |
|    5 | wangwu   |   21 | Python |
|    1 | chenyi   |   22 | Java   |
|    7 | qianqi   |   22 | C      |
|    2 | huanger  |   23 | Python |
|    9 | yangjiu  |   24 | Java   |
+------+----------+------+--------+

從兩次查詢結果中看到,即使都是對age進行升序排列,但age=20的兩行前後順序不一致,age=22的行順序也不一致。

因此一般會給另一個建議,為了確保數據一定是符合預期的,在order by中應該再加一列(最好具有唯一性)作為決勝屬性,例如對age排序後再按照sid排序,這樣就能保證返回結果不是隨機的。

select * from Student order by age,sid;
select * from Student order by age,sid limit 9;

1.7 關於DISTINCT和GROUP BY

DISTINCT子句用於消除select_list列的重覆行,這很容易理解。大多數情況下,DISTINCT子句在功能上都可以認為等價於group by子句。有些DISTINCT不適合做的操作,可以在GROUP BY中來完成。

例如下麵兩個SQL語句是等價的:

select distinct class,age from Student; 
select class,age from Student group by class,age;

正因為等價,很多時候對DISTINCT的優化行為總是和GROUP BY的優化行為一致。以下是sql server上對上述兩條語句的執行計劃:

select distinct class,age from Student;
  |--Sort(DISTINCT ORDER BY:([test].[dbo].[Student].[class] ASC, [test].[dbo].[Student].[age] ASC))
       |--Table Scan(OBJECT:([test].[dbo].[Student]))
 select class,age from Student group by class,age;
  |--Sort(DISTINCT ORDER BY:([test].[dbo].[Student].[class] ASC, [test].[dbo].[Student].[age] ASC))
       |--Table Scan(OBJECT:([test].[dbo].[Student]))

從結果中看到,執行DISTINCT去除重覆行時,預設就帶有了排序過程。實際上,DISTINCT幾乎總是會將數據複製到記憶體中的一張臨時表中進行,該臨時表的結構和前面得到的虛擬表欄位結構幾乎一致,但卻多了一個唯一性索引列用來做重覆消除。

但如果DISTINCT結合GROUP BY子句呢?其實不建議這麼做。這裡也不討論這種問題。

1.8 關於DISTINCT和ORDER BY

既然DISTINCT預設就帶了排序行為,那此時再指定ORDER BY會如何?例如下麵的語句:

select distinct class,age from Student ORDER BY age desc; 

在SQL Server中的執行計劃如下:

select distinct class,age from Student ORDER BY age desc;
  |--Sort(DISTINCT ORDER BY:([test].[dbo].[Student].[age] DESC, [test].[dbo].[Student].[class] ASC))
       |--Table Scan(OBJECT:([test].[dbo].[Student]))

其實和前面沒什麼區別,無非是先對order by列進行排序而已。但是從這裡能看出,DISTINCT和ORDER BY字句其實沒有嚴格的邏輯執行先後順序,甚至ORDER BY指定的排序列還優先於DISTINCT的排序行為。

但是,DISTINCT和ORDER BY結合時,order by的排序列是有要求的:排序列必須是select_list中的列(distinct很多時候都可以看作group by)。例如 select distinct a,b from t order by c; 是錯誤的。但MySQL和mariadb又在這裡進行了擴展,它們的排序列允許非select_list中的列。

先說標準SQL為何不允許使用非select_list中的列,這歸根結底還是關係型資料庫的範式問題。假如DISTINCT消除了部分列的重覆值,最終將只返回一條重覆記錄,而如果使用非select_list的列排序,將要求返回一條重覆記錄的同時還要返回每個重覆值對應的多條記錄以便排序,而在要求範式的關係表中是無法整合這樣的結果。

例如表中數據如下:

MariaDB [test]> select sid,age,class from Student order by class;
+------+------+--------+
| sid  | age  | class  |
+------+------+--------+
|    7 |   22 | C      |
|    4 |   20 | C#     |
|    8 |   20 | C++    |
|    1 |   22 | Java   |
|    3 |   21 | Java   |
|    6 |   19 | Java   |
|    9 |   24 | Java   |
|    2 |   23 | Python |
|    5 |   21 | Python |
+------+------+--------+

現在對class列進行去重。

MariaDB [test]> select distinct class from Student order by class;
+--------+
| class  |
+--------+
| C      |
| C#     |
| C++    |
| Java   |
| Python |
+--------+

現在假設order by的排序列能使用sid進行排序。那麼期待的結果將是根據如下數據進行返回的:

select distinct class from Student order by sid;
+------+--------+
| sid  | class  |
+------+--------+
|    7 | C      |
+---------------+
|    4 | C#     |
+---------------+
|    8 | C++    |
+---------------+
|    1 |        |
|    3 | Java   |
|    6 |        |
|    9 |        |
+---------------+
|    2 |        |
|    5 | Python |
+------+--------+ 

這樣的結構已經違反了關係型資料庫的範式要求。因此,sql server和oracle會直接對該語句報錯。

但是MySQL/mariadb就允許在order by中使用非select_list列進行排序。它們是如何"偷姦耍滑"的呢?還是上面違反關係模型範式的數據結構,MySQL和mariadb會從Java和Python對應的sid中挑選第一行(order by已經對其排序,因此不是隨機數據),然後和Java、Python分別組成一行,得到如下虛擬表:

+------+--------+
| sid  | class  |
+------+--------+
|    7 | C      |
+---------------+
|    4 | C#     |
+---------------+
|    8 | C++    |
+---------------+
|    1 | Java   |
+---------------+
|    2 | Python |
+------+--------+ 

然後將此虛擬表中非select_list中的列都去掉,得到最終結果。真的是最終結果嗎?

MariaDB [test]> select distinct class from Student order by sid; 
+--------+
| class  |
+--------+
| Java   |
| Python |
| C#     |
| C      |
| C++    |
+--------+

雖然返回的結果內容上和前面分析的一致,但是順序卻不一致,影響因素就是"order by sid"。

其實認真觀察結果,很容易就發現它們是根據sid排序後再對class去重得到的結果。也就是說,ORDER BY子句比DISTINCT子句先執行了。稍稍分析一下,這裡先以sid排序,得到如下虛擬結果:

+--------+------+
| class  | sid  |
+--------+------+
| Java   |    1 |
| Python |    2 |
| Java   |    3 |
| C#     |    4 |
| Python |    5 |
| Java   |    6 |
| C      |    7 |
| C++    |    8 |
| Java   |    9 |
+--------+------+

再對class去重,得到如下虛擬結果:

+------+--------+
| sid  | class  |
+---------------+
|    1 |        |
|    3 | Java   |
|    6 |        |
|    9 |        |
+------+--------+
|    2 |        |
|    5 | Python |
+------+--------+
|    4 | C#     |
+---------------+
|    7 | C      |
+---------------+
|    8 | C++    |
+---------------+

最後去掉非select_list中的列sid,得到最終結果。

1.9 關於標準SQL的GROUP BY

如果讓我給SQL語句的邏輯執行順序劃分為兩段式,我會將"三八線"劃在GROUP BY這裡。因為在GROUP BY之前甚至完全沒有GROUP BY子句的語句部分,操作的對象都是表中的每行數據,也就是說操作的上下文環境是表的數據行。而在GROUP BY之後,操作的對象是組而不再是行,也就是說操作的上下文將從表中的數據行變成組。

直白一點說,GROUP BY之前,關係引擎的目光集中在數據行的細節上,GROUP BY之後,關係引擎的目光則集中在組上。至於每個分組中的行,對關係引擎來說是透明的,它不在乎組中行這種細節性的東西是否存在,而且按照關係模型的要求,也不應該認為它們存在。註意,這裡說的是標準SQL,而MySQL和mariadb又"偷姦耍滑"去了。

舉個例子就很容易理解GROUP BY前後側重點的變化過程。

以下是Student表的內容。

MariaDB [test]> select * from Student;
+------+----------+------+--------+
| sid  | name     | age  | class  |
+------+----------+------+--------+
|    1 | chenyi   |   22 | Java   |
|    2 | huanger  |   23 | Python |
|    3 | zhangsan |   21 | Java   |
|    4 | lisi     |   20 | C#     |
|    5 | wangwu   |   21 | Python |
|    6 | zhaoliu  |   19 | Java   |
|    7 | qianqi   |   22 | C      |
|    8 | sunba    |   20 | C++    |
|    9 | yangjiu  |   24 | Java   |
+------+----------+------+--------+

現在按照class進行分組。下麵是分組後經過我加工的表結構:

其中第一列是分組得到的結果,我把它和原表的數據結合在一起了。註意,這是一個不符合關係模型範式要求的結構。

在分組前,關係引擎會對sid、name、age和class列的每一行進行篩選。但是分組後,關係引擎只看得到第一列,也就是class列,而sid、name和age列被直接忽略,因此無法引用它們。

關於GROUP BY,有以下兩個問題:

1.為什麼分組之後涉及到對組的操作時只允許返回標量值?

標量值即單個值,比如聚合函數返回的值就是標量值。在分組之後,組將成為表的工作中心,一個組將成為一個整體,所有涉及到分組的查詢,將以組作為操作對象。組的整體是重要的,組中的個體不重要,甚至可以理解為分組後只有組的整體,即上圖中左邊加粗的部分,而組中的個體是透明的。

以上圖中的第一條記錄舉一個通俗的例子。在分組以前,知道了該學生的姓名"chenyi"之後,關註點可能要轉化為它的主鍵列sid值"1",因為主鍵列唯一標識每一行,知道了主鍵值就知道了該行的所有信息。而在分組之後,關註的中心只有分組列class,無論是知道姓名"chenyi"還是學號"1"都不是關註的重點,重點是該行記錄(集合的元素)是屬於"Java"班級的

這就能解釋為什麼只能以組作為操作對象並返回標量值。例如,在分組之後進行SUM彙總,將以"Java"班作為一個彙總對象,以"Python"班作為另一個彙總對象,彙總的將是每個分組的總值,而不是整個表的總值,並且彙總的值是一個標量值,不會為組中的每行都返回這個彙總值。否則就違反了關係模型的範式。

2.為什麼分組之後只能使用GROUP BY列表中的列,如果不在GROUP BY列表中,就必須進行聚合?

分組後分組列成為表的工作中心,以後的操作都必須只能為組這個整體返回一個標量值。

如果使用了非分組列表的列,將不能保證這個標量值。例如,分組後對"Java"班返回了一個彙總值,假如同時要使用sid列和name列,因為這兩列沒有被聚合或分組,因此只能為這兩列的每個值返回一行,也就是說在返回彙總標量值的同時還要求返回"Java"班組中的每一行,要實現這樣的結果,需要整合為如上圖所示的結果,但在關係表中這是違反規範的。正如前文介紹的DISTINCT一樣,ORDER BY的排序列只能使用DISTINCT去重的select_list列表。

因此,分組後只能使用分組列表中的列。如果要使用非分組列表中的列,應該讓它們也返回一個標量值,只有這樣才能實現分組列和非分組列結果的整合。例如,下麵的語句將會產生錯誤,因為select_list在GROUP BY階段後執行,且select_list中的列沒有包含在GROUP BY中,也沒有使用聚合函數。

SELECT sid,name FROM Student GROUP BY class;

事實上從嚴格意義上看待這條語句,它沒有實現分組的意義:既然不返回分組列的分組結果,那為什麼還要進行分組呢?

其實,無論是標準SQL還是MySQL、mariadb,執行group by子句時都會表掃描並創建一個臨時表(此處為了說明group by的特性,不考慮group by使用索引優化的情況),這個臨時表中只有group by的分組列,沒有那些非分組列。這也是前面說group by之後,關係引擎的目光從行轉為組的真正原因。由此,已經足夠說明為什麼select_list中不能使用非group by的分組列。

1.10 關於MySQL/MariaDB的GROUP BY

MySQL和mariadb的GROUP BY有幾個擴展特性(都是標準SQL不支持的):(1).能夠在group by中使用列別名;(2).可以在select_list中使用非分組列;(3).可以在group by子句中指定分組列的升序和降序排序下麵分別說明這些特性。

(1).group by中能夠使用列別名。

其實對於MySQL和mariadb而言,並非是有一個專門的select_list篩選過程,使得篩選完成後,後續的步驟就能使用這些篩選出來的列。而是從WHERE子句篩選了行之後,後面所有的過程都可以對select_list進行檢索掃描。其中ORDER BY子句掃描select_list的時候是先檢索出列表達式,再檢索所引用表中的列,直到找出所有的排序列;而GROUP BY和HAVING子句則是先檢索表中的列,再檢索列表達式,直到找出所有的分組列。因此,MySQL、mariadb能夠使用列別名。

下麵兩個查詢的例子很能說明問題:

MariaDB [test]> set @a:=0;select sid,name,class,@a:=@a+1 as class from Student order by class;
+------+----------+--------+-------+
| sid  | name     | class  | class |
+------+----------+--------+------

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

-Advertisement-
Play Games
更多相關文章
  • 在ORACLE資料庫的管理、維護過程中,偶爾會遇到歸檔日誌暴增的情況,也就是說一些SQL語句產生了大量的redo log,那麼如何跟蹤、定位哪些SQL語句生成了大量的redo log日誌呢? 下麵這篇文章結合實際案例和官方文檔“How to identify the causes of High R... ...
  • 狀態不是很穩定,晚上把資料庫經常性的無法啟動解決了,只能說是找到解決的辦法了吧。 開始的時候mysql根本無法運行起來。。 查了百度才知道,要把任務管理器對的mysqld進城結束掉,再用 --console啟動mysqld.exe 反正最後終於是登錄進去了。。 但是服務依舊是啟動,而不是像其他服務一 ...
  • 三範式定義 1NF:每個數據項都是最小單元,不可分割,其實就是確定行列之後只能對應一個數據。 2NF:每一個非主屬性完全依賴於候選碼(屬性組的值能唯一的標識一個元組,但是其子集不可以)。 3NF:每一個非主屬性既不傳遞依賴於主碼,也不部分依賴於主碼。 BCNF:主屬性(候選碼中的某一個屬性)內部也 ...
  • 直入主題 1.首先註冊ngrok,地址是:https://dashboard.ngrok.com/user/signup 。關於怎麼註冊不是本次的重點,所以在這裡就不多說,百度上也有很多教程。 2.接著獲取密鑰, 註冊後會自動分配一個密鑰,查看密鑰地址:https://dashboard.ngrok ...
  • 基本情況: 系統:centos 7 mariadb安裝方式:yum 亂碼解決過程: 1. 查看當前資料庫編碼(登錄資料庫後) (上圖為已經配置成功) 2. 如果結果不為上圖則需要設置資料庫配置文件 編輯 /etc/my.cnf.d/client.cnf 文件,添加如下內容 [client] defa ...
  • 前言 編寫後臺管理系統,測試使用,發現載入數據很慢,平均1秒多。之後和同事一起查找原因,從後段代碼到前端, 最後問題是在資料庫到設計,表沒有主外鍵,之後我們把索引加上後,速度杠杠的... 資料庫索引是什麼 索引是對資料庫表中一列或多列的值進行排序的一種結構,使用索引可快速訪問資料庫表中的特定信息。 ...
  • [20180327]行遷移與ITL浪費.txt--//生產系統遇到的一個問題,增加一個欄位到表結構,修改數據字典,導致出現行遷移,而更加嚴重的是沒有修改pctfree值,--//以後的業務操作,依舊會導致大量的行遷移,不僅僅是操作時IO增加,而且還導致的問題ITL槽浪費,特別在密集的dml操作的-- ...
  • redhat7.2靜默安裝Oracle11.2.0.4我們將oracle安裝到/opt/oracle目錄下,後面的環境變數則以此為基礎。一、設置主機hosts映射vi /etc/hosts[plain] view plain copy 127.0.0.1 localhost 192.168.61.1 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...