关键字:SQL,CTE,递归查询

概述:通常递归查询是一个有难度的话题,尽管如此,它们仍使您能够完成在 SQL 中无法实现的操作。本文通过示例进行了简单介绍,并展示了与 PL/SQL的递归查询实现的差异。

一、公用表表达式(WITH子句)

公用表表达式(CTE)可以被看作是一个视图,只适用于一个单一的查询:

  1. WITH ctename AS (
  2. SELECT ...
  3. )
  4. SELECT ...
  5. FROM ctename ...

这也可以写成子查询,但使用 CTE 有一些优点:

  • 查询变得更具可读性。
  • 您可以在查询中多次引用 CTE,并且只会计算一次。
  • 您可以在 CTE 中使用数据修改语句(通常带有RETURNING子句)。

请注意,在 V8R3 ,总是物化 CTE。这意味着,CTE 是独立于包含查询计算的。从 V8R6 开始,CTE 可以“内联”到查询中,这提供了进一步的优化潜力。

二、递归查询的语法

递归查询是使用递归 CTE编写的,即包含RECURSIVE关键字的CTE :

  1. WITH RECURSIVE ctename AS (
  2. SELECT /* non-recursive branch, cannot reference "ctename" */
  3. UNION [ALL]
  4. SELECT /* recursive branch referencing "ctename" */
  5. )
  6. SELECT ...
  7. FROM ctename ...

三、如何处理递归查询

KingbaseES内部使用 WorkTable 来处理递归 CTE。这种处理并不是真正的递归,而是迭代:

  • 首先,通过执行 CTE 的非递归分支来初始化WorkTable 。CTE 的结果也用这个结果集初始化。如果递归 CTE 使用UNION而不是UNION ALL,则删除重复的行。
  • 然后,KingbaseES重复以下操作,直到WorkTable 为空:
    • 评估 CTE 的递归分支,用WorkTable 替换对 CTE 的引用。
    • 将所有结果行添加到 CTE 结果。如果UNION用于合并分支,则丢弃重复的行。
    • 用上一步中的所有新行替换WorkTable (不包括任何已删除的重复行)。

请注意,到目前为止,CTE的自引用分支并未使用完整的 CTE 结果执行,而是仅使用自上次迭代(WorkTable )以来的新行。

必须意识到这里无限循环的危险:如果迭代永远不会结束,查询将一直运行直到结果表变得足够大以导致错误。有两种方法可以处理:

  • 通常,您可以通过使用 UNION来避免无限递归,这会删除重复的结果行(但当然需要额外的处理工作)。
  • 另一种方法是LIMIT在使用 CTE 的查询上放置一个子句,因为如果递归 CTE 计算的行数与父查询获取的行数一样多,KingbaseES将停止处理。请注意,此技术不可移植到其他符合标准的数据库。

请看实际执行计划:

  1. test=# explain WITH RECURSIVE ctename AS (
  2. test(# SELECT empno, ename
  3. test(# FROM emp
  4. test(# WHERE empno = 7566
  5. test(# UNION ALL
  6. test(# SELECT emp.empno, emp.ename
  7. test(# FROM emp JOIN ctename ON emp.mgr = ctename.empno
  8. test(# )
  9. test-# SELECT * FROM ctename;
  10.  
  11. --------------------------------------------------------------------------------------------------
  12. CTE Scan on ctename (cost=417.62..489.74 rows=3606 width=36)
  13. CTE ctename
  14. -> Recursive Union (cost=0.00..417.62 rows=3606 width=36)
  15. -> Seq Scan on emp (cost=0.00..25.00 rows=6 width=36)
  16. Filter: (empno = 7566)
  17. -> Hash Join (cost=1.95..32.05 rows=360 width=36)
  18. Hash Cond: (emp_1.mgr = ctename_1.empno)
  19. -> Seq Scan on emp emp_1 (cost=0.00..22.00 rows=1200 width=40)
  20. -> Hash (cost=1.20..1.20 rows=60 width=4)
  21. -> WorkTable Scan on ctename ctename_1 (cost=0.00..1.20 rows=60 width=4)

四、一个简单的例子

让我们假设一个像这样的自引用表

  1. TABLE emp;
  2.  
  3. empno | ename | job | mgr | hiredate | sal | comm | deptno
  4. -------+--------+-----------+------+------------+---------+---------+--------
  5. 7839 | KING | PRESIDENT | | 1981-11-17 | 5000.00 | | 10
  6. 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | | 30
  7. 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | | 10
  8. 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | | 20
  9. 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | | 20
  10. 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | | 20
  11. 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30
  12. 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30
  13. 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30
  14. 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30
  15. 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | | 30
  16. 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | | 10
  17. (12 rows)

我们要查找人员 7566 的所有下属,包括人员本身。查询的非递归分支将是:

  1. SELECT empno, ename
  2. FROM emp
  3. WHERE empno = 7566;

递归分支会找到WorkTable中所有条目的所有下级:

  1. SELECT emp.empno, emp.ename
  2. FROM emp JOIN ctename ON emp.mgr = ctename.empno;

可以假设依赖项不包含循环(没有人是他或她自己的经理,直接或间接)。所以可以将查询与 UNION ALL 结合起来,因为不会发生重复。所以完整查询将是:

  1. WITH RECURSIVE ctename AS (
  2. SELECT empno, ename
  3. FROM emp
  4. WHERE empno = 7566
  5. UNION ALL
  6. SELECT emp.empno, emp.ename
  7. FROM emp JOIN ctename ON emp.mgr = ctename.empno
  8. )
  9. SELECT * FROM ctename;
  10.  
  11. empno | ename
  12. -------+-------
  13. 7566 | JONES
  14. 7902 | FORD
  15. 7369 | SMITH
  16. (3 rows)

五、添加生成的列

有时您想添加更多信息,例如层级。您可以通过将起始级别添加为非递归分支中的常量来实现。在递归分支中,您只需将 1 添加到级别:

  1. WITH RECURSIVE ctename AS (
  2. SELECT empno, ename,
  3. 0 AS level
  4. FROM emp
  5. WHERE empno = 7566
  6. UNION ALL
  7. SELECT emp.empno, emp.ename,
  8. ctename.level + 1
  9. FROM emp
  10. JOIN ctename ON emp.mgr = ctename.empno
  11. )
  12. SELECT * FROM ctename;
  13.  
  14. empno | ename | level
  15. -------+-------+-------
  16. 7566 | JONES | 0
  17. 7902 | FORD | 1
  18. 7369 | SMITH | 2
  19. (3 rows)

如果UNION在循环引用的情况下使用避免重复行,则不能使用此技术。这是因为添加level会使之前相同的行不同。但在那种情况下,分层级别无论如何都没有多大意义,因为一个条目可能出现在无限多个级别上。

另一个常见的要求是收集“路径”中的所有祖先:

  1. WITH RECURSIVE ctename AS (
  2. SELECT empno, ename,
  3. ename AS path
  4. FROM emp
  5. WHERE empno = 7566
  6. UNION ALL
  7. SELECT emp.empno, emp.ename,
  8. ctename.path || ' -> ' || emp.ename
  9. FROM emp
  10. JOIN ctename ON emp.mgr = ctename.empno
  11. )
  12. SELECT * FROM ctename;
  13.  
  14. empno | ename | path
  15. -------+-------+------------------------
  16. 7566 | JONES | JONES
  17. 7902 | FORD | JONES -> FORD
  18. 7369 | SMITH | JONES -> FORD -> SMITH

六、与 PLSQL 的比较

PLSQL对于不符合 SQL 标准的递归查询有不同的语法。原始示例如下所示:

  1. SELECT empno, ename
  2. FROM emp
  3. START WITH empno = 7566
  4. CONNECT BY PRIOR empno = mgr;
  5.  
  6. EMPNO ENAME
  7. ---------- ----------
  8. 7566 JONES
  9. 7902 FORD
  10. 7369 SMITH

这种语法更简洁,但不如递归 CTE 强大。对于涉及连接的更复杂的查询,它可能变得困难和混乱。将 PLSQL “分层查询”转换为递归 CTE 总是很容易的:

  • 非递归分支是不带CONNECT BY子句但包含START WITH子句的 查询。
  • 递归分支是不带START WITH子句但包含CONNECT BY子句的 查询。添加具有递归 CTE 名称的PRIOR联接,并用来自该联接 CTE 的列替换所有列。
  • 如果 查询使用CONNECT BY NOCYCLE,则使用UNION,否则使用UNION ALL。

七、递归查询的真正实力

如果没有递归 CTE,很多可以用过程语言编写的东西就不能用 SQL 编写。这通常影响数据库的使用,因为 SQL 是用来查询数据库的。但是递归 CTE 使 SQL过程代码更完善,也就是说,它可以执行与任何其他编程语言相同的计算。前面的示例表明递归 CTE 可以完成您在 SQL 中无法执行的有用工作。

作为递归查询功能的示例,这里是一个递归 CTE,它计算斐波那契数列的第一个元素:

  1. WITH RECURSIVE t(n,last_n,cnt) AS (
  2. SELECT 1,0,1 FROM DUAL
  3. UNION ALL
  4. SELECT t.n+t.last_n, t.n, t.cnt+1
  5. FROM t
  6. )
  7. SELECT * FROM T limit 10
  8.  
  9. n | last_n | cnt
  10. ----+--------+-----
  11. 1 | 0 | 1
  12. 1 | 1 | 2
  13. 2 | 1 | 3
  14. 3 | 2 | 4
  15. 5 | 3 | 5
  16. 8 | 5 | 6
  17. 13 | 8 | 7
  18. 21 | 13 | 8
  19. 34 | 21 | 9
  20. 55 | 34 | 10

  

理解 KingbaseES 中的递归查询的更多相关文章

  1. 如何理解javaSript中函数的参数是按值传递

    本文是我基于红宝书<Javascript高级程序设计>中的第四章,4.1.3传递参数小节P70,进一步理解javaSript中函数的参数,当传递的参数是对象时的传递方式. (结合资料的个人 ...

  2. 怎么理解js中的事件委托

    怎么理解js中的事件委托 时间 2015-01-15 00:59:59  SegmentFault 原文  http://segmentfault.com/blog/sunchengli/119000 ...

  3. 如何理解T-SQL中Merge语句(二)

    写在前面的话:上一篇写了如何理解T-SQL中Merge语句,基本把Merge语句要讲的给讲了,在文章的后面,抛出了几个结,当时没有想明白怎么去用文字表达,这一篇就来解答一下这几个结,又是一篇“天马行空 ...

  4. 如何理解T-SQL中Merge语句

    写在前面的话:之前看过Merge语句,感觉没什么用,完全可以用其他的方式来替代,最近又看了看Merge语句,确实挺好用,可以少写很多代码,看起来也很紧凑,当然也有别的优点. ====正文开始===== ...

  5. 深入理解JDK中的I/O

    深入理解JDK中的I/O 目 录 java内存模型GCHTTP协议事务隔离级并发多线程设计模式清楚redis.memcache并且知道区别mysql分表分库有接口幂等性了解jdk8稍微了解一下特性 j ...

  6. 深度理解Jquery 中 offset() 方法

    参考原文:深度理解Jquery 中 offset() 方法

  7. 简单理解Struts2中拦截器与过滤器的区别及执行顺序

    简单理解Struts2中拦截器与过滤器的区别及执行顺序 当接收到一个httprequest , a) 当外部的httpservletrequest到来时 b) 初始到了servlet容器 传递给一个标 ...

  8. 深入理解CSS中的层叠上下文和层叠顺序(转)

    by zhangxinxu from http://www.zhangxinxu.com 本文地址:http://www.zhangxinxu.com/wordpress/?p=5115 零.世间的道 ...

  9. 理解JavaScript中的原型继承(2)

    两年前在我学习JavaScript的时候我就写过两篇关于原型继承的博客: 理解JavaScript中原型继承 JavaScript中的原型继承 这两篇博客讲的都是原型的使用,其中一篇还有我学习时的错误 ...

随机推荐

  1. React技巧之组件中返回多个元素

    原文链接:https://bobbyhadz.com/blog/react-return-multiple-elements 作者:Borislav Hadzhiev 正文从这开始~ fragment ...

  2. 关于android里activity之间利用button组件使用intent跳转页面

    在需要跳转的activity 中 添加 Button button = findViewById(R.id.login);button.setOnClickListener(new View.OnCl ...

  3. UiPath手把手教程

    UiPath下载安装与激活 链接: https://pan.baidu.com/s/1o5Ur-QNTxsnlhi97-losJQ 提取码: 9dmf 复制这段内容后打开百度网盘手机App,操作更方便 ...

  4. Android 12(S) 图像显示系统 - HWC HAL 初始化与调用流程

    必读: Android 12(S) 图像显示系统 - 开篇 接口定义 源码位置:/hardware/interfaces/graphics/composer/ 在源码目录下可以看到4个版本的HIDL ...

  5. 高级程序员必知必会,一文详解MySQL主从同步原理,推荐收藏

    1. MySQL主从同步实现方式 MySQL主从同步是基于Bin Log实现的,而Bin Log记录的是原始SQL语句. Bin Log共有三种日志格式,可以binlog_format配置参数指定. ...

  6. 用Bootstrap4写了一个WordPress主题Writing

    这是一个简洁的WordPress博客主题,为专注写作而设计. 本主题使用Bootstrap4框架开发. 主要功能 自适应: 标签云页面模板: 两栏设计: 全宽页面模板: 支持设置背景色和背景图片: 8 ...

  7. 2020 CSP-J 初赛解析

    题面  老师给的解析  自己觉得很好的一篇题解 直接说重点题吧,不耽误时间了 T5: 这个很显然就是让进这个 while 的次数尽可能少, 那么我们可以让他只进一次 while,即让第一次进 whil ...

  8. Go死锁——当Channel遇上Mutex时

    背景 用metux lock for循环,在for循环中又 向带缓冲的Channel 写数据时,千万要小心死锁! 最近,我在测试ws长链接网关,平均一个星期会遇到一次服务假死问题,因为并不是所有rou ...

  9. NewApiDay03_File类

    File类创建一个新文件 File类的每一个实例可以表示硬盘(文件系统)中的一个文件或目录(实际上表示的是一个抽象路径) 使用File可以做到: 1:访问其表示的文件或目录的属性信息,例如:名字,大小 ...

  10. Hadoop - MapReduce 过程

    Hadoop - MapReduce 一.MapReduce设计理念 map--->映射 reduce--->归纳 mapreduce必须构建在hdfs之上的一种大数据离线计算框架 在线: ...