首页 游戏 软件 资讯 排行榜 专题
首页
数据库
SQL如何实现递归关联查询_在PostgreSQL和MySQL8中使用WITH_RECURSIVE

SQL如何实现递归关联查询_在PostgreSQL和MySQL8中使用WITH_RECURSIVE

热心网友
58
转载
2026-04-24

PostgreSQL 和 MySQL 8 都支持 WITH RECURSIVE,但写法、限制和默认行为有实质差异,不能直接复用同一段 SQL。

SQL如何实现递归关联查询_在PostgreSQL和MySQL8中使用WITH_RECURSIVE

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈

先说一个核心结论:PostgreSQL 和 MySQL 8 虽然都支持 WITH RECURSIVE 语法,但两者在细节上的差异,足以让一段在 PostgreSQL 上运行良好的递归查询,在 MySQL 里直接“罢工”。简单来说,它们都支持递归,但规矩不太一样,直接复制粘贴大概率会出问题。

MySQL 8 的 WITH RECURSIVE 必须显式加 RECURSIVE 关键字

这是第一个容易踩的坑。MySQL 对语法要求非常严格,WITH RECURSIVE 中的 RECURSIVE 关键字绝对不能省略。如果你漏掉了,MySQL 可不会去猜测你的意图,它会直接报错,比如 ERROR 1248 (42000): Every derived table must ha ve its own alias,或者给出一些更隐晦的解析失败信息。

相比之下,PostgreSQL 就“聪明”得多。当它发现 WITH 子句里的公共表表达式(CTE)引用了自身时,会自动推断这是一个递归查询,所以 RECURSIVE 关键字是可选的。当然,为了清晰起见,加上它总是个好习惯。

  • 正确写法(MySQL)WITH RECURSIVE cte AS (...)
  • 错误写法(MySQL)WITH cte AS (...) —— 即使查询体里包含了 UNION ALL 也不行。
  • 正确写法(PostgreSQL)WITH RECURSIVE cte AS (...)WITH cte AS (...) 都可以。

所以,最稳妥的迁移策略是什么?别图省事。保留 RECURSIVE 关键字,这样写出来的 SQL 在两个数据库里都能兼容,是改动最小、风险最低的方案。

锚点查询(Anchor Member)里不能用 ORDER BY、LIMIT、GROUP BY

这个限制是 PostgreSQL 和 MySQL 8 共有的,而且是个硬性规定。所谓“锚点查询”,就是递归查询里 UNION ALL 之前的那部分,它定义了递归的起点。

如果你在锚点查询里加上了 ORDER BY,MySQL 会直接报错:ERROR 3577 (HY000): Recursive common table expression anchor member cannot ha ve ORDER BY。PostgreSQL 虽然不会报错,但它会默默地忽略掉这个 ORDER BY 子句。这意味着,你原本希望通过排序来控制递归展开顺序(比如优先展开某个子树)的想法,在 PostgreSQL 里也会落空,结果顺序变得不可预测。

  • 错误示例SELECT ... FROM t WHERE id = 1 ORDER BY sort_order —— 在 MySQL 里会执行失败,在 PostgreSQL 里排序无效。
  • 正确做法:把排序逻辑移到最外层的最终 SELECT 语句里。例如:SELECT * FROM cte ORDER BY level, name
  • ⚠️ 重要提醒:递归的层级深度是由数据本身的关联关系决定的,而不是通过在锚点里排序就能控制的。

递归终止靠“无新行产生”,不是靠 WHERE 条件主动截断

这是一个非常关键且常见的误解。很多人以为,在递归部分(UNION ALL 之后的部分)加一个像 WHERE level < 5 这样的条件,就能安全地限制递归深度。其实不然。

这个 WHERE 条件仅仅过滤了当前这一轮递归产生的结果行,但它并不能阻止下一轮递归的执行。递归真正停止的唯一条件是:某一轮递归查询产生的结果集为空,没有新行被加入到 CTE 中。

如果连接条件写错了(比如本应是 ON d.parent_id = r.id,却写成了 ON d.id = r.parent_id),很可能导致逻辑上的无限循环,或者查询卡住。

  • 危险写法SELECT ... FROM dept d JOIN cte r ON d.parent_id = r.id WHERE r.level < 4 —— 这里的 r.level 是上一轮的结果,它不会阻止本轮生成 level=5 的行,只要连接条件满足。
  • 安全写法:正确的做法是在递归分支的 SELECT 列表中计算层级,并在 WHERE 子句中对其进行判断。例如:SELECT ..., r.level + 1 AS level FROM ... WHERE r.level < 4。这样,当层级达到4时,就不会再产生新的 level=5 的行了。
  • ? 数据库差异:PostgreSQL 还提供了一个“安全阀”——可以通过设置 max_recursion_depth 参数来限制最大递归深度(通常需要超级用户权限)。而 MySQL 目前没有等效的全局配置,深度控制完全依赖于查询逻辑本身。

字段对齐和别名必须严格一致

这是另一个容易出错的细节。UNION ALL 连接的两个部分(锚点查询和递归查询),其输出列的数量、数据类型和顺序必须完全匹配,否则数据库会直接报错。

一个典型的坑是:锚点查询选择了 id, name, parent_id 三列,而递归查询为了记录层级,多选了一个 level 列,变成了四列。这就会导致执行失败。

  • 错误示例
    锚点: SELECT id, name, parent_id FROM t WHERE id = 1
    递归: SELECT id, name, parent_id, level+1 FROM ... (多了一列)
  • 统一写法:必须在锚点查询里就把所有列补齐。
    锚点: SELECT id, name, parent_id, 0 AS level FROM t WHERE id = 1
    递归: SELECT d.id, d.name, d.parent_id, r.level + 1 FROM t d JOIN cte r ON d.parent_id = r.id
  • ? 类型注意:MySQL 对数据类型的隐式转换更为敏感(例如 VARCHARCHAR 混用可能有问题),PostgreSQL 相对宽松,但仍需注意精度截断等潜在风险。

最后,还有一个最容易被忽略的逻辑陷阱:递归方向与连接条件的对应关系。向下查询子节点时,连接条件通常是 child.parent_id = parent.id;而向上查询父节点时,必须反过来写成 parent.id = child.parent_id。这两者看似对称,但如果写反了,不会报语法错误,只会默默地返回空结果集或者逻辑混乱的数据,排查起来相当棘手。这才是关键所在。

来源:https://www.php.cn/faq/2324802.html
免责声明: 游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

相关攻略

MySQL视图与用户权限管理从入门到精通
数据库
MySQL视图与用户权限管理从入门到精通

1 视图 1 1 视图的基本概念 想象一下,你面前有一张表格,但它并不真正存在于数据库的物理存储中,而是由查询语句动态生成的。这就是视图。你可以把它理解为一个“虚拟表”,它的数据来源于一个或多个基础表(或其他视图)的查询结果。用户可以对视图进行查询、更新等操作,就像操作一张普通的表一样。关键在于,

热心网友
04.24
mysql并发更新同一行数据怎么办_利用乐观锁或分段更新优化
数据库
mysql并发更新同一行数据怎么办_利用乐观锁或分段更新优化

MySQL并发更新同一行数据怎么办?利用乐观锁或分段更新优化 先说结论:最稳妥的方案,是优先采用带条件的 UPDATE 配合 ROW_COUNT() 检查,并结合 version 字段实现乐观锁。至于分段更新,它只在批量修正这类少数场景中作为兜底手段,绝不能替代核心的并发控制逻辑。 为什么不能指望

热心网友
04.23
MySQL数据库异构迁移面临的挑战_转换数据类型与存储引擎
数据库
MySQL数据库异构迁移面临的挑战_转换数据类型与存储引擎

MySQL异构迁移:四大核心挑战与实战应对指南 直接说结论:一次成功的MySQL异构迁移,远不止是数据搬运。它更像是一次精密的“器官移植”,需要针对不同“组织”的特性进行预处理。整个过程可以归纳为四类核心问题的系统化处理:时间类型必须按UTC显式转换并规避自动更新陷阱;存储引擎切换应禁用简单的ALT

热心网友
04.23
mysql如何处理mysql服务无法启动_查看error日志排查原因
数据库
mysql如何处理mysql服务无法启动_查看error日志排查原因

MySQL服务启动失败?别慌,先看懂error log在说什么 遇到MySQL服务启动失败,很多人的第一反应是重装或者四处搜索错误代码。其实,最直接、最准确的“故障诊断书”就在眼前——那就是MySQL的error log。问题在于,很多人要么找不到它,要么面对满屏的日志信息不知从何看起。今天,我们就

热心网友
04.23
mysql数据意外丢失该怎么找回_InnoDB事务日志RedoLog灾备原理
数据库
mysql数据意外丢失该怎么找回_InnoDB事务日志RedoLog灾备原理

MySQL数据意外丢失该怎么找回:InnoDB事务日志RedoLog灾备原理 开门见山,先说一个核心结论:当数据库遭遇误删,很多人第一时间想到的REDO LOG,其实**并不能直接帮你“找回”数据**。无论是手滑执行了DROP DATABASE,还是跑错了DELETE FROM语句,指望REDO L

热心网友
04.23

最新APP

宝宝过生日
宝宝过生日
应用辅助 04-07
台球世界
台球世界
体育竞技 04-07
解绳子
解绳子
休闲益智 04-07
骑兵冲突
骑兵冲突
棋牌策略 04-07
三国真龙传
三国真龙传
角色扮演 04-07

热门推荐

html中的dialog标签怎么用?
前端开发
html中的dialog标签怎么用?

HTML中的dialog标签怎么用? 很多开发者第一次接触 标签时,都会有个美丽的误会:以为把它写进HTML,页面就会自动弹出一个对话框。其实不然,这个标签的默认状态是“隐藏”的。你可以把它想象成一扇关着的门——写了标签只是造好了门框,想让门打开,你得要么手动加上 open 属性,要么用Ja vaS

热心网友
04.24
如何为响应式下拉菜单添加可点击关闭的“X”按钮
前端开发
如何为响应式下拉菜单添加可点击关闭的“X”按钮

本文介绍如何在基于 CSS 媒体查询和 checkbox 的响应式导航菜单中,通过重构 HTML 结构并结合轻量 Ja vaScript,实现点击汉堡图标展开菜单、再点击右上角“×”按钮即时收起的功能,解决纯 CSS 方案无法主动关闭的问题。 你是否遇到过这样的场景?在移动端,用户点击汉堡图标打开了

热心网友
04.24
如何用 Array.prototype.entries 配合 for...of 在遍历数组的同时获取索引和值
前端开发
如何用 Array.prototype.entries 配合 for...of 在遍历数组的同时获取索引和值

如何用 Array prototype entries 配合 for of 在遍历数组的同时获取索引和值 entries() 返回的是什么类型的迭代器 先说清楚一个核心概念:Array prototype entries() 返回的,是一个标准的数组迭代器对象。这意味着,每次调用它的 next(

热心网友
04.24
伊朗驳斥特朗普所谓分裂内斗
web3.0
伊朗驳斥特朗普所谓分裂内斗

伊朗驳斥特朗普所谓“分裂内斗”论调:美方言论被指为心理投射 近日,围绕伊朗国内局势的表述,美伊之间再次上演了一场外交言辞交锋。这场对话的焦点,似乎已悄然发生了转移。 谈判重心的转向与核心关切的明确 根据伊朗外交部发言人纳赛尔·卡纳尼的表态,一个关键信号已经释放:当前伊美谈判的重心,已不再局限于核问题

热心网友
04.24
HTML怎么做复古风格_html复古怀旧风格页面实现【手册】
前端开发
HTML怎么做复古风格_html复古怀旧风格页面实现【手册】

真正复古的CRT效果需叠加扫描线与亚像素抖动:用repeating-linear-gradient生成2px间距、rgba(0,0,0,0 08)透明度的黑色条纹层,并配以transform: translateX(0 5px) translateY(-0 3px)和steps(1)动画,辅以bac

热心网友
04.24