---
title: "层次查询 - OceanBase 数据库 V4.2.5 | OceanBase 文档中心"
description: 层次查询 层次查询（Hierarchical Query）是一种具有特殊功能的查询语句，通过它能够将分层数据按照层次关系展示出来。 分层数据是指关系表中的数据之间具有层次关系。这种关系在现实生活中十分常见，示例如下： 组织架构中组长和组员之间的关系 企业中上下级部门之间的关系 Web 网页中页面跳转的关系 使用限制及…
---
切换语言

- 中文站 - 简体中文
- International - English
- 日本站 - 日本語

文档反馈![](https://mdn.alipayobjects.com/huamei_22khvb/afts/img/A*P8CuR4UJ_FkAAAAAAAAAAAAADiGDAQ/original) OceanBase 数据库分布式版 - V 4.2.5 LTS

# 层次查询

更新时间：2026-07-15 20:56:58

[编辑](https://github.com/oceanbase/oceanbase-doc/edit/V4.2.5/zh-CN/700.reference/500.sql-reference/100.sql-syntax/300.common-tenant-of-oracle-mode/800.queries-and-subqueries-of-oracle-mode/300.hierarchical-query-of-oracle-mode.md)  

层次查询（Hierarchical Query）是一种具有特殊功能的查询语句，通过它能够将分层数据按照层次关系展示出来。

分层数据是指关系表中的数据之间具有层次关系。这种关系在现实生活中十分常见，示例如下：

- 组织架构中组长和组员之间的关系
 - 企业中上下级部门之间的关系
 - Web 网页中页面跳转的关系

## 使用限制及注意事项

如果层次查询中包含了 `FOR UPDATE` 子句，那么不支持以下的使用场景：

- 如果子查询使用了 `DISTINCT` 关键字或者聚合，这种情况下将不能和 `FOR UPDATE` 一起使用。
 - 任何包含公共表达式（Common Table Expressions，CTE）的情景都不支持，即带有 `WITH ... AS ...` 子句的 `SELECT` 查询不能与 `FOR UPDATE` 同时使用。更多 `WITH ... AS ...` 子句的信息，请参见 [WITH CLAUSE](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001504439)。
 - 如果层次查询中包含 `CONNECT BY`，数据量大的场景下，可以通过 `USE_HASH` Hint 或 `USE_NL` Hint 调优选择最佳执行计划。更多关于 `USE_HASH` 和 `USE_NL` 的信息，请参见 [与联接操作相关的 Hint](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001504818)。

为了保证数据库的正常操作及性能优化，请避免在上述提及的前两种场景中使用 `FOR UPDATE` 子句。

## 语法

简单层次查询的 `SELECT` 语法如下：

```sql
SELECT [LEVEL], column, expr... FROM table [WHERE condition]
[ START WITH start_expression ] CONNECT BY [NOCYCLE] { PRIOR child_expr = parent_expr | parent_expr = PRIOR child_expr } [ ORDER SIBLINGS BY ...]
[ GROUP BY ... ] [ HAVING ... ] [ ORDER BY ... ]
[FOR UPDATE [OF column] [ {NOWAIT | WAIT integer | SKIP LOCKED } ] ]

```

有关 `SELECT` 语法的详细信息，请参见 [SIMPLE SELECT](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001504437)。

## 参数解释

| 参数 | 说明 |
| --- | --- |
| LEVEL | 表示等级，即节点的层次，属于伪列。由查询的起点开始算起，起点为 1，依次类推。 |
| CONNECT_BY_ISLEAF | 当前数据行是否是层次关系中的叶子节点，属于伪列。   - 0 表示不是叶子节点。 - 1 表示是叶子节点。 |
| CONNECT_BY_ISCYCLE | 当前数据行是否在循环中，属于伪列。   - 0 表示不在循环中。 - 1 表示在循环中。 |
| CONNECT_BY_ROOT | `CONNECT_BY_ROOT` 是一元运算符，表示参数中的列来自于层次查询的根节点，与一元的 + 和 - 具有相同的优先级。 |
| condition | 指定条件。 |
| CONNECT BY | 指定如何来确定父子关系，这里通常使用等值表达式，但其他表达式同样支持。 |
| START WITH | 指定层次查询中的根行（Root Row）。 |
| PRIOR | `PRIOR` 是一元运算符，表示参数中的列来自于父行（Parent Row），与一元的 + 和 - 具有相同的优先级。 |
| NOCYCLE | 当指定该关键字时，即使返回结果中有循环仍旧可以返回，并可以通过 `CONNECT_BY_ISCYCLE` 虚拟列来指定哪里出现循环；否则，会给客户端报错。 |
| ORDER SIBLINGS BY | 指定同一个层级行之间的排列顺序。 |
| FOR UPDATE | 可选项，对查询结果所有行加上排他锁，以阻止其他事务的并发修改，或阻止在某些事务隔离级别时的并发读取。   - `OF column`：对于多表 Join 的场景，使用该子句可以指示仅锁定某些表（即 `column` 指定列所在的表）的查询结果行。 - `NOWAIT`：立即对查询结果行尝试加锁如果查询结果行中已经有被其他会话加锁的行，则执行失败。 - `WAIT integer`：等待 `Interger` 时间后再对查询结果行尝试加锁，如果查询结果行中已经有被其他会话加锁的行，则执行失败。 - `SKIP LOCKED`：如果查询结果中已经有被其他会话加锁的行，则会跳过这些加锁行，返回未被加锁的行。     #### 注意      `SKIP LOCKED` 暂不支持多表 `JOIN` 加锁的场景。 |

## 执行流程

使用和实现层次查询最关键是要理解其执行流程，一般的层次查询执行流程如下：

1. 执行 `FROM` 后面的 `SCAN` 或 `JOIN` 操作。
 2. 根据 `START WITH` 和 `CONNECT BY` 的内容生成层次关系结果。

   对于步骤 2 中生成层次关系的流程可以理解为：

      1. 根据 `START WITH` 中的表达式得到根行（Root Rows）。
      2. 根据 `CONNECT BY` 中的表达式选择每个根行（Root Rows）的子行（Child Rows）。
 3. 将步骤 2 中生成的子行（Child Rows）作为新的根行（Root Rows）进一步生成子行（Child Rows），循环执行直到没有新行生成。
 4. 按照常规查询执行流程执行剩下的子句（例如 `WHERE`、`GROUP`、`ORDER BY`......）。

## 示例

1. 创建表 `emp`，并向表中的 `emp_id`、`position` 和 `mgr_id` 列插入数据。

   ```sql
   CREATE TABLE emp(emp_id INT,position VARCHAR(50),mgr_id INT);
   INSERT INTO emp VALUES (1,'全球经理',NULL);
   INSERT INTO emp VALUES (2,'欧洲区经理',1);
   INSERT INTO emp VALUES (3,'亚太区经理',1);
   INSERT INTO emp VALUES (4,'美洲区经理',1);
   INSERT INTO emp VALUES (5,'意大利区经理',2);
   INSERT INTO emp VALUES (6,'法国区经理',2);
   INSERT INTO emp VALUES (7,'中国区经理',3);
   INSERT INTO emp VALUES (8,'韩国区经理',3);
   INSERT INTO emp VALUES (9,'日本区经理',3);
   INSERT INTO emp VALUES (10,'美国区经理',4);
   INSERT INTO emp VALUES (11,'加拿大区经理',4);
   INSERT INTO emp VALUES (12,'北京区经理',7);

   ```

   以上示例表中的列 `position` 具有清晰的层次关系。树状结构如下：

   ![层次查询](https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/2345220461/p371444.png)
 2. 执行以下语句将结果按照层次结构展示出来。

   ```sql
   obclient [SYS]> SELECT emp_id, mgr_id, position, LEVEL
   FROM emp
   START WITH mgr_id IS NULL
       CONNECT BY PRIOR emp_id = mgr_id;

   ```

   返回结果如下：

   ```shell
   +--------+--------+--------------------+-------+
   | EMP_ID | MGR_ID | POSITION           | LEVEL |
   +--------+--------+--------------------+-------+
   |      1 |   NULL | 全球经理            |     1 |
   |      2 |      1 | 欧洲区经理          |     2 |
   |      5 |      2 | 意大利区经理        |     3 |
   |      6 |      2 | 法国区经理          |     3 |
   |      3 |      1 | 亚太区经理          |     2 |
   |      7 |      3 | 中国区经理          |     3 |
   |     12 |      7 | 北京区经理          |     4 |
   |      8 |      3 | 韩国区经理          |     3 |
   |      9 |      3 | 日本区经理          |     3 |
   |      4 |      1 | 美洲区经理          |     2 |
   |     10 |      4 | 美国区经理          |     3 |
   |     11 |      4 | 加拿大区经理        |     3 |
   +--------+--------+--------------------+-------+
   12 rows in set

   ```
 3. 仅查询 "亚太区" 的层次结构，并且使用 `FOR UPDATE` 子句锁定查询结果。

   ```sql
   SELECT emp_id, mgr_id, position, LEVEL
   FROM emp
   START WITH position = '亚太区经理'
       CONNECT BY PRIOR emp_id = mgr_id
   FOR UPDATE;

   ```

   返回结果如下：

   ```shell
   +--------+--------+-----------------+-------+
   | EMP_ID | MGR_ID | POSITION        | LEVEL |
   +--------+--------+-----------------+-------+
   |      3 |      1 | 亚太区经理       |     1 |
   |      7 |      3 | 中国区经理       |     2 |
   |     12 |      7 | 北京区经理       |     3 |
   |      8 |      3 | 韩国区经理       |     2 |
   |      9 |      3 | 日本区经理       |     2 |
   +--------+--------+-----------------+-------+
   5 rows in set

   ```
 4. 使用 `CONNECT BY` 子句进行递归查询，并使用 `USE_HASH` Hint 指示优化器使用 HASH-JOIN 算法。

   ```sql
   obclient> SELECT /*+ USE_HASH(emp) */ EMP_ID, POSITION, MGR_ID  FROM emp  START WITH EMP_ID = 1  CONNECT BY PRIOR EMP_ID = MGR_ID;

   ```

   返回结果如下：

   ```shell
   +--------+--------------------+--------+
   | EMP_ID | POSITION           | MGR_ID |
   +--------+--------------------+--------+
   |      1 | 全球经理           |   NULL |
   |      2 | 欧洲区经理         |      1 |
   |      5 | 意大利区经理       |      2 |
   |      6 | 法国区经理         |      2 |
   |      3 | 亚太区经理         |      1 |
   |      7 | 中国区经理         |      3 |
   |     12 | 北京区经理         |      7 |
   |      8 | 韩国区经理         |      3 |
   |      9 | 日本区经理         |      3 |
   |      4 | 美洲区经理         |      1 |
   |     10 | 美国区经理         |      4 |
   |     11 | 加拿大区经理       |      4 |
   +--------+--------------------+--------+
   12 rows in set (0.004 sec)

   ```

   执行计划如下：

   ```sql
   obclient> EXPLAIN SELECT /*+ USE_HASH(emp) */ EMP_ID, POSITION, MGR_ID  FROM emp  START WITH EMP_ID = 1  CONNECT BY PRIOR EMP_ID = MGR_ID;
   +--------------------------------------------------------------------------------------------------------------------------------+
   | Query Plan                                                                                                                     |
   +--------------------------------------------------------------------------------------------------------------------------------+
   | ====================================================                                                                           |
   | |ID|OPERATOR           |NAME |EST.ROWS|EST.TIME(us)|                                                                           |
   | ----------------------------------------------------                                                                           |
   | |0 |HASH CONNECT BY    |     |3       |11          |                                                                           |
   | |1 |├─SUBPLAN SCAN     |VIEW1|1       |5           |                                                                           |
   | |2 |│ └─TABLE FULL SCAN|EMP  |1       |5           |                                                                           |
   | |3 |└─SUBPLAN SCAN     |VIEW2|12      |5           |                                                                           |
   | |4 |  └─TABLE FULL SCAN|EMP  |12      |5           |                                                                           |
   | ====================================================                                                                           |
   | Outputs & filters:                                                                                                             |
   | -------------------------------------                                                                                          |
   |   0 - output([VIEW2.EMP.EMP_ID], [VIEW2.EMP.POSITION], [VIEW2.EMP.MGR_ID]), filter(nil)                                        |
   |       equal_conds([VIEW1.EMP.EMP_ID = VIEW2.EMP.MGR_ID]), other_conds(nil)                                                     |
   |   1 - output([VIEW1.EMP.__pk_increment], [VIEW1.EMP.EMP_ID], [VIEW1.EMP.MGR_ID], [VIEW1.EMP.POSITION]), filter(nil), rowset=16 |
   |       access([VIEW1.EMP.__pk_increment], [VIEW1.EMP.EMP_ID], [VIEW1.EMP.MGR_ID], [VIEW1.EMP.POSITION])                         |
   |   2 - output([EMP.__pk_increment], [EMP.EMP_ID], [EMP.MGR_ID], [EMP.POSITION]), filter([EMP.EMP_ID = 1]), rowset=16            |
   |       access([EMP.__pk_increment], [EMP.EMP_ID], [EMP.MGR_ID], [EMP.POSITION]), partitions(p0)                                 |
   |       is_index_back=false, is_global_index=false, filter_before_indexback[false],                                              |
   |       range_key([EMP.__pk_increment]), range(MIN ; MAX)always true                                                             |
   |   3 - output([VIEW2.EMP.__pk_increment], [VIEW2.EMP.EMP_ID], [VIEW2.EMP.MGR_ID], [VIEW2.EMP.POSITION]), filter(nil), rowset=16 |
   |       access([VIEW2.EMP.__pk_increment], [VIEW2.EMP.EMP_ID], [VIEW2.EMP.MGR_ID], [VIEW2.EMP.POSITION])                         |
   |   4 - output([EMP.__pk_increment], [EMP.EMP_ID], [EMP.MGR_ID], [EMP.POSITION]), filter(nil), rowset=16                         |
   |       access([EMP.__pk_increment], [EMP.EMP_ID], [EMP.MGR_ID], [EMP.POSITION]), partitions(p0)                                 |
   |       is_index_back=false, is_global_index=false,                                                                              |
   |       range_key([EMP.__pk_increment]), range(MIN ; MAX)always true                                                             |
   +--------------------------------------------------------------------------------------------------------------------------------+
   25 rows in set (0.004 sec)

   ```

## 相关文档

- [SIMPLE SELECT](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001504437)
 - [WITH CLAUSE](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001504439)
 - [锁定查询结果 SELECT FOR UPDATE](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001500337)

 上一篇 下一篇 ![有帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*y6ocSqN8cqsAAAAAAAAAAAAAARQnAQ)![无帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*BG9IQJyLHF8AAAAAAAAAAAAAARQnAQ)![反馈](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*eTWdQKCRKHwAAAAAAAAAAAAAARQnAQ)[AI](https://www.oceanbase.com/obi) 咨询热线
