【问题标题】:SQLite loop statements?SQLite 循环语句?
【发布时间】:2011-09-10 09:10:33
【问题描述】:

SQLite 中是否有类似FOR .. in .. LOOP 之类的循环语句?我有两列StartRange, EndRange,我需要在其他表中插入整个序列。因此,如果StartRange 为 1,EndRange 为 3,则需要插入三个包含 1, 2, 3 的值。

【问题讨论】:

  • 恐怕你需要编写一些命令式代码来做到这一点,SQL 通常是一种声明性语言,只有它的扩展(如 T-SQL、SQL/PL 等)支持控制流结构像循环。但是,如果使用 SQLite 事务在一次运行中执行插入操作应该很容易。

标签: sql sqlite loops for-loop range


【解决方案1】:

您可以使用递归触发器在 SQL 中创建循环。使用mu 太短的架构

sqlite> create table t (startrange int not null, endrange int not null);
sqlite> insert into t values(1, 3);
sqlite> create table target (i int not null);

我们需要在 SQLite 中启用递归触发器:

sqlite> PRAGMA recursive_triggers = on;

制作一个临时触发器以循环到范围的末尾:

sqlite> create temp trigger ttrig
   ...> before insert on target
   ...> when new.i < (select t.endrange from t) begin
   ...> insert into target values (new.i + 1);
   ...> end;

开始吧:

sqlite> insert into target values ((select t.startrange from t));
sqlite> select * from target;
3
2
1
sqlite> 

【讨论】:

  • 对于大范围,这会导致“触发器递归级别过多”
【解决方案2】:

显然,SQLite 中的循环结构是 WITH RECURSIVE 子句。 该文档链接包含计数到十的示例代码、Mandelbrot 集绘图仪和数独解谜器,全部使用纯 SQL。 这是一个计算斐波那契数列的 SQLite 查询,让您感受一下:

sqlite> WITH RECURSIVE
   ...>   fibo (curr, next)
   ...> AS
   ...>   ( SELECT 1,1
   ...>     UNION ALL
   ...>     SELECT next, curr+next FROM fibo
   ...>     LIMIT 100 )
   ...> SELECT group_concat(curr) FROM fibo;
1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,...

这是Sieve of Eratosthenes

begin transaction;

drop table if exists naturals;
create table naturals
( n integer unique primary key asc,
  isprime bool,
  factor integer);

with recursive
  nn (n)
as (
  select 2
  union all
  select n+1 as newn from nn
  where newn < 1e4
)
insert into naturals
select n, 1, null from nn;

insert or replace into naturals
  with recursive
    product (prime,composite)
  as (
    select n, n*n as sqr
      from naturals
      where sqr <= (select max(n) from naturals)
    union all
    select prime, composite+prime as prod
    from
      product
    where
      prod <= (select max(n) from naturals)
  )
select n, 0, prime
from product join naturals
  on (product.composite = naturals.n)
;
commit;

【讨论】:

    【解决方案3】:

    如果你有一个额外的表来保存你需要的所有整数,你可以直接用 SQL 来做这种事情。

    假设您的 StartRangeEndRange 范围在 1 到 10 之间,并且您有一个这样的表:

    sqlite> select i from ints;
    i
    1
    .
    .
    .
    10
    

    此表仅包含您需要的所有可能的整数(即 1 到 10)。

    如果你也有这个:

    sqlite> create table t (startrange int not null, endrange int not null);
    sqlite> insert into t values(1, 3);
    sqlite> create table target (i int not null);
    

    您可以通过连接将您的 INSERT 插入到 target

    insert into target (i)
    select ints.i
    from ints join t on (ints.i >= t.startrange and ints.i <= t.endrange)
    

    结果是这样的:

    sqlite> select * from target;
    i
    1
    2
    3
    

    当然,您真正的 t 会有更多行,因此您需要一个 WHERE 子句来限制您查看 t 的哪一行。

    类似的事情通常用日期来完成(查找“日历表”)。

    因此,如果您的范围很小(对于 small 的某些定义),则生成一次 ints 表,为其添加索引,然后使用上述技术在内部执行所有 INSERT数据库。其他数据库有自己的方式(例如 PostgreSQL 的 generate_series)来做这种事情,而不需要显式的 ints 表,但 SQLite 是(有意)受到限制的。

    SQL 通常是基于集合的,因此循环不是自然的。通过描述您的需求来构建适当的集合是很自然的。 OTOH,有时不自然的行为是必要且明智的。

    我不知道这对您的应用程序是否有意义,我只是想演示一下如何完成。如果这种方法在您的情况下没有意义,那么您可以在数据库之外生成一堆 INSERT 语句。

    【讨论】:

    • SQLite 表具有可用于查询的行号。
    • @reinierpost 你指的是rowid吗?
    • 是的。 (还有你的名字,还是太短了)
    • @reinierpost 但是如果rowids 中有空白怎么办?或者如果它们与所需范围不匹配?
    • 是的,对于明确的范围,使用明确的行号似乎更好。但 kseen 也需要担心出现差距的可能性。
    猜你喜欢
    • 1970-01-01
    • 2015-12-11
    • 1970-01-01
    • 1970-01-01
    • 2019-06-18
    • 2017-06-20
    • 2019-08-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多