用递归语句对表行重新编号

时间:2019-11-11 20:50:22

标签: sqlite infinite-loop recursive-query

要了解递归的行为(在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;

1 个答案:

答案 0 :(得分:0)

  

为什么递归到达表tb的末尾时不停止?

因为这就是它的设计方式,所以它非常有用。它与大多数具有某种形式的递归的语言几乎没有什么不同,并且通常是解决某些编程问题(例如遍历目录树)的有效途径。

  •   

    大多数计算机编程语言都通过允许   函数从自己的代码中调用自身。一些功能   编程语言没有定义任何循环结构,而是依赖   完全基于递归来反复调用代码。可计算性理论   证明这些仅递归语言是图灵完整的;他们   与图灵完全命令一样强大的计算能力   语言,这意味着它们可以解决与   命令式语言,甚至没有迭代控制结构,例如   时间和时间。Recursion (computer science)

如果您使用LIMIT(SELECT tb()from tb)而不是LIMIT 6,则基于表中的行数,递归将停止。

但是,如果您要重新编号(通过在rowid上加1),那么您将看到的像是:-

WITH RECURSIVE 
    cte(idx,newidx) AS (
        SELECT (SELECT max(rowid) FROM tb),(SELECT max(rowid) FROM tb) +1
        UNION ALL
        SELECT 
        idx-1, newidx-1 FROM cte
        WHERE idx > 0
    )
SELECT (SELECT x FROM tb WHERE tb.rowid = cte.idx) AS x, newidx, idx AS original FROM cte WHERE x IS NOT NULL;

这将(假设tb包含具有a,b和c .... X,Y和Z的行,并且删除了d-w行)导致:-

enter image description here

SQlite的推理是:-

  

递归公用表表达式提供了执行   树和图的分层或递归查询,一种功能   这是SQL语言无法提供的。   SQL As Understood By SQLite - WITH clause

     

这可以预防吗?

是的,您不能使用递归,因为可能有其他选择,但是与递归其他语言一样,如果您确实使用递归,则必须具有某种方法来检测何时应递归。使用WHERE或LIMIT子句可以简化这一过程。