源自MySQL 5.7 官方手冊:13.2.9 SELECT Syntax
SELECT的語法如下:
SELECT [ALL | DISTINCT | DISTINCTROW ] [HIGH_PRIORITY] [STRAIGHT_JOIN] [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT] [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS] select_expr [, select_expr ...] [FROM table_references [PARTITION partition_list] [WHERE where_condition] [GROUP BY {col_name | expr | position} [ASC | DESC], ... [WITH ROLLUP]] [HAVING where_condition] [ORDER BY {col_name | expr | position} [ASC | DESC], ...] [LIMIT {[offset,] row_count | row_count OFFSET offset}] [PROCEDURE procedure_name(argument_list)] [INTO OUTFILE 'file_name' [CHARACTER SET charset_name] export_options | INTO DUMPFILE 'file_name' | INTO var_name [, var_name]] [FOR UPDATE | LOCK IN SHARE MODE]]
一、SELECT解析
SELECT用於從一個或者多個表中取回數據行,也可以包括UNION語句和子查詢。UNION語句參考后續文章,而子查詢在手冊13.2.10節。
SELECT語句最常用的子句有這些:
- 每個select_expr指示要檢索的列。所以必須至少有一個select_expr。
- table_references指示要從中取回行數據的一個或多個表。其語法在第13.2.9.2節“JOIN語法”中描述。
- SELECT支持使用PARTITION顯式分區選擇,其中包含table_reference中表的名稱后面的分區或子分區(或兩者)列表。關於分區的更多信息在Section 22.6.4, “Partitioning and Locking”
- 如果存在WHERE子句,其中的條件對行數據進行篩選。where_condition是一個表達式,對於要選擇的每一行,其計算結果為true。如果沒有WHERE子句,該語句將選擇所有行。在WHERE表達式中,您可以使用除聚合函數之外的任何MySQL支持的函數和運算符。See Section 9.5, “Expressions”, and Chapter 12, Functions and Operators.
SELECT也可用於檢索計算的行而不引用任何表。
mysql> SELECT 1 + 1; -> 2
在沒有引用表的情況下,允許將DUAL指定為虛擬表名:
mysql> SELECT 1 + 1 FROM DUAL; -> 2
DUAL的存在只是為了方便。MySQL可能會忽略這些子句。如果沒有引用表,MySQL不需要FROM DUAL。
通常,使用的子句必須完全按照語法描述中展示的順序給出。
例如,HAVING子句必須位於任何GROUP BY子句之后和任何ORDER BY子句之前。例外情況是INTO子句可以如語法描述中所示出現,也可以緊跟在select_expr列表之后出現。SELECT...INTO后續文章會講。
select_expr項列表包括了要取回的列,該項可以指定一個字段、一個表達式或者使用*號。
- SELECT列表中只包含一個*號,即非限定*,意味着從所有表中取回所有的列。
- 而tb.*,限定*,表示取回一個指定表中的所有列。
- 在SELECT列表中使用非限定的*,可能會產生解析錯誤。要注意使用場合,
二、關於SELECT子句的一些附加知識:
2.1
可以使用AS alias_name為select_expr指定別名。別名用作表達式的列名,可用於GROUP BY,ORDER BY或HAVING子句。
AS關鍵字是可選項,在指定列的別名時養成使用AS是一種好習慣。
MySQL不允許在WHERE子句中引用列的別名,因為當WHERE子句執行時,列值可能還尚未確定。See Section B.4.4.4, “Problems with Column Aliases”.
2.2
FROM后的table_references指示參與查詢的一個或者多個表。如果列出了多個表,就會執行JOIN操作。而對於每一個指定表,都可以為其定義別名。
tbl_name [[AS] alias] [index_hint]
使用index_hint,會為優化器提供有關如何在查詢處理期間選擇索引的信息。關於它,see Section 8.9.4, “Index Hints”。
當然,也可以使用SET max_seeks_for_key = value作為替代方法,強制MySQL更優先使用鍵掃描而不是表掃描。See Section 5.1.7, “Server System Variables”
2.3
不使用AS也能為表定義別名,直接空格后填寫即可。
2.4
SELECT后被選擇的列,可以在ORDER IN和GROUP BY中,通過列名、列別名或者代表列位置的整數(從1開始)來引用:
SELECT college, region, seed FROM tournament ORDER BY region, seed; SELECT college, region AS r, seed AS s FROM tournament ORDER BY r, s; SELECT college, region, seed FROM tournament ORDER BY 2, 3;
如果ORDER BY出現在子查詢中,並且也應用於外部查詢,則最外層的ORDER BY優先。
例如,以下語句的結果按降序排序,而不是按升序排序:
(SELECT ... ORDER BY a) ORDER BY a DESC;
不推薦使用代表列位置的數字的方法,因為這已經從標准SQL中刪除。
2.5
如果使用了GROUP BY,被分組的列會自動按升序排列(就好像有一個ORDER BY語句后面跟了同樣的列)。如果要避免GROUP BY因為自動排序生成的開銷,添加ORDER BY NULL可以解決:
SELECT a, COUNT(b) FROM test_table GROUP BY a ORDER BY NULL;
雖然在GROUP BY語句上也有一些對字段指定排序的用法,但是他們現在都已經被拋棄。正確的做法是,使用ORDER BY子句來進行排序。
2.6
當使用ORDER BY或GROUP BY對SELECT中的列進行排序時,服務器僅使用max_sort_length系統變量指示的初始字節數對值進行排序。
2.7
MySQL擴展了GROUP BY的用法,它允許在Select列表中出現沒有在GROUP BY中進行分組的字段。這點在前面的聚合函數的文章中已經講得很清楚。
2.8
GROUP BY子句允許添加WITH ROLLUP修飾符
2.9
Having子句一般應用在最后,恰好在結果集被返回給MySQL客戶端前,且沒有進行優化。(而LIMIT應用在HAVING后)
SQL標准要求:HAVING必須引用在GROUP BY列表中或者聚合函數使用的列。然而,MySQL對此進行了擴展,它允許HAVING引用Select子句列表中的列,還有外部子查詢的列。
如果HAVING引用的列具有歧義,會有警告產生。下面的語句中,col2具有歧義:
SELECT COUNT(col1) AS col2 FROM t GROUP BY col2 HAVING col2 = 2;
這里沒報錯只有警告是為什么呢?因為MySQLS雖然對標准SQL進行了擴展,但是在這種情況下,標准SQL的選擇具有優先權。即MySQL把HAVING中的col2優先指向GROUP BY中的col2。
2.11
切記不要在該使用WHERE的地方使用HAVING。HAVING是和GROUP BY搭配的。
2.12
HAVING子句可以引用聚合函數,而WHERE不能。
SELECT user, MAX(salary) FROM users GROUP BY user HAVING MAX(salary) > 10;
上面的查詢在老版本的MySQL可能無效。
2.13
MySQL允許重復的列名。也就是說,同名的select_expr可以有多個。這是對標准SQL的擴展。而因為MySQL允許GROUP BY和HAVING引用select_expr值,這可能會引起歧義:
SELECT 12 AS a, a FROM t GROUP BY a;
在這個語句中,兩個列都名為a(實際應該是不同)。(着應該是被允許的)
那么為了不引起歧義以對正確的列進行分組,切記對select_expr使用不同的別名。
2.14
對於ORDER BY子句中的非限定列或別名引用,MySQL是這樣進行解析的:先搜索SELECT子句列表中的select_expr值,然后在FROM后的表中的列。
而對於GROUP BY和HAVING子句中的非限定列或別名,MySQL先搜索FROM子句,再搜索SELECT子句。
2.15
LIMIT子句可用於約束SELECT語句返回的行數。
LIMIT可以有一個或者兩個參數,都必須為非負整數。
但是也有例外情況:
- 在預編譯的語句中,LIMIT參數可以使用占位符標記——“?” 進行指定。
- 在存儲的程序(存儲過程?)中,可以使用整數值例程參數(integer-valued routine parameters)或局部變量指定LIMIT參數。
若LIMIT有兩個參數,第一個指定相對於第一行的偏移量,第二個參數指定應該返回的行數。第一行自己的偏移量是0,而不是1:
/*取回結果集中的6~15行*/
SELECT * FROM tbl LIMIT 5,10;
那如果要取回一個設定某個偏移量之后的所有行,可以為第二參數設定一個非常大的常量。以下查詢取回從第96行起的所有數據:
SELECT * FROM tbl LIMIT 95,18446744073709551615;
若LIMIT只有一個參數,則參數指定應該取回的行數,偏移量默認為0,即從第一行起。
對於預編譯SQL語句,可以使用占位符:
/*取回第一行數據*/
SET @a=1; PREPARE STMT FROM 'SELECT * FROM tbl LIMIT ?'; EXECUTE STMT USING @a; /*取回第2~6行數據*/
SET @skip=1; SET @numrows=5; PREPARE STMT FROM 'SELECT * FROM tbl LIMIT ?, ?'; EXECUTE STMT USING @skip, @numrows;
為了與PostgreSQL兼容,MySQL還支持LIMIT row_count OFFSET offset 的語法。
如果子查詢中有LIMIT,而外部的查詢同樣有LIMIT,此時最外層查詢的LIMIT優先。例如,以下語句返回的行應該是2條,而不是1條:
(SELECT ... LIMIT 1) LIMIT 2;
2.16
PROCEDURE子句命名了一個對結果集進行處理的存儲過程。Section 8.4.2.4, “Using PROCEDURE ANALYSE” 這一節,描述了ANALYSE關鍵字的使用。一個存儲過程,可用於獲取有助於減少表大小的最佳列數據類型的建議。(點進去才發現從MySQL 5.7.18開始,PROCEDURE ANALYZE()已被棄用,並在MySQL 8.0中被刪除。)
2.17
SELECT...INTO可以讓查詢結果寫入到文件或者保存到變量中,后續的文章會講這個。
2.18
如果使用在一個使用了頁級鎖或者行級鎖的存儲引擎中使用FOR UPDATE,被某個查詢所檢查的行將會處於“寫鎖定”中,直到當前事務結束。使用LOCK IN SHARE MODE設置共享鎖,允許其他事務讀取已檢查的行,但不允許更新或刪除它們。See Section 14.7.2.4, “Locking Reads”.
此外,你不能在下面的語句中將FOR UPDATE作為SELECT查詢的一部分:
CREATE TABLE new_table SELECT ... FROM old_table ....
如果嘗試這么做,該語句會被拒絕,並報錯——Can't update table 'old_table' while 'new_table' is being created。在MySQL 5.5以及更早的版本中有所不同。(This is a change in behavior from MySQL 5.5 and earlier, which permitted CREATE TABLE ... SELECT statements to make changes in tables other than the table being created.)
三、SELECT關鍵字的修飾符
緊跟SELECT關鍵字,你可以使用一些列修飾符,來影響語句的操作。
HIGH_PRIORITY,STRAIGHT_JOIN和以SQL_開頭的修飾符是標准SQL的MySQL擴展。
3.1
ALL和DISTINCT修飾符指定是否對結果集中的行(應該不是某個列)去重。
ALL是默認修飾符,即滿足要求的所有行都要被取回來。
DISTINCT刪除重復的行。
3.2
HIGH_PRIORITY修飾符會讓SELECT語句比更改表(這里英文用得是update,不曉得是更新還是更改)的語句有更高的優先級。
這個修飾符只應該在非常快且能被立即執行的查詢當中使用。
在表被鎖定以進行讀取時發出的SELECT HIGH_PRIORITY查詢也會運行,即使有一個更新語句正在等待表被釋放。這僅影響僅使用表級鎖定的存儲引擎(例如MyISAM,MEMORY和MERGE)。
HIGH_PRIORITY不能與屬於UNION的SELECT語句一起使用。
3.3
STRAIGHT_JOIN強制優化器按照FROM子句中列出的順序連接表。如果優化程序以非最佳順序連接表,則可以使用此方法加速查詢.STRAIGHT_JOIN也可以在table_references列表中使用。JOIN語法中會講到。
STRAIGHT_JOIN不適用於被優化器視為const或system的任何表。這種表只產生一行數據,在查詢執行的優化階段讀取,並且在查詢執行進行之前用適當的列值替換對其列的引用。這些表將首先出現在EXPLAIN顯示的查詢計划中。See Section 8.8.1, “Optimizing Queries with EXPLAIN”.
This exception may not apply to const or system tables that are used on the NULL-complemented side of an outer join (that is, the right-side table of a LEFT JOIN or the left-side table of a RIGHT JOIN.
3.4
SQL_BIG_RESULT或SQL_SMALL_RESULT可以與GROUP BY或DISTINCT一起使用,分別告訴優化器結果集有很多行或者很小。
對於SQL_BIG_RESULT,MySQL直接使用基於磁盤的臨時表(如果已創建),並且更偏向使用在GROUP BY中元素的key的臨時表來進行排序。
對於SQL_SMALL_RESULT,MySQL使用內存中的臨時表來存儲生成的表而不是使用排序。通常不需要這樣做。
3.5
SQL_BUFFER_RESULT強制將結果放入臨時表中.
這有助於MySQL盡早釋放表鎖,在需要很長時間將結果集發送到客戶端的情況下,這樣也更好。
此修飾符只能用於頂級SELECT語句,不能用於子查詢或UNION之后。
3.6
SQL_CALC_FOUND_ROWS告訴MySQL計算結果集中將有多少行,忽略任何LIMIT子句。
然后可以使用SELECT FOUND_ROWS()檢索行數。See Section 12.15, “Information Functions”.
這個參數就是為了方便統計在不使用LIMIT時的結果集。
3.7
SQL_CACHE和SQL_NO_CACHE修飾符會影響查詢緩存中查詢結果的緩存。(see Section 8.10.3, “The MySQL Query Cache”)
SQL_CACHE告訴MySQL將結果存儲在查詢緩存中(如果它是可緩存的,並且query_cache_type系統變量的值是2或DEMAND)。
使用SQL_NO_CACHE,服務器不使用查詢緩存。它既不檢查查詢緩存,也不檢查結果是否已緩存,也不緩存查詢結果。
這兩個修飾符是互斥的,如果同時指定它們則會發生錯誤。
此外,子查詢(包括FROM子句中的子查詢)和第一個SELECT以外的UNION中的SELECT語句不允許使用這些修飾符。
對於視圖,如果SQL_NO_CACHE出現在查詢中的任何SELECT中,則適用
For a cacheable query, SQL_CACHE applies if it appears in the first SELECT of a view referred to by the query.
從MySQL 5.7.20開始,不推薦使用查詢緩存,並在MySQL 8.0中刪除。棄用包括SQL_CACHE和SQL_NO_CACHE。
最后還有一個關於對分表查詢時鎖定情況因存儲引擎不同而不同的段落。
With SQL_NO_CACHE, the server does not use the query cache. It neither checks the query cache to see whether the result is already cached, nor does it cache the query result.
