【发布时间】:2019-11-11 20:50:22
【问题描述】:
为了了解递归的行为(在 SQLite 中),我尝试了以下语句来使用递归语句对表的行重新编号:
让我们创建一个示例表,
CREATE TABLE tb
(x TEXT(1) PRIMARY KEY);
INSERT INTO tb
VALUES ('a'), ('b'), ('c');
并重新编号从 2 开始的行
SELECT tb.x as x, tb.rowid + 1 as idx from tb;
/* yields expected:
a|2
b|3
c|4
*/
尝试对递归 WITH(忽略 ROWID)执行相同操作,会导致分歧 — 在这里,我添加了 LIMIT 6 以防止分歧:
WITH RECURSIVE
newtb AS (
SELECT tb.x, 2 AS idx FROM tb
UNION ALL
SELECT tb.x, newtb.idx + 1
FROM tb, newtb
LIMIT 6 -- only to prevent divergence!
)
SELECT * FROM newtb;
/* yields indefinitely:
a|2
b|2
c|2
a|3
b|3
c|3
...
*/
为什么递归在到达表tb的末尾时没有停止?可以预防吗?
请注意,该问题可以重新表述为如何在 SQLite 中生成以下过程伪代码的结果(不用多说):
tb := {'a', 'b', 'c'};
num := {1, 2, 3};
result := {}; # initialize an empty table
for i in {1, ..., length(tb)} # assume index starts from 1
append tuple(num[i], tb[i]) to result;
end for
# result will be {(1, 'a'), (2, 'b'), (3, 'c')}
这相当于 Python 等语言中的zip 操作。
根据@CPerkins 的提示,可以通过window functions(对于SQLite >= 3.25)非常优雅地实现这一目标;例如,
SELECT (row_number() OVER (ORDER BY x)) + 2 AS newId, x FROM tb;
【问题讨论】:
-
第一个问题是没有关键字的连接——只有一个表的列表——产生两个源表的笛卡尔积。换句话说,
FROM tb, newtb是您在所有记录中获得重复组合的原因。这不仅在递归语句中是正确的,而且是应用任何此类连接的方式。 -
接下来,如果你没有 LIMIT 子句,那么你需要一个 WHERE 子句来真正停止递归。这就是为什么如果没有 LIMIT 6,您将获得无限数量的行。
-
当递归部分不返回任何行时,递归 cte 停止;你的总是每次都会返回一个。
-
将
rowid视为连续行号是错误的,因为前者并不总是连续的。 -
窗口函数更适合重新编号行。
标签: sqlite infinite-loop recursive-query