MySQL 公用表達(dá)式的實(shí)現(xiàn)示例
公用表表達(dá)式和生成列是MySQL 8.x版本中新增的特性。本篇文章將簡(jiǎn)單介紹MySQL中新增的公用表表達(dá)式和生成列。
公用表表達(dá)式
從MySQL 8.x版本開(kāi)始支持公用表表達(dá)式(簡(jiǎn)稱(chēng)為CTE)。公用表表達(dá)式通過(guò)WITH語(yǔ)句實(shí)現(xiàn),可以分為非遞歸公用表表達(dá)式和遞歸公用表表達(dá)式。在常規(guī)的子查詢(xún)中,派生表無(wú)法被引用兩次,否則會(huì)引起MySQL的性能問(wèn)題。如果使用CTE查詢(xún)的話,子查詢(xún)只會(huì)被引用一次,這也是使用CTE的一個(gè)重要原因。
非遞歸CTE
MySQL 8.0之前,想要進(jìn)行數(shù)據(jù)表的復(fù)雜查詢(xún),需要借助子查詢(xún)語(yǔ)句實(shí)現(xiàn),但SQL語(yǔ)句的性能低下,而且子查詢(xún)的派生表不能被多次引用。CTE的出現(xiàn)極大地簡(jiǎn)化了復(fù)雜SQL的編寫(xiě),提高了數(shù)據(jù)查詢(xún)的性能。
非遞歸CTE的語(yǔ)法格式如下:
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;可以對(duì)比子查詢(xún)與CTE的查詢(xún)來(lái)加深對(duì)CTE的理解。
子查詢(xún)
例如:在MySQL命令行中執(zhí)行如下SQL語(yǔ)句來(lái)實(shí)現(xiàn)子查詢(xún)的效果。
mysql> SELECT * FROM (SELECT YEAR(NOW())) AS year; +-------------+ | YEAR(NOW()) | +-------------+ | 2025 | +-------------+ 1 row in set (0.01 sec)
上面的SQL語(yǔ)句使用子查詢(xún)獲取當(dāng)前年份的信息。
CTE查詢(xún)
使用CTE實(shí)現(xiàn)查詢(xún)的效果如下:
mysql> WITH year AS (SELECT YEAR(NOW())) SELECT * FROM year; +-------------+ | YEAR(NOW()) | +-------------+ | 2025 | +-------------+ 1 row in set (0.01 sec)
通過(guò)兩種查詢(xún)的SQL語(yǔ)句對(duì)比可以發(fā)現(xiàn),使用CTE查詢(xún)能夠使SQL語(yǔ)義更加清晰。
CTE定義多個(gè)字段
也可以在CTE語(yǔ)句中定義多個(gè)查詢(xún)字段,如下:
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)
重用上次查詢(xún)結(jié)果
CTE可以重用上次的查詢(xún)結(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語(yǔ)句中,在cte2的定義中引用了cte1。
注意:在SQL語(yǔ)句中定義多個(gè)CTE時(shí),每個(gè)CTE之間需要用逗號(hào)進(jìn)行分隔。
遞歸CTE
遞歸CTE的子查詢(xún)可以引用自身,相比非遞歸CTE的語(yǔ)法格式多一個(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子查詢(xún)類(lèi)型
在遞歸CTE中,子查詢(xún)包含兩種:
種子查詢(xún):種子查詢(xún)會(huì)初始化查詢(xún)數(shù)據(jù),并在查詢(xún)中不會(huì)引用自身,
遞歸查詢(xún):遞歸查詢(xún)是在種子查詢(xún)的基礎(chǔ)上,根據(jù)一定的規(guī)則引用自身的查詢(xún)。
這兩個(gè)查詢(xún)之間會(huì)通過(guò)UNION、UNION ALL或者UNION DISTINCT語(yǔ)句連接起來(lái)。
例如:使用遞歸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查詢(xún)對(duì)于遍歷有組織、有層級(jí)關(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ù)表中插入測(cè)試數(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語(yǔ)句執(zhí)行成功,查詢(xún)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)
接下來(lái),使用遞歸CTE查詢(xún)t_area數(shù)據(jù)表中的層級(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列表示查詢(xún)出的每條數(shù)據(jù)的層級(jí)關(guān)系。
遞歸CTE的限制
遞歸CTE的查詢(xún)語(yǔ)句中需要包含一個(gè)終止遞歸查詢(xún)的條件。
當(dāng)由于某種原因在遞歸CTE的查詢(xún)語(yǔ)句中未設(shè)置終止條件時(shí),
MySQL會(huì)根據(jù)相應(yīng)的配置信息,自動(dòng)終止查詢(xún)并拋出相應(yīng)的錯(cuò)誤信息。
終止遞歸CTE配置項(xiàng)
在MySQL中默認(rèn)提供了如下兩個(gè)配置項(xiàng)來(lái)終止遞歸CTE。
cte_max_recursion_depth:如果在定義遞歸CTE時(shí)沒(méi)有設(shè)置遞歸終止條件,當(dāng)達(dá)到此參數(shù)設(shè)置的執(zhí)行次數(shù)后,MySQL報(bào)錯(cuò)。
max_execution_time:表示SQL語(yǔ)句執(zhí)行的最長(zhǎng)毫秒時(shí)間,當(dāng)SQL語(yǔ)句的執(zhí)行時(shí)間超過(guò)此參數(shù)設(shè)置的值時(shí),MySQL報(bào)錯(cuò)。
例如:未設(shè)置查詢(xún)終止條件的遞歸CTE, MySQL會(huì)拋出錯(cuò)誤信息并終止查詢(xún)。
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)沒(méi)有為遞歸CTE設(shè)置終止條件時(shí),MySQL默認(rèn)會(huì)在第1001次查詢(xún)時(shí)拋出錯(cuò)誤信息并終止查詢(xún)。
查看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會(huì)在第1001次查詢(xún)時(shí)拋出錯(cuò)誤并終止查詢(xún)。
設(shè)置cte_max_recursion_depth
接下來(lái),驗(yàn)證MySQL是如何根據(jù)max_execution_time配置項(xiàng)終止遞歸CTE。
首先,為了演示max_execution_time參數(shù)的限制,
需要將cte_max_recursion_depth參數(shù)設(shè)置為一個(gè)很大的數(shù)字,
這里在MySQL會(huì)話級(jí)別中設(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,也就是沒(méi)有限制。
設(shè)置max_execution_time
在MySQL會(huì)話級(jí)別將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語(yǔ)句的執(zhí)行時(shí)間超過(guò)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ù)防了無(wú)限遞歸的問(wèn)題。
注意:雖然MySQL默認(rèn)提供了終止遞歸的機(jī)制,但是在使用MySQL的遞歸CTE時(shí),建議還是根據(jù)實(shí)際的需求,在CTE的SQL語(yǔ)句中明確設(shè)置遞歸終止的條件。
另外,CTE支持SELECT/INSERT/UPDATE/DELETE等語(yǔ)句,這里只演示了SELECT語(yǔ)句,其他語(yǔ)句可以自行實(shí)現(xiàn)。
生成列
MySQL中生成列的值是根據(jù)數(shù)據(jù)表中定義列時(shí)指定的表達(dá)式計(jì)算得出的,主要包含兩種類(lèi)型:VIRSUAL生成列和SORTED生成列,其中VIRSUAL生成列是從數(shù)據(jù)表中查詢(xún)記錄時(shí),計(jì)算該列的值;SORTED生成列是向數(shù)據(jù)表中寫(xiě)入記錄時(shí),計(jì)算該列的值并將計(jì)算的結(jié)果數(shù)據(jù)作為常規(guī)列存儲(chǔ)在數(shù)據(jù)表中。
通常,使用的比較多的是VIRSUAL生成列,原因是VIRSUAL生成列不占用存儲(chǔ)空間。
創(chuàng)建表時(shí)指定生成列
例如,創(chuàng)建數(shù)據(jù)表t_genearted_column,數(shù)據(jù)表中包含DOUBLE類(lèi)型的字段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
查詢(xún)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í),并沒(méi)有向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ò)信息為不能為生成的列手動(dòng)賦值。
使用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語(yǔ)句執(zhí)行成功,查詢(xún)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ù)并查詢(xún)結(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語(yǔ)句為已有的數(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語(yǔ)句執(zhí)行成功,查詢(xún)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ù)表添加生成列,會(huì)自動(dòng)根據(jù)已有的數(shù)據(jù)計(jì)算該列的值,并存儲(chǔ)到該列中。
修改已有的生成列
例如:修改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
查詢(xún)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語(yǔ)句實(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語(yǔ)句執(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL基礎(chǔ)入門(mén)之Case語(yǔ)句用法實(shí)例
case語(yǔ)句是mysql中的一個(gè)條件語(yǔ)句,可以在字段中使用case語(yǔ)句進(jìn)行復(fù)雜的篩選以及構(gòu)造新的字段,下面這篇文章主要給大家介紹了關(guān)于MySQL基礎(chǔ)入門(mén)之Case語(yǔ)句用法的相關(guān)資料,需要的朋友可以參考下2022-08-08
Ubuntu?18.04.4安裝mysql的過(guò)程詳解?親測(cè)可用
這篇文章主要介紹了Ubuntu?18.04.4安裝mysql-親測(cè)可用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12
MySql?查詢(xún)符合條件的最新數(shù)據(jù)行
這篇文章主要介紹了MySql?怎么查出符合條件的最新的數(shù)據(jù)行,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
MySQL 創(chuàng)建多對(duì)多和一對(duì)一關(guān)系方法
這篇文章主要介紹了MySQL 創(chuàng)建多對(duì)多和一對(duì)一關(guān)系方法,文章舉例詳細(xì)說(shuō)明具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-03-03
安裝MySQL 5.7出現(xiàn)報(bào)錯(cuò):unknown variable ‘mysqlx_port
這篇文章主要介紹了安裝MySQL 5.7出現(xiàn)報(bào)錯(cuò):unknown variable ‘mysqlx_port=0.0‘的解決方法,文中通過(guò)圖文結(jié)合的方式介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-06-06
mysql order by limit 1和max的比較及說(shuō)明
使用ORDER BY + LIMIT 1和MAX查詢(xún)最大值時(shí),ORDER BY + LIMIT 1更快,因主鍵索引有序,ORDER BY在找到第一條數(shù)據(jù)后停止,而MAX直接通過(guò)索引獲取最大值,無(wú)需遍歷表,執(zhí)行計(jì)劃顯示前者使用索引排序,后者利用索引優(yōu)化直接返回結(jié)果2025-09-09
Ubuntu10下如何搭建MySQL Proxy讀寫(xiě)分離探討
MySQL Proxy是一個(gè)處于你的Client端和MySQL server端之間的簡(jiǎn)單程序,它可以監(jiān)測(cè)、分析或改變它們的通信2012-11-11

