MySQL 公用表達(dá)式的實(shí)現(xiàn)示例
公用表表達(dá)式和生成列是MySQL 8.x版本中新增的特性。本篇文章將簡單介紹MySQL中新增的公用表表達(dá)式和生成列。
公用表表達(dá)式
從MySQL 8.x版本開始支持公用表表達(dá)式(簡稱為CTE)。公用表表達(dá)式通過WITH語句實(shí)現(xiàn),可以分為非遞歸公用表表達(dá)式和遞歸公用表表達(dá)式。在常規(guī)的子查詢中,派生表無法被引用兩次,否則會引起MySQL的性能問題。如果使用CTE查詢的話,子查詢只會被引用一次,這也是使用CTE的一個(gè)重要原因。
非遞歸CTE
MySQL 8.0之前,想要進(jìn)行數(shù)據(jù)表的復(fù)雜查詢,需要借助子查詢語句實(shí)現(xiàn),但SQL語句的性能低下,而且子查詢的派生表不能被多次引用。CTE的出現(xiàn)極大地簡化了復(fù)雜SQL的編寫,提高了數(shù)據(jù)查詢的性能。
非遞歸CTE的語法格式如下:
WITH
cte_name [(col_name [, col_name] ...)] AS (subquery)
[, cte_name [(col_name [, col_name] ...)] AS (subquery)] …
SELECT [(col_name [, col_name] ...)] FROM cte_name;可以對比子查詢與CTE的查詢來加深對CTE的理解。
子查詢
例如:在MySQL命令行中執(zhí)行如下SQL語句來實(shí)現(xiàn)子查詢的效果。
mysql> SELECT * FROM (SELECT YEAR(NOW())) AS year; +-------------+ | YEAR(NOW()) | +-------------+ | 2025 | +-------------+ 1 row in set (0.01 sec)
上面的SQL語句使用子查詢獲取當(dāng)前年份的信息。
CTE查詢
使用CTE實(shí)現(xiàn)查詢的效果如下:
mysql> WITH year AS (SELECT YEAR(NOW())) SELECT * FROM year; +-------------+ | YEAR(NOW()) | +-------------+ | 2025 | +-------------+ 1 row in set (0.01 sec)
通過兩種查詢的SQL語句對比可以發(fā)現(xiàn),使用CTE查詢能夠使SQL語義更加清晰。
CTE定義多個(gè)字段
也可以在CTE語句中定義多個(gè)查詢字段,如下:
mysql> WITH cte_year_month (year, month) AS (SELECT YEAR(NOW()) AS year, MONTH(NOW()) AS month) SELECT * FROM cte_year_month; +------+-------+ | year | month | +------+-------+ | 2025 | 8 | +------+-------+ 1 row in set (0.02 sec)
重用上次查詢結(jié)果
CTE可以重用上次的查詢結(jié)果,多個(gè)CTE之間還可以相互引用:
mysql> WITH cte1(cte1_year, cte1_month) AS (SELECT YEAR(NOW()) AS cte1_year, MONTH(NOW()) AS cte1_month), cte2(cte2_year, cte2_month) AS (SELECT (cte1_year+1) AS cte2_year, (cte1_month + 1) AS cte2_month FROM cte1) SELECT * FROM cte1 JOIN cte2; +-----------+------------+-----------+------------+ | cte1_year | cte1_month | cte2_year | cte2_month | +-----------+------------+-----------+------------+ | 2025 | 8 | 2026 | 9 | +-----------+------------+-----------+------------+ 1 row in set (0.01 sec)
上面的SQL語句中,在cte2的定義中引用了cte1。
注意:在SQL語句中定義多個(gè)CTE時(shí),每個(gè)CTE之間需要用逗號進(jìn)行分隔。
遞歸CTE
遞歸CTE的子查詢可以引用自身,相比非遞歸CTE的語法格式多一個(gè)關(guān)鍵字RECURSIVE。
WITH RECURSIVE
cte_name [(col_name [, col_name] ...)] AS (subquery)
[, cte_name [(col_name [, col_name] ...)] AS (subquery)] …
SELECT [(col_name [, col_name] ...)] FROM cte_name;遞歸CTE子查詢類型
在遞歸CTE中,子查詢包含兩種:
種子查詢:種子查詢會初始化查詢數(shù)據(jù),并在查詢中不會引用自身,
遞歸查詢:遞歸查詢是在種子查詢的基礎(chǔ)上,根據(jù)一定的規(guī)則引用自身的查詢。
這兩個(gè)查詢之間會通過UNION、UNION ALL或者UNION DISTINCT語句連接起來。
例如:使用遞歸CTE在MySQL命令行中輸出1~8的序列。
mysql> WITH RECURSIVE cte_num(num) AS ( SELECT 1 UNION ALL SELECT num + 1 FROM cte_num WHERE num < 8 ) SELECT * FROM cte_num; +-----+ | num | +-----+ | 1 | | 2 | | 3 | | 4 | | 5 | | 6 | | 7 | | 8 | +-----+ 8 rows in set (0.02 sec)
遞歸CTE查詢對于遍歷有組織、有層級關(guān)系的數(shù)據(jù)時(shí)非常方便。
例如,創(chuàng)建一張區(qū)域數(shù)據(jù)表t_area,該數(shù)據(jù)表中包含省市區(qū)信息。
mysql> CREATE TABLE t_area( id INT NOT NULL, name VARCHAR(30), pid INT ); Query OK, 0 rows affected (0.02 sec)
向t_area數(shù)據(jù)表中插入測試數(shù)據(jù)。
mysql> INSERT INTO t_area (id, name, pid) VALUES (1, '河北省', NULL), (2, '邯鄲市', 1), (3, '邯山區(qū)', 2), (4, '復(fù)興區(qū)', 2), (5, '河南省', NULL), (6, '鄭州市', 5), (7, '中原區(qū)', 6); Query OK, 7 rows affected (0.01 sec) Records: 7 Duplicates: 0 Warnings: 0
SQL語句執(zhí)行成功,查詢t_area數(shù)據(jù)表中的數(shù)據(jù)。
mysql> SELECT * FROM t_area; +----+--------+------+ | id | name | pid | +----+--------+------+ | 1 | 河北省 | NULL | | 2 | 邯鄲市 | 1 | | 3 | 邯山區(qū) | 2 | | 4 | 復(fù)興區(qū) | 2 | | 5 | 河南省 | NULL | | 6 | 鄭州市 | 5 | | 7 | 中原區(qū) | 6 | +----+--------+------+ 7 rows in set (0.03 sec)
接下來,使用遞歸CTE查詢t_area數(shù)據(jù)表中的層級關(guān)系。
mysql> WITH RECURSIVE area_depth(id, name, path) AS ( SELECT id, name, CAST(id AS CHAR(300)) FROM t_area WHERE pid IS NULL UNION ALL SELECT a.id, a.name, CONCAT(ad.path, '->', a.id) FROM area_depth AS ad JOIN t_area AS a ON ad.id = a.pid ) SELECT * FROM area_depth ORDER BY path; +----+--------+---------+ | id | name | path | +----+--------+---------+ | 1 | 河北省 | 1 | | 2 | 邯鄲市 | 1->2 | | 3 | 邯山區(qū) | 1->2->3 | | 4 | 復(fù)興區(qū) | 1->2->4 | | 5 | 河南省 | 5 | | 6 | 鄭州市 | 5->6 | | 7 | 中原區(qū) | 5->6->7 | +----+--------+---------+ 7 rows in set (0.02 sec)
其中,path列表示查詢出的每條數(shù)據(jù)的層級關(guān)系。
遞歸CTE的限制
遞歸CTE的查詢語句中需要包含一個(gè)終止遞歸查詢的條件。
當(dāng)由于某種原因在遞歸CTE的查詢語句中未設(shè)置終止條件時(shí),
MySQL會根據(jù)相應(yīng)的配置信息,自動終止查詢并拋出相應(yīng)的錯(cuò)誤信息。
終止遞歸CTE配置項(xiàng)
在MySQL中默認(rèn)提供了如下兩個(gè)配置項(xiàng)來終止遞歸CTE。
cte_max_recursion_depth:如果在定義遞歸CTE時(shí)沒有設(shè)置遞歸終止條件,當(dāng)達(dá)到此參數(shù)設(shè)置的執(zhí)行次數(shù)后,MySQL報(bào)錯(cuò)。
max_execution_time:表示SQL語句執(zhí)行的最長毫秒時(shí)間,當(dāng)SQL語句的執(zhí)行時(shí)間超過此參數(shù)設(shè)置的值時(shí),MySQL報(bào)錯(cuò)。
例如:未設(shè)置查詢終止條件的遞歸CTE, MySQL會拋出錯(cuò)誤信息并終止查詢。
mysql> WITH RECURSIVE cte_num (n) AS ( SELECT 1 UNION ALL SELECT n+1 FROM cte_num ) SELECT * FROM cte_num; Recursive query aborted after 1001 iterations. Try increasing @@cte_max_recursion_depth to a larger value.
從輸出結(jié)果可以看出,當(dāng)沒有為遞歸CTE設(shè)置終止條件時(shí),MySQL默認(rèn)會在第1001次查詢時(shí)拋出錯(cuò)誤信息并終止查詢。
查看cte_max_recursion_depth
查看cte_max_recursion_depth參數(shù)的默認(rèn)值。
mysql> SHOW VARIABLES LIKE 'cte_max%'; +-------------------------+-------+ | Variable_name | Value | +-------------------------+-------+ | cte_max_recursion_depth | 1000 | +-------------------------+-------+ 1 row in set (0.02 sec)
結(jié)果顯示,cte_max_recursion_depth參數(shù)的默認(rèn)值為1000,所以MySQL會在第1001次查詢時(shí)拋出錯(cuò)誤并終止查詢。
設(shè)置cte_max_recursion_depth
接下來,驗(yàn)證MySQL是如何根據(jù)max_execution_time配置項(xiàng)終止遞歸CTE。
首先,為了演示max_execution_time參數(shù)的限制,
需要將cte_max_recursion_depth參數(shù)設(shè)置為一個(gè)很大的數(shù)字,
這里在MySQL會話級別中設(shè)置。
mysql> SET SESSION cte_max_recursion_depth=999999999; Query OK, 0 rows affected (0.00 sec) mysql> SHOW VARIABLES LIKE 'cte_max%'; +-------------------------+-----------+ | Variable_name | Value | +-------------------------+-----------+ | cte_max_recursion_depth | 999999999 | +-------------------------+-----------+ 1 row in set (0.02 sec)
已經(jīng)成功將cte_max_recursion_depth參數(shù)設(shè)置為999999999。
查看max_execution_time
查看MySQL中max_execution_time參數(shù)的默認(rèn)值。
mysql> SHOW VARIABLES LIKE 'max_execution%'; +--------------------+-------+ | Variable_name | Value | +--------------------+-------+ | max_execution_time | 0 | +--------------------+-------+ 1 row in set (0.00 sec)
在MySQL中max_execution_time參數(shù)的值為毫秒值,默認(rèn)為0,也就是沒有限制。
設(shè)置max_execution_time
在MySQL會話級別將max_execution_time的值設(shè)置為1s。
mysql> SET SESSION max_execution_time=1000; Query OK, 0 rows affected (0.00 sec) mysql> SHOW VARIABLES LIKE 'max_execution%'; +--------------------+-------+ | Variable_name | Value | +--------------------+-------+ | max_execution_time | 1000 | +--------------------+-------+ 1 row in set (0.02 sec)
已經(jīng)成功將max_execution_time的值設(shè)置為1s。
當(dāng)SQL語句的執(zhí)行時(shí)間超過max_execution_time設(shè)置的值時(shí),MySQL報(bào)錯(cuò)。
mysql> WITH RECURSIVE cte(n) AS ( SELECT 1 UNION ALL SELECT n+1 FROM CTE ) SELECT * FROM cte; Query execution was interrupted, maximum statement execution time exceeded
MySQL提供的終止遞歸的機(jī)制(cte_max_recursion_depth和max_execution_time),有效地預(yù)防了無限遞歸的問題。
注意:雖然MySQL默認(rèn)提供了終止遞歸的機(jī)制,但是在使用MySQL的遞歸CTE時(shí),建議還是根據(jù)實(shí)際的需求,在CTE的SQL語句中明確設(shè)置遞歸終止的條件。
另外,CTE支持SELECT/INSERT/UPDATE/DELETE等語句,這里只演示了SELECT語句,其他語句可以自行實(shí)現(xiàn)。
生成列
MySQL中生成列的值是根據(jù)數(shù)據(jù)表中定義列時(shí)指定的表達(dá)式計(jì)算得出的,主要包含兩種類型:VIRSUAL生成列和SORTED生成列,其中VIRSUAL生成列是從數(shù)據(jù)表中查詢記錄時(shí),計(jì)算該列的值;SORTED生成列是向數(shù)據(jù)表中寫入記錄時(shí),計(jì)算該列的值并將計(jì)算的結(jié)果數(shù)據(jù)作為常規(guī)列存儲在數(shù)據(jù)表中。
通常,使用的比較多的是VIRSUAL生成列,原因是VIRSUAL生成列不占用存儲空間。
創(chuàng)建表時(shí)指定生成列
例如,創(chuàng)建數(shù)據(jù)表t_genearted_column,數(shù)據(jù)表中包含DOUBLE類型的字段a、b和c,其中c字段是由a字段和b字段計(jì)算得出的,如下:
mysql> CREATE TABLE t_genearted_column( a DOUBLE, b DOUBLE, c DOUBLE AS (a * a + b * b) ); Query OK, 0 rows affected (0.07 sec)
向t_genearted_column數(shù)據(jù)表中插入數(shù)據(jù)。
mysql> INSERT INTO t_genearted_column (a, b) VALUES (1, 1), (2, 2), (3, 3); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0
查詢t_genearted_column數(shù)據(jù)表中的數(shù)據(jù)。
mysql> SELECT * FROM t_genearted_column; +---+---+----+ | a | b | c | +---+---+----+ | 1 | 1 | 2 | | 2 | 2 | 8 | | 3 | 3 | 18 | +---+---+----+ 3 rows in set (0.02 sec)
結(jié)果顯示,在向t_genearted_column數(shù)據(jù)表中插入數(shù)據(jù)時(shí),并沒有向c字段中插入數(shù)據(jù),
c字段的值是由a字段的值和b字段的值計(jì)算得出的。
如果在向t_genearted_column數(shù)據(jù)表插入數(shù)據(jù)時(shí)包含c字段,則向c字段插入數(shù)據(jù)時(shí),必須使用DEFAULT,否則MySQL報(bào)錯(cuò)。
mysql> INSERT INTO t_genearted_column (a, b, c) VALUES (4, 4, 32); 3105 - The value specified for generated column 'c' in table 't_genearted_column' is not allowed.
MySQL報(bào)錯(cuò),報(bào)錯(cuò)信息為不能為生成的列手動賦值。
使用DEFAULT關(guān)鍵字代替具體的值。
mysql> INSERT INTO t_genearted_column (a, b, c) VALUES (4, 4, DEFAULT); Query OK, 1 row affected (0.00 sec)
SQL語句執(zhí)行成功,查詢t_genearted_column數(shù)據(jù)表中的數(shù)據(jù)。
mysql> SELECT * FROM t_genearted_column; +------+------+------+ | a | b | c | +------+------+------+ | 1 | 1 | 2 | | 2 | 2 | 8 | | 3 | 3 | 18 | | 4 | 4 | 32 | +------+------+------+ 4 rows in set (0.00 sec)
已經(jīng)成功為c字段賦值。
也可以在創(chuàng)建表時(shí)明確指定VIRSUAL生成列。
mysql> CREATE TABLE t_column_virsual ( a DOUBLE, b DOUBLE, c DOUBLE GENERATED ALWAYS AS (a + b) VIRTUAL); Query OK, 0 rows affected (0.02 sec)
向t_column_virsual數(shù)據(jù)表中插入數(shù)據(jù)并查詢結(jié)果。
mysql> INSERT INTO t_column_virsual (a, b) VALUES (1, 1); Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM t_column_virsual; +---+---+---+ | a | b | c | +---+---+---+ | 1 | 1 | 2 | +---+---+---+ 1 row in set (0.02 sec)
為已有表添加生成列
可以使用ALTER TABLE ADD COLUMN語句為已有的數(shù)據(jù)表添加生成列。
例如:創(chuàng)建數(shù)據(jù)表t_add_column。
mysql> CREATE TABLE t_add_column( a DOUBLE, b DOUBLE ); Query OK, 0 rows affected (0.10 sec)
向數(shù)據(jù)表中插入數(shù)據(jù)。
mysql> INSERT INTO t_add_column (a, b) VALUES (2, 2); Query OK, 1 row affected (0.01 sec)
為t_add_column數(shù)據(jù)表添加生成列。
mysql> ALTER TABLE t_add_column ADD COLUMN c DOUBLE GENERATED ALWAYS AS(a * a + b * b) STORED; Query OK, 1 row affected (0.15 sec) Records: 1 Duplicates: 0 Warnings: 0
SQL語句執(zhí)行成功,查詢t_add_column數(shù)據(jù)表中的數(shù)據(jù)。
mysql> SELECT * FROM t_add_column; +---+---+---+ | a | b | c | +---+---+---+ | 2 | 2 | 8 | +---+---+---+ 1 row in set (0.02 sec)
結(jié)果:當(dāng)數(shù)據(jù)表中存在數(shù)據(jù)時(shí),為數(shù)據(jù)表添加生成列,會自動根據(jù)已有的數(shù)據(jù)計(jì)算該列的值,并存儲到該列中。
修改已有的生成列
例如:修改t_add_column數(shù)據(jù)表的生成列c,將其計(jì)算規(guī)則修改為a * b。
mysql> ALTER TABLE t_add_column MODIFY COLUMN c DOUBLE GENERATED ALWAYS AS (a * b) STORED; Query OK, 1 row affected (0.05 sec) Records: 1 Duplicates: 0 Warnings: 0
查詢t_add_column數(shù)據(jù)表中的數(shù)據(jù)。
mysql> SELECT * FROM t_add_column; +---+---+---+ | a | b | c | +---+---+---+ | 2 | 2 | 4 | +---+---+---+ 1 row in set (0.02 sec)
c列的值此時(shí)已經(jīng)被修改為a列的值乘以b列的值的結(jié)果數(shù)據(jù)。
刪除生成列
刪除生成列可以使用ALTER TABLE DROP COLUMN語句實(shí)現(xiàn)。
例如:刪除t_add_column數(shù)據(jù)表中的生成列c。
mysql> ALTER TABLE t_add_column DROP COLUMN c; Query OK, 1 row affected (0.10 sec) Records: 1 Duplicates: 0 Warnings: 0
SQL語句執(zhí)行成功,再次查看t_add_column數(shù)據(jù)表中的數(shù)據(jù)。
mysql> SELECT * FROM t_add_column; +---+---+ | a | b | +---+---+ | 2 | 2 | +---+---+ 1 row in set (0.02 sec)
結(jié)果:生成列c已經(jīng)被成功刪除。
總結(jié)
到此這篇關(guān)于MySQL 公用表達(dá)式的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)MySQL 公用表達(dá)式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決SQL文件導(dǎo)入MySQL數(shù)據(jù)庫1118錯(cuò)誤的問題
在使用Navicat導(dǎo)入SQL文件時(shí),有時(shí)會遇到報(bào)錯(cuò)問題,這通常與MySQL版本差異或嚴(yán)格模式設(shè)置有關(guān),若報(bào)錯(cuò)提示rowsize長度過長,可能是因?yàn)镸ySQL的嚴(yán)格模式開啟導(dǎo)致,解決方法是檢查嚴(yán)格模式是否開啟,若開啟則需關(guān)閉2024-10-10
數(shù)據(jù)庫索引的知識點(diǎn)整理小結(jié),你所需要了解的都在這兒了
這篇文章主要介紹了數(shù)據(jù)庫索引的知識點(diǎn)整理小結(jié),你所需要了解的都在這兒了,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
MySQL數(shù)據(jù)庫主從復(fù)制與讀寫分離
大家好,本篇文章主要講的是MySQL數(shù)據(jù)庫主從復(fù)制與讀寫分離,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12
如何解決局域網(wǎng)內(nèi)mysql數(shù)據(jù)庫連接慢
通過內(nèi)網(wǎng)連另外一臺機(jī)器的mysql服務(wù), 確發(fā)現(xiàn)速度N慢! 等了大約幾十秒才等到提示輸入密碼。非常急人,有沒有辦法可以解決局域網(wǎng)內(nèi)mysql數(shù)據(jù)庫連接慢呢?下面小編帶領(lǐng)大家來解決此問題,感興趣的朋友一起看看吧2015-09-09
MySQL?常見時(shí)間字段設(shè)置小結(jié)
本文詳細(xì)探討了MySQL中常見時(shí)間字段的設(shè)置和處理,包括DATETIME、DATE、TIME和TIMESTAMP等類型的介紹,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-11-11
MySQL統(tǒng)計(jì)高頻用戶實(shí)現(xiàn)方法詳解
這篇文章主要介紹了MySQL統(tǒng)計(jì)高頻用戶實(shí)現(xiàn)的相關(guān)資料,文中通過示例代碼講解了如何用SQL實(shí)現(xiàn)數(shù)據(jù)清洗、分組聚合與排序,識別高頻用戶,用于活躍度分析和異常檢測,需要的朋友可以參考下2025-05-05
MySQL中因一個(gè)雙引號錯(cuò)位引發(fā)的血案詳析
這篇文章主要給大家介紹了關(guān)于MySQL中因一個(gè)雙引號錯(cuò)位引發(fā)的血案的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11

