Oracle兼容-语法-分层查询(Hierarchical Query)


GreatSQL支持分层查询(Hierarchical Query)。

1. 语法

  1. { CONNECT BY [ NOCYCLE ] condition [ START WITH condition ]
  2. | START WITH condition CONNECT BY [ NOCYCLE ] condition
  3. }

2. 定义和用法

    1. 分层查询简介

为了查找父行的子行,需要计算父行的 CONNECT BY 条件的 PRIOR 表达式和表中每一行的另一个表达式。条件为真的行是父项的子项。CONNECT BY 条件可以包含其他条件以进一步过滤查询选择的行。

如果 CONNECT BY 条件导致层次结构中出现循环,则会返回错误。如果一行既是另一行的父项(或祖父项或直系祖先)又是子项(或孙项或直系后代),则会导致循环。

    1. 对分层查询的处理方式如下:
    • 1.) 如果存在连接,首先会被评估,无论连接是在FROM子句中还是在WHERE子句中指定。
    • 2.) CONNECT BY条件被评估。
    • 3.) 任何剩余的WHERE子句谓语都被评估。
    1. 对于分成查询的处理顺序为:
    • 1.) 选择层次结构的根行——那些满足 START WITH 条件的行。
    • 2.) 选择每个根行的子行。每个子行都必须满足与根行之一相关的 CONNECT BY 条件。
    • 3.) 择连续几代的子行。 选择在步骤 2 中返回的行的子项,然后是这些子项的子项,依此类推。
    • 4.) 如果查询包含没有连接的 WHERE 子句,那么会从层次结构中删除所有不满足 WHERE 子句条件的行。
    • 5.) 根据先序排列信息返回结果

示例:

分层查询Hierarchical Query

3. Oracle兼容说明

  • 1、查询表输出结果的排序和Oracle可能不一致

由于在遍历查询表数据方式与Oracle不一致,导致 CONNECT BY 单一层次内数据排序结果不一致。

  1. -- 初始化测试表及数据
  2. greatsql> CREATE TABLE student(
  3. id INT NOT NULL,
  4. name VARCHAR2(20) NOT NULL,
  5. grade INT NOT NULL
  6. );
  7. -- 案例1:测试数据顺序写入
  8. -- 顺序插入四条记录(id值顺序:1,2,3,4
  9. greatsql> INSERT INTO student VALUES (1, 'John', -1);
  10. greatsql> INSERT INTO student VALUES (2, 'Paul', 1);
  11. greatsql> INSERT INTO student VALUES (3, 'Nancy', 1);
  12. greatsql> INSERT INTO student VALUES (4, 'Sarah', 2);
  13. greatsql> SELECT * FROM student;
  14. +------+-------+-------+
  15. | id | name | grade |
  16. +------+-------+-------+
  17. | 1 | John | -1 |
  18. | 2 | Paul | 1 |
  19. | 3 | Nancy | 1 |
  20. | 4 | Sarah | 2 |
  21. +------+-------+-------+
  22. -- 此种情况下,GreatSQLOracle查询结果数据顺序一致
  23. greatsql> SELECT id, name, grade, LEVEL FROM student CONNECT BY PRIOR id = grade;
  24. +------+-------+-------+-------+
  25. | id | name | grade | LEVEL |
  26. +------+-------+-------+-------+
  27. | 1 | John | -1 | 1 |
  28. | 2 | Paul | 1 | 2 |
  29. | 4 | Sarah | 2 | 3 |
  30. | 3 | Nancy | 1 | 2 |
  31. | 2 | Paul | 1 | 1 |
  32. | 4 | Sarah | 2 | 2 |
  33. | 3 | Nancy | 1 | 1 |
  34. | 4 | Sarah | 2 | 1 |
  35. +------+-------+-------+-------+
  36. 8 rows in set (0.00 sec)
  37. -- Oracle中查询得到的结果如下,二者一致(建表和写入数据过程略过)
  38. SQL> SELECT id, name, grade, LEVEL FROM student CONNECT BY PRIOR id = grade;
  39. ID NAME GRADE LEVEL
  40. ---------- -------------------- ---------- ----------
  41. 1 John -1 1
  42. 2 Paul 1 2
  43. 4 Sarah 2 3
  44. 3 Nancy 1 2
  45. 2 Paul 1 1
  46. 4 Sarah 2 2
  47. 3 Nancy 1 1
  48. 4 Sarah 2 1
  49. 8 rows selected.
  50. -- 案例2:测试数据乱序写入
  51. -- 清空表,调整数据写入顺序(id值顺序:3,4,1,2
  52. greatsql> TRUNCATE TABLE student;
  53. greatsql> INSERT INTO student VALUES (3, 'Nancy', 1);
  54. greatsql> INSERT INTO student VALUES (4, 'Sarah', 2);
  55. greatsql> INSERT INTO student VALUES (1, 'John', -1);
  56. greatsql> INSERT INTO student VALUES (2, 'Paul', 1);
  57. greatsql> SELECT * FROM student;
  58. +------+-------+-------+
  59. | id | name | grade |
  60. +------+-------+-------+
  61. | 3 | Nancy | 1 |
  62. | 4 | Sarah | 2 |
  63. | 1 | John | -1 |
  64. | 2 | Paul | 1 |
  65. +------+-------+-------+
  66. 4 rows in set (0.00 sec)
  67. -- 此种情况下,GreatSQLOracle查询结果数据顺序不一致
  68. greatsql> SELECT id, name, grade, LEVEL FROM student CONNECT BY PRIOR id = grade;
  69. +------+-------+-------+-------+
  70. | id | name | grade | LEVEL |
  71. +------+-------+-------+-------+
  72. | 3 | Nancy | 1 | 1 |
  73. | 4 | Sarah | 2 | 1 |
  74. | 1 | John | -1 | 1 |
  75. | 3 | Nancy | 1 | 2 |
  76. | 2 | Paul | 1 | 2 |
  77. | 4 | Sarah | 2 | 3 |
  78. | 2 | Paul | 1 | 1 |
  79. | 4 | Sarah | 2 | 2 |
  80. +------+-------+-------+-------+
  81. 8 rows in set (0.00 sec)
  82. -- Oracle中查询得到的结果如下,二者不一致(建表和写入数据过程略过)
  83. SQL> SELECT id, name, grade, LEVEL FROM student CONNECT BY PRIOR id = grade;
  84. ID NAME GRADE LEVEL
  85. ---------- -------------------- ---------- ----------
  86. 1 John -1 1
  87. 3 Nancy 1 2
  88. 2 Paul 1 2
  89. 4 Sarah 2 3
  90. 3 Nancy 1 1
  91. 2 Paul 1 1
  92. 4 Sarah 2 2
  93. 4 Sarah 2 1
  94. 8 rows selected.

可以看到,由于第一个层级查询结果排序不一致,导致分层查询的结果顺序不一致。

  • 2、与 LISTAGG 结合使用输出组合顺序时,LISTAGG 的实现依赖 ORDER BY,因此在内部查询的时候会根据 GROUP BY 进行排序。

  • 3、在 ORACLE 模式下,SYSDATE 的行为与Oralce一致,在 DEFAULT 模式下,循环查询可能导致错误。

  • 4、与上一条类似,在自定义PACKAGE与FUNCTION中可能存在定义包含 DETERMINISTIC 的情况,也可能存在导致循环检查失效问题。

  • 5、当 CONNECT BY 中存在永真的条件时,不管是否使用了 PRIORCONNECT_BY_ROOT 的条件,会报告死循环错误。

  • 6、数据表中可能含有特殊的数据会产生死循环,或者单次遍历的子节点非常多,会根据 cte_max_recursion_depth 选项限制迭代数据大小。

  • 7、与 ROWNUM 伪列结合使用的时候,ROWNUMWHERE 中作为查询条件结果会产生差异,如下例所示:

与Oracle区别:ROWNUM 的值会根据在 CONNECT BY 排序后的结果保存,而不重新计算。

  • 示例1:
  1. greatsql> SELECT LEVEL, ROWNUM FROM DUAL WHERE ROWNUM < 3 AND LEVEL = 2 CONNECT BY LEVEL <= 10;
  2. +-------+--------+
  3. | LEVEL | rownum |
  4. +-------+--------+
  5. | 2 | 2 |
  6. +-------+--------+
  7. -- Oracle中的执行结果如下
  8. SQL> SELECT LEVEL, ROWNUM FROM DUAL WHERE ROWNUM < 3 AND LEVEL = 2 CONNECT BY LEVEL <= 10;
  9. LEVEL ROWNUM
  10. ---------- ----------
  11. 2 1
  • 示例2
  1. greatsql> SELECT LEVEL, SYS_CONNECT_BY_PATH(ROWNUM, '->') FROM DUAL WHERE
  2. ROWNUM < 3 AND LEVEL = 2
  3. CONNECT BY LEVEL <= 10;
  4. +-------+-----------------------------------+
  5. | LEVEL | SYS_CONNECT_BY_PATH(ROWNUM, '->') |
  6. +-------+-----------------------------------+
  7. | 2 | ->1->2 |
  8. +-------+-----------------------------------+
  9. -- Oracle中的执行结果如下
  10. LEVEL SYS_CONNECT_BY_PATH(ROWNUM,'->')
  11. ---------- -------------------------------------
  12. 2 ->1->1
  • 8、在GreatSQL中,支持在 CONNECT BY 条件中含有 BLOB 列,而Oracle不支持。

3. 分层查询伪列

分层查询伪列(Hierarchical Query Pseudocolumns)

分层查询伪列仅在分层查询中有效,包括以下三个伪列。

3.1 分层查询伪列 CONNECT_BY_ISCYCLE

表示在分层查询中,当前数据是否会导致形成循环。即根据层次关系,当前层数据是否存在其叶子节点也是其父节点。该列只有在同时指定 NOCYCLE 关键字时才有意义,当前数据会导致形成环则结果为1,否则为0。

3.2 分层查询伪列 CONNECT_BY_ISLEAF

表示在分层查询中,当前数据是否是分层查询形成的树结构中的叶子节点。若是叶子节点值为1,否则为0。此信息指示是否可以进一步扩展给定行以显示更多层次结构。

3.3 分层查询伪列 LEVEL

表示在分层查询中,形成的树结构中的当前层数。该列值一直从1开始,即 START WITH 对应的数据的层数一直为1,之后子孙节点的LEVEL值依次递增。

下图显示了倒置树的节点及其LEVEL值:

Pseudocolumn LEVEL

4. 分层查询操作符

GreatSQL提供如下两个标识符,可用于指定层次关系中的某个节点属性。

4.1 分层查询操作符 PRIOR

在分层查询中,CONNECT BY 条件中的一个表达式必须由 PRIOR 运算符限定。可以有多个 PRIOR 条件,但只有一个条件需要 PRIOR 运算符。PRIOR 操作符之后的参数将作为分层查询中的父节点。PRIOR 计算层次查询中当前行的父行的紧跟在后面的表达式。

PRIOR 使用列中父行的值。

PRIOR 最常用于使用相等运算符比较列值,PRIOR 关键字可以位于运算符的任一侧。

理论上,CONNECT BY 子句中可以使用等号 = 以外的运算符,但是其他运算符创建的条件可能会导致可能的组合出现无限循环。

4.2 分层查询操作符 CONNECT_BY_ROOT

CONNECT_BY_ROOT 是一元运算符,仅在分层查询中有效。当使用此运算符限定列时,将使用根行中的数据返回列值。

注意:

  1. 只支持基础数据类型,不支持JSON、GEO等特殊的数据类型。

  2. 参数不可以是虚拟列、层次查询函数、操作符、伪列及子查询。

  3. 两个操作符 PRIORCONNECT_BY_ROOT 都不能在 START WITH 子句中使用。

5. 分层查询函数 SYS_CONNECT_BY_PATH()

5.1 语法

  1. SYS_CONNECT_BY_PATH(column_name, DELIMITER)

其中 DELIMITER 表示分隔符。该函数将获取从根节点到当前节点的路径上所有节点名为column_name的值,中间用DELIMITER进行分隔开。SYS_CONNECT_BY_PATH()不能在 CONNECT BYSTART WITHGROUP BY 等几个子句中使用。

5.2 定义和用法

SYS_CONNECT_BY_PATH() 函数主要用于 树查询(层次查询) 以及 多列转行

SYS_CONNECT_BY_PATH() 仅在分层查询中有效,它返回列值从根到节点的路径,对于 CONNECT BY 条件返回的每一行,列值由 char 分隔。

含有伪列的计算,并且存在 WHERE 过滤条件,ROWNUM 的值,根据在 CONNECT BY 排序后的结果将会保存,而不是重新计算,例如:

  • GreatSQL运行结果:
  1. greatsql> SELECT LEVEL, SYS_CONNECT_BY_PATH(ROWNUM, '->') FROM DUAL WHERE ROWNUM < 3 AND LEVEL=2 CONNECT BY LEVEL<=10;
  2. +-------+--------+
  3. | LEVEL | ROWNUM |
  4. +-------+--------+
  5. | 2 | ->1->2 |
  6. +-------+--------+
  • Oracle运行结果:
  1. SQL> SELECT LEVEL, SYS_CONNECT_BY_PATH(ROWNUM, '->') FROM DUAL WHERE ROWNUM < 3 AND LEVEL=2 CONNECT BY LEVEL<=10;
  2. LEVEL SYS_CONNECT_BY_PATH(ROWNUM,'->')
  3. ---------- ----------------------------------
  4. 2 ->1->1

6. 示例

初始化测试表及数据。

  1. greatsql> CREATE TABLE student(
  2. id INT NOT NULL,
  3. name VARCHAR2(20) NOT NULL,
  4. grade INT NOT NULL
  5. );
  6. greatsql> INSERT INTO student VALUES (1, 'John', -1), (2, 'Paul', 1), (3, 'Nancy', 1), (4, 'Sarah', 2);

几个示例。

  1. greatsql> SELECT id, name, grade, LEVEL FROM student CONNECT BY PRIOR id = grade;
  2. +------+-------+-------+-------+
  3. | id | name | grade | LEVEL |
  4. +------+-------+-------+-------+
  5. | 1 | John | -1 | 1 |
  6. | 2 | Paul | 1 | 2 |
  7. | 4 | Sarah | 2 | 3 |
  8. | 3 | Nancy | 1 | 2 |
  9. | 2 | Paul | 1 | 1 |
  10. | 4 | Sarah | 2 | 2 |
  11. | 3 | Nancy | 1 | 1 |
  12. | 4 | Sarah | 2 | 1 |
  13. +------+-------+-------+-------+
  14. greatsql> SELECT id, name, grade, PRIOR name FROM student CONNECT BY PRIOR id = grade;
  15. +------+-------+-------+------------+
  16. | id | name | grade | PRIOR name |
  17. +------+-------+-------+------------+
  18. | 1 | John | -1 | NULL |
  19. | 2 | Paul | 1 | John |
  20. | 4 | Sarah | 2 | Paul |
  21. | 3 | Nancy | 1 | John |
  22. | 2 | Paul | 1 | NULL |
  23. | 4 | Sarah | 2 | Paul |
  24. | 3 | Nancy | 1 | NULL |
  25. | 4 | Sarah | 2 | NULL |
  26. +------+-------+-------+------------+
  27. 8 rows in set (0.00 sec)
  28. greatsql> SELECT id, CONNECT_BY_ROOT name FROM student CONNECT BY PRIOR id = grade;
  29. +------+----------------------+
  30. | id | CONNECT_BY_ROOT name |
  31. +------+----------------------+
  32. | 1 | John |
  33. | 2 | John |
  34. | 4 | John |
  35. | 3 | John |
  36. | 2 | Paul |
  37. | 4 | Paul |
  38. | 3 | Nancy |
  39. | 4 | Sarah |
  40. +------+----------------------+
  41. 8 rows in set (0.01 sec)
  42. -- 修改当前时间戳
  43. greatsql> SET TIMESTAMP = 1;
  44. greatsql> SELECT id, grade, NOW() FROM student CONNECT BY NOCYCLE PRIOR id = (grade + 1);
  45. +------+-------+---------------------+
  46. | id | grade | NOW() |
  47. +------+-------+---------------------+
  48. | 1 | -1 | 1970-01-01 03:00:01 |
  49. | 2 | 1 | 1970-01-01 03:00:01 |
  50. | 3 | 1 | 1970-01-01 03:00:01 |
  51. | 4 | 2 | 1970-01-01 03:00:01 |
  52. | 3 | 1 | 1970-01-01 03:00:01 |
  53. | 4 | 2 | 1970-01-01 03:00:01 |
  54. | 4 | 2 | 1970-01-01 03:00:01 |
  55. +------+-------+---------------------+
  56. 7 rows in set (0.00 sec)
  57. greatsql> SELECT id, name, grade, CONNECT_BY_ISCYCLE FROM student CONNECT BY NOCYCLE PRIOR id = (grade + 1);
  58. +------+-------+-------+--------------------+
  59. | id | name | grade | CONNECT_BY_ISCYCLE |
  60. +------+-------+-------+--------------------+
  61. | 1 | John | -1 | 0 |
  62. | 2 | Paul | 1 | 1 |
  63. | 3 | Nancy | 1 | 0 |
  64. | 4 | Sarah | 2 | 0 |
  65. | 3 | Nancy | 1 | 0 |
  66. | 4 | Sarah | 2 | 0 |
  67. | 4 | Sarah | 2 | 0 |
  68. +------+-------+-------+--------------------+
  69. 7 rows in set (0.01 sec)
  70. greatsql> SELECT id, name, grade, CONNECT_BY_ISLEAF FROM student CONNECT BY NOCYCLE PRIOR id = (grade + 1);
  71. +------+-------+-------+-------------------+
  72. | id | name | grade | CONNECT_BY_ISLEAF |
  73. +------+-------+-------+-------------------+
  74. | 1 | John | -1 | 1 |
  75. | 2 | Paul | 1 | 0 |
  76. | 3 | Nancy | 1 | 0 |
  77. | 4 | Sarah | 2 | 1 |
  78. | 3 | Nancy | 1 | 0 |
  79. | 4 | Sarah | 2 | 1 |
  80. | 4 | Sarah | 2 | 1 |
  81. +------+-------+-------+-------------------+
  82. 7 rows in set (0.01 sec)
  83. greatsql> SELECT id, grade, LEVEL, SYS_CONNECT_BY_PATH(id ,'->'), PRIOR name, CONNECT_BY_ROOT id FROM student CONNECT BY PRIOR id = grade;
  84. +------+-------+-------+-------------------------------+------------+--------------------+
  85. | id | grade | LEVEL | SYS_CONNECT_BY_PATH(id ,'->') | PRIOR name | CONNECT_BY_ROOT id |
  86. +------+-------+-------+-------------------------------+------------+--------------------+
  87. | 1 | -1 | 1 | ->1 | NULL | 1 |
  88. | 2 | 1 | 2 | ->1->2 | John | 1 |
  89. | 4 | 2 | 3 | ->1->2->4 | Paul | 1 |
  90. | 3 | 1 | 2 | ->1->3 | John | 1 |
  91. | 2 | 1 | 1 | ->2 | NULL | 2 |
  92. | 4 | 2 | 2 | ->2->4 | Paul | 2 |
  93. | 3 | 1 | 1 | ->3 | NULL | 3 |
  94. | 4 | 2 | 1 | ->4 | NULL | 4 |
  95. +------+-------+-------+-------------------------------+------------+--------------------+
  96. 8 rows in set (0.00 sec)

问题反馈

联系我们

扫码关注微信公众号

greatsql-wx