知识卡片

递归与传递闭包的不动点定义及SQL递归查询违反闭包的陷阱

结构图卡

内容

材料单场景(关系变量PP记录零件间的直接组成关系)需要递归求解传递闭包TC: 配对(px,py)出现在tc中,当且仅当它直接出现在pp中,或者存在某个pz使得(px,pz) 出现在pp中且(pz,py)出现在tc中(tc引用自身,是典型的递归定义)。这个定义有 一种等价的过程化/迭代实现:初始化TC:=PP,反复用TC UNION (PP和TC按PZ连接 再投影)更新TC,直到TC不再增长为止——直到到达一个”定点”(fixpoint),这种 用不动点迭代求解递归定义的思路,是关系代数处理层级/图结构数据的标准手法。 SQL的WITH RECURSIVE语法本质上是这套递归定义的直接翻译。但SQL在处理递归 查询时有一个容易被忽视的隐患:像Oracle早期的CONNECT BY那种实现方式,对 同一个终点存在多条不同路径时(比如零件P4既能通过P2到达也能通过P3到达P1), 会产生”看起来像重复行”、但实际携带着完全不同信息的结果行——两行(2,P4)分别 代表两条不同的路径,删除任何一行都会真的丢失信息,这和[[SQL查询结果中重复 的产生与真重复的辨识困境]]里讨论的”无意义重复行”有本质区别,但表面上二者 在SQL结果集里看起来一模一样,这正是[[关系代数的闭包性质]]被违反的一个具体 案例——这样的结果集里不仅有重复行,行的顺序本身还携带着必须保留的路径信息, 彻底破坏了关系”元组无序”的基本性质。递归数据里若存在环(比如交通网络中的 往返航线),传递闭包本身不受影响,但过程化求解容易陷入死循环,简单的”排除 回到起点”式技巧不足以根治问题(仍可能出现绕远路的死循环路径),SQL标准为此 专门提供了CYCLE子句,用于标记已访问过的节点、在检测到重复访问时提前终止 递归。

结构图

flowchart TD
    A["传递闭包递归定义<br/>tc(px,py) ⟺ pp(px,py) 或<br/>∃pz: pp(px,pz)∧tc(pz,py)"] --> B["不动点迭代求解<br/>TC:=PP; 循环 TC∪=新增配对<br/>直到TC不再增长"]
    B --> C["SQL WITH RECURSIVE<br/>= 递归定义的直接翻译"]
    A --> D["多路径到达同一终点<br/>= 表面重复行, 实携带不同信息"]
    D --> E["违反关系闭包性质<br/>删除/重排行都会丢失信息"]
    A --> F["数据含环 (如往返航线)<br/>过程化求解可能死循环"]
    F --> G["SQL CYCLE子句<br/>标记已访问节点提前终止"]

参考来源

- 位置:《SQL与关系数据库理论——如何编写健壮的SQL代码》第7章"SQL和关系代数 II:附加运算符"7.12节"对于递归的说明"(源文件:OEBPS/text00085.html) - 结论依据:原文明确"当且仅当满足下述条件之一时,配对(px,py)才会出现在 tc中:a.配对出现在pp中;b.存在某个pz……重复进行,直到中间结果到达了'定点' (fixpoint)……此结果不是一个关系(因此也违反了关系闭包性质)……这些重复 行并不是SQL中我们通常所理解的……重复行……如果删掉了其中一行,就丢失了 信息……SQL实际上就包含一个可在递归查询中用于此目的的特性——CYCLE子句"。 - 原始内容:重复进行,直到中间结果到达了"定点"(fixpoint)……此结果不是 一个关系(因此也违反了关系闭包性质)……如果删掉了其中一行,就丢失了信息 ……SQL实际上就包含一个可在递归查询中用于此目的的特性——CYCLE子句。