CTE的语法如下:

以下图示来自

Non-recursive CTEs

Recursive CTEs
screenshot.png

CTE的使用

  • CTE使语句更加简洁

例如以下两个语句表达的是同一语义,使用CTE比未使用CTE的嵌套查询更简洁明了。

1) 使用嵌套子查询

  1. FROM
  2. (
  3. SELECT concat(cte2.txt, cte3.txt) as txt
  4. FROM
  5. (
  6. SELECT CONCAT(cte1.txt,'is a ') as txt
  7. FROM
  8. (
  9. SELECT 'This ' as txt
  10. ) as cte1
  11. ) as cte2,
  12. (
  13. SELECT 'nice query' as txt
  14. UNION
  15. SELECT 'query that rocks'
  16. UNION
  17. SELECT 'query'
  18. ) as cte3
  19. ) as cte4;

2) 使用CTE

  1. WITH cte1(txt) AS (SELECT "This "),
  2. cte2(txt) AS (SELECT CONCAT(cte1.txt,"is a ") FROM cte1),
  3. cte3(txt) AS (SELECT "nice query" UNION
  4. SELECT "query that rocks" UNION
  5. SELECT "query"),
  6. cte4(txt) AS (SELECT concat(cte2.txt, cte3.txt) FROM cte2, cte3)
  7. SELECT MAX(txt), MIN(txt) FROM cte4;
  • CTE 可以进行树形查询
    初始化这颗树

1) 层序遍历

  1. with recursive cte as (
  2. select id, value, 0 as level from t1 where parent_id is null
  3. union all
  4. select t1.id, t1.value, cte.level+1 from cte join t1 on t1.parent_id=cte.id)
  5. select * from cte;
  6. +------+-------+-------+
  7. | id | value | level |
  8. +------+-------+-------+
  9. | 1 | A | 0 |
  10. | 2 | B | 1 |
  11. | 3 | C | 1 |
  12. | 4 | D | 1 |
  13. | 5 | E | 2 |
  14. | 6 | F | 2 |
  15. | 7 | G | 2 |
  16. | 8 | H | 3 |
  17. +------+-------+-------+
  1. with recursive cte as (
  2. select id, value, 0 as level, CAST(id AS CHAR(200)) AS path from t1 where parent_id is null
  3. union all
  4. select t1.id, t1.value, cte.level+1, CONCAT(cte.path, ",", t1.id) from cte join t1 on t1.parent_id=cte.id)
  5. select * from cte order by path;
  6. +------+-------+-------+---------+
  7. | id | value | level | path |
  8. +------+-------+-------+---------+
  9. | 1 | A | 0 | 1 |
  10. | 2 | B | 1 | 1,2 |
  11. | 5 | E | 2 | 1,2,5 |
  12. | 6 | F | 2 | 1,2,6 |
  13. | 8 | H | 3 | 1,2,6,8 |
  14. | 4 | D | 1 | 1,4 |
  15. | 7 | G | 2 | 1,4,7 |
  16. +------+-------+-------+---------+

Oracle从9.2才开始支持CTE, 但只支持non-recursive with, 直到Oracle 11.2才完全支持CTE。但oracle 之前就支持connect by 的树形查询,recursive with 语句可以与connect by语句相互转化。 一些相互转化案例可以参考这里.

Oracle recursive with 语句不需要指定recursive关键字,可以自动识别是否recursive.

Oracle 还支持CTE相关的hint,

“MATERIALIZE”告诉优化器产生一个全局的临时表保存结果,多次引用CTE时直接访问临时表即可。而”INLINE”则表示每次需要解析查询CTE。

PostgreSQL

PostgreSQL从8.4开始支持CTE,PostgreSQL还扩展了CTE的功能, CTE的query中支持DML语句,例如

  1. create table t1 (c1 int, c2 char(10));
  2. insert into t1 values(1,'a'),(2,'b');
  3. c1 | c2
  4. ----+----
  5. 1 | a
  6. 2 | b
  7. WITH cte AS (
  8. UPDATE t1 SET c1= c1 * 2 where c1=1
  9. RETURNING *
  10. )
  11. SELECT * FROM cte; //返回更新的值
  12. c1 | c2
  13. ----+------------
  14. 2 | a
  15. truncate table t1;
  16. insert into t1 values(1,'a'),(2,'b');
  17. WITH cte AS (
  18. UPDATE t1 SET c1= c1 * 2 where c1=1
  19. RETURNING *
  20. )
  21. SELECT * FROM t1;//返回原值
  22. c1 | c2
  23. ----+------------
  24. 1 | a
  25. 2 | b
  26. truncate table t1;
  27. insert into t1 values(1,'a'),(2,'b');
  28. WITH cte AS (
  29. DELETE FROM t1
  30. WHERE c1=1
  31. RETURNING *
  32. )
  33. SELECT * FROM cte;//返回删除的行
  34. c1 | c2
  35. ----+------------
  36. 1 | a
  37. truncate table t1;
  38. insert into t1 values(1,'a'),(2,'b');
  39. WITH cte AS (
  40. DELETE FROM t1
  41. WHERE c1=1
  42. RETURNING *
  43. )
  44. SELECT * FROM t1;//返回原值
  45. c1 | c2
  46. ----+------------
  47. 1 | a
  48. 2 | b
  49. (2 rows)

MariaDB从10.2开始支持CTE。10.2.1 支持non-recursive CTE, 10.2.2开始支持recursive CTE。 目前的GA的版本是10.1.

MySQL

MySQL从8.0开始支持完整的CTE。MySQL8.0还在development 阶段,RC都没有,GA还需时日。

AliSQL基于mariadb10.2, port了no-recursive CTE的实现,此功能近期会上线。

//解析识别with table引用
find_table_def_in_with_clauses

//检查依赖关系,比如不能重复定义with table名字
With_clause::check_dependencies

// 为每个引用clone一份定义
With_element::clone_parsed_spec

//替换with table指定的列名
With_element::rename_columns_of_derived_unit

此实现对于多次引用CTE,CTE会解析多次,因此此版本CTE有简化SQL的作用,但效率上没有效提高。

  1. select count(*) from t1 where c2 !='z';
  2. +----------+
  3. | count(*) |
  4. +----------+
  5. | 65536 |
  6. 1 row in set (0.25 sec)
  7. with t as (select count(*) from t1 where c2 !='z')
  8. select * from t union select * from t union select * from t;
  9. +----------+
  10. | count(*) |
  11. +----------+
  12. | 65536 |
  13. +----------+
  14. 1 row in set (0.59 sec)
  15. select count(*) from t1 where c2 !='z'
  16. union
  17. select count(*) from t1 where c2 !='z'
  18. union
  19. select count(*) from t1 where c2 !='z';
  20. +----------+
  21. | count(*) |
  22. +----------+
  23. | 65536 |
  24. +----------+
  25. 1 row in set (0.57 sec)
  26. explain with t as (select count(*) from t1 where c2 !='z')
  27. -> select * from t union select * from t union select * from t;
  28. +------+-----------------+--------------+------+---------------+------+---------+------+-------+-------------+
  29. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  30. +------+-----------------+--------------+------+---------------+------+---------+------+-------+-------------+
  31. | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 65536 | |
  32. | 2 | SUBQUERY | t1 | ALL | NULL | NULL | NULL | NULL | 65536 | Using where |
  33. | 3 | RECURSIVE UNION | <derived5> | ALL | NULL | NULL | NULL | NULL | 65536 | |
  34. | 5 | SUBQUERY | t1 | ALL | NULL | NULL | NULL | NULL | 65536 | Using where |
  35. | 4 | RECURSIVE UNION | <derived6> | ALL | NULL | NULL | NULL | NULL | 65536 | |
  36. | 6 | SUBQUERY | t1 | ALL | NULL | NULL | NULL | NULL | 65536 | Using where |
  37. | NULL | UNION RESULT | <union1,3,4> | ALL | NULL | NULL | NULL | NULL | NULL | |
  38. +------+-----------------+--------------+------+---------------+------+---------+------+-------+-------------+
  39. 7 rows in set (0.00 sec)
  40. explain select count(*) from t1 where c2 !='z'
  41. union
  42. select count(*) from t1 where c2 !='z'
  43. union
  44. select count(*) from t1 where c2 !='z';
  45. +------+--------------+--------------+------+---------------+------+---------+------+-------+-------------+
  46. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  47. +------+--------------+--------------+------+---------------+------+---------+------+-------+-------------+
  48. | 1 | PRIMARY | t1 | ALL | NULL | NULL | NULL | NULL | 65536 | Using where |
  49. | 2 | UNION | t1 | ALL | NULL | NULL | NULL | NULL | 65536 | Using where |
  50. | 3 | UNION | t1 | ALL | NULL | NULL | NULL | NULL | 65536 | Using where |
  51. | NULL | UNION RESULT | <union1,2,3> | ALL | NULL | NULL | NULL | NULL | NULL | |
  52. +------+--------------+--------------+------+---------------+------+---------+------+-------+-------------+
  53. 4 rows in set (0.00 sec)

以下是MySQL8.0 只扫描一次的执行计划

以下是PostgreSQL9.4 只扫描一次的执行计划

  1. postgres=# explain with t as (select count(*) from t1 where c2 !='z')
  2. postgres-# select * from t union select * from t union select * from t;
  3. HashAggregate (cost=391366.28..391366.31 rows=3 width=8)
  4. Group Key: t.count
  5. CTE t
  6. -> Aggregate (cost=391366.17..391366.18 rows=1 width=0)
  7. -> Seq Scan on t1 (cost=0.00..384392.81 rows=2789345 width=0)
  8. Filter: ((c2)::text <> 'z'::text)
  9. -> Append (cost=0.00..0.09 rows=3 width=8)
  10. -> CTE Scan on t (cost=0.00..0.02 rows=1 width=8)
  11. -> CTE Scan on t t_1 (cost=0.00..0.02 rows=1 width=8)
  12. -> CTE Scan on t t_2 (cost=0.00..0.02 rows=1 width=8)

AliSQL还有待改进。