锚成员与递归成员
了解递归 CTE 的两部分结构,以及终止机制的工作方式
锚成员与递归成员 是 CoddyKit 上的免费 Coding Interview Prep 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Coding Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Coding Interview Prep 课程共包含 4 节课。
为什么会考递归 CTE
当面试官给您一张组织架构图、一份物料清单或一棵分类树,并要求您找出所有后代节点时,他们是在测试您是否会想到使用递归 CTE。普通连接只能遍历固定层数;递归则可以遍历任意深度。
题目中的提示性短语通常是“任意深度”或“一直向下”。这就是您的信号。在本课中,您将学习每个递归 CTE 都具备的两部分结构:锚点成员和递归成员。
两部分骨架
递归 CTE 始终包含关键字 WITH RECURSIVE(PostgreSQL、SQLite、MySQL 8+;SQL Server 省略 RECURSIVE),其主体由两个通过 UNION ALL 组合的查询组成:
- 锚点成员 — 起始行,只运行一次。
- 递归成员 — 引用 CTE 自身的名称,反复运行。
请记住这个骨架;面试官很喜欢要求您从头写出它。
WITH RECURSIVE cte AS (
-- anchor member
SELECT ...
UNION ALL
-- recursive member
SELECT ... FROM cte JOIN ...
)
SELECT * FROM cte;锚点的作用
锚点成员是一个不引用 CTE 的普通查询。它生成种子行,也就是第 0 层的起点。对于组织架构图,通常是 CEO(经理为 NULL 的那一行);对于数字序列,则是第一个数字。
锚点恰好运行一次。它的输出会成为传入递归步骤的第一批行。
-- Anchor: the top of the hierarchy
SELECT id, name, manager_id, 1 AS depth
FROM employees
WHERE manager_id IS NULL递归成员的作用
递归成员通过名称引用 CTE。在每次迭代中,它会将上一次迭代产生的行与基础表连接起来,从而找出下一层。
它看不到目前为止的整个 CTE,只能看到紧接着前一步新增的行。这是面试官重点考察的关键思维模型。
-- Recursive: children of the rows found so far
SELECT e.id, e.name, e.manager_id, c.depth + 1
FROM employees e
JOIN cte c ON e.manager_id = c.id组合起来
使用 UNION ALL 将锚点成员和递归成员组合起来,数据库引擎就会自动进行迭代。每次运行都会追加下一层,直到递归成员返回零行,此时递归停止。
下面是一个完整且可运行的组织架构遍历示例,同时还会跟踪 depth。
WITH RECURSIVE org AS (
SELECT id, name, manager_id, 1 AS depth
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT e.id, e.name, e.manager_id, o.depth + 1
FROM employees e
JOIN org o ON e.manager_id = o.id
)
SELECT id, name, depth FROM org ORDER BY depth, id;递归如何终止
当递归成员生成没有新行时,递归就会停止。无需显式的循环计数器 — 到达树的叶节点后,连接自然就无法继续产生结果。
在组织架构示例中,到达没有直接下属的员工后,下一次迭代的连接找不到子节点,返回空结果,引擎便会停止。理解这种自动终止行为是一个经典的追问点。
UNION ALL 与 UNION
面试官经常会问,为什么使用 UNION ALL 而不是 UNION。原因有两个:
- 性能 —
UNION会在每次迭代中去重,代价很高。 - 正确性 — 在树结构中通常不会出现重复行,因此去重只是浪费工作。
只有当结构是图,并且您确实想要合并重复节点时,才使用 UNION — 但为了防止循环,显式的保护条件更好(后文会介绍)。
跟踪深度和路径
增加两列可以让递归结果实用得多,面试中也经常会被要求这样做:
- 深度 — 在锚点中从 1 开始,在递归成员中加 1。
- 路径 — 累积标识符或名称组成的链路,以便查看从根节点到当前节点的路线。
将 path 构建为字符串,稍后还可以将其作为检测循环的工具。
WITH RECURSIVE org AS (
SELECT id, name, manager_id, 1 AS depth,
CAST(name AS VARCHAR(1000)) AS path
FROM employees WHERE manager_id IS NULL
UNION ALL
SELECT e.id, e.name, e.manager_id, o.depth + 1,
o.path || ' > ' || e.name
FROM employees e JOIN org o ON e.manager_id = o.id
)
SELECT name, depth, path FROM org;列类型必须匹配
有一个容易忽略的问题:锚点成员和递归成员必须返回相同数量的列,并且列的类型兼容。如果您要构建 path 字符串,锚点中的初始值必须转换为长度足够大的类型(例如 VARCHAR(1000)),否则引擎可能会截断值,或在后续迭代中抛出类型不匹配错误。
面试官正是会埋下这种细节,来判断您是否真正运行过递归 CTE,而不只是读过相关介绍。
物料清单示例
同一个骨架也能解决物料清单问题:给定一个零件,列出任意深度的所有子零件。锚点选择顶层组件;递归成员沿着从 parent_part 到 child_part 的连接进行遍历。
请注意,其结构与组织架构图完全相同 — 只有列名称发生了变化。认识到同一个骨架可以适用于许多问题,才是真正的面试技能。
WITH RECURSIVE bom AS (
SELECT child_part, parent_part, 1 AS lvl
FROM parts WHERE parent_part = 'ENGINE'
UNION ALL
SELECT p.child_part, p.parent_part, b.lvl + 1
FROM parts p JOIN bom b ON p.parent_part = b.child_part
)
SELECT child_part, lvl FROM bom;方言说明
面试官很欣赏下面这份简明的跨方言速查表:
- PostgreSQL、SQLite、MySQL 8+:
WITH RECURSIVE name AS (...)。 - SQL Server:只需使用
WITH name AS (...)—RECURSIVE关键字是隐式的,并且默认将MAXRECURSION限制为 100。 - Oracle:同时支持递归 CTE 和较旧的
CONNECT BY语法。
说出“SQL Server 不使用 RECURSIVE 这个词”,就能体现出真正的知识广度。
快速检查
请测试您对这两部分结构的掌握程度。
回顾
您现在已经掌握了递归 CTE 的骨架:
- WITH RECURSIVE + 锚点 +
UNION ALL+ 递归成员。 - 锚点提供第 0 层的种子,并运行一次。
- 递归成员将上一次迭代与基础表连接,并持续运行,直到返回零行。
- 使用
UNION ALL,跟踪depth和path,并确保列类型兼容。
下一步:使用这个骨架向下和向上遍历真实的组织架构图。
常见问题解答
「锚成员与递归成员」课时是免费的吗?
是的 — 「锚成员与递归成员」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Coding Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 Coding Interview Prep 课程共包含 4 节课。
「锚成员与递归成员」这节课中我会学到什么?
了解递归 CTE 的两部分结构,以及终止机制的工作方式 你通过在浏览器中直接运行的动手代码来练习 Coding Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Coding Interview Prep 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Coding Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「锚成员与递归成员」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Coding Interview Prep 课中编写并运行代码吗?
能。每节 Coding Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。