sqlite3-树形递归查询-with

在一个递归公共表表达式里,递归表用CTE表名命名。在上面的递归公共表表达式示意图中,递归表必须且只能出现在递归查询的FROM子句里,不能
出现在initial-select或者the recursive-select,包括subqueries的任何地方。

初始查询可以是一个联合查询,但是它不能包含ORDER BY, LIMIT, OFFSET.

递归查询必须是一个简单查询,而不能是一个联合查询语句。递归查询允许包含ORDER BY, LIMIT, OFFSET.

执行递归查询表内容的基本算法如下:
1.执行初始查询并将查询结果放入一个队列;
2.如果队列不为空
  1.从队列中提取一条记录;
  2.将这条记录插入递归表;
  3.假设刚刚被提取的记录是递归表中唯一一条记录,然后,运行递归查询,把所有结果放入队列

以上,基本过程可能被以下附加规则修改:

如果,用一个UNION操作符把初始查询和递归查询衔接起来,那么,仅仅当这条记录不在队列时,才将这条记录添加到队列中。重复的记录被丢弃,在加入队列之前。
即使在递归查询时,重复的队列已经从队列中提取出来,如果,操作符是UNION ALL,那么在初始查询和递归查询阶段产生的记录总是被添加到队列当中,即使他们存在重复。
判断记录是否重复,NULL值与另一个NULL值比较是相等的,和其他值比较是不相等的。

LIMIT子句,如果出现,判断递归表中记录的个数,一旦达到指定记录个数,递归停止。一个以0作为限制数值意味阒没有记录被添加到递归表。
一个负值意味着添加到递归表的记录个数是没有限制的。

OFFSET子句,如果出现,有一个正值N, 会阻止前N条记录添加到递归表中。前N条记录仍然被递归查询处理。只是他们不添加到递归表中。当所有OFFSET记录被跳过,才开始记录
记录个数,直到达到限制数值。

如果有ORDER BY子句出现,它会在步骤2a中,决定队列中记录被提取的顺序,如果没有ORDER BY, 那么在被提取的记录顺序是未定义的。在当前实现中,如果省略ORDER BY子句,队列是一个先进先出队列。

但是应用程序不应该依赖这一个事实,因为它可能会改变。

递归查询示例:
  以下查询返回1到1000000之间的所有整数。

with recursive
     cnt(x) as (values(1) union all select x+1 from cnt where x < 1000000 br style=‘font-size:16px;font-style:normal;font-weight:normal;color:rgb(0, 0, 0);‘ />  select * from cnt;

思考这个查询是如何工作的,

初始查询首先运行和返回一个单一字段的单一记录1,这条记录被添加到队列中。
  在步骤2a中,这条记录从队列中移出并添加到递归表中,
  接着,执行递归查询。
  按照步骤2c,将产生的单一记录2添加到队列中。这里队列仍然还有一条记录,因此步骤2继续重复。
  记录2按照步骤2a和2c被提取并添加到递归表中,接着记录2被使用,就好像它是递归表中的全部内容,递归查询继续。结果产生一条记录3,并被添加到队列
  这样重复999999次,直到最后队列中包含的记录为1000000,这条记录被提取和添加到递归表,但是,这次,where子句会引起递归查询返回结果无记录,因此队列
剩下为空和递归结束。

优化注意事项:

在上面的讨论中,如“插入行插入递归表”报表应在概念上理解,不能从字面上。
这听起来好像是SQLite的积累含有一百万行一个巨大的表,然后回去和扫描该表由上到下产生的结果。
实际的情况是,查询优化器认为,在“CNT”递归表值只使用一次。
从而各行被添加到递归表,该行被立即返回作为主SELECT语句的结果,然后丢弃。
SQLite不累积一百万行的临时表。很少的内存是需要运行上面的例子。
但是,如果例如曾使用UNION代替UNION ALL,那么SQLite的将不得不保持所有以前生成的内容,以检查重复。
出于这个原因,程序员应该努力利用一切,而不是UNION UNION,可行的情况下。

在以上讨论中,像插入记录到递归表中的语句应该在概念上理解,不能从字面上,它听起来就像是sqlite正在积累一张含有一百万行的巨大的表,
    然后回去和从上到下扫描该表并产生结果,实际的情况是,查询优化器认为,在"cnt"递归表中的值仅仅被使用一次,因此,当一条记录被添加到递归表时,
    记录直接作为主查询语句的结果返回,然后丢弃。sqlite不会累积一张一百万行的临时表。运行以上示例,只需很少的内存空间。无论怎么样,如果示例
    使用UNION代替UNION ALL,然后sqlite不得不保留所有先前产生的记录内容,以检查是否重复。
    因为这个原因,在可行情况下,程序员应该努力使用UNION ALL代替UNION.

对上面的例子做一些改变,如下:

with recursive
        cnt(x) as (select 1 union all select x+1 from cnt limit 1000000)
        select * from cnt;

这里有两个地方不同,初始查询用"SELECT 1"代替"VALUES(1)".但是这些只不过是利用不同的语句做相同的事。另一个不同的地方是递归结束通过一个LIMIT而不是
一个WHERE子句,使用LIMIT意味着当一百万行记录添加到递归表时(主查询执行返回,由于查询优化器),接着递归直接结束而不管在队列中还有多少条记录。
在一个更复杂的查询中,它有时是很难的,要保证where子句最终引起队列为空和递归中止,但是,LIMIT子句总是能会停止递归。如果递归记录的上界大小是已知的,
为安全起见,总是包含一个LIMIT子句是一个好的方式。

分层查询示例:

创建一张表,描述一个组织的成员以及组织内部的关系链

CREATE TABLE org(
  name TEXT PRIMARY KEY,
  boss TEXT REFERENCES org,
  height INT,
  -- other content omitted
);

在组织里的每个成员都有一个名字,所有的成员只有一个老板,也就是整个组织的最顶端,这个表的所有记录关系形成一个棵树。

这里有一个查询,计算ALICE组织部门中每个人的平均体重,包括ALICE

WITH RECURSIVE
  works_for_alice(n) AS (
    VALUES(‘Alice‘)
    UNION
    SELECT name FROM org, works_for_alice
     WHERE org.boss=works_for_alice.n
  )
SELECT avg(height) FROM org
WHERE org.name IN works_for_alice;

下面一个例子在一个WITH子句中,使用两个公共表表达式,以下表表示一个家庭树

CREATE TABLE family(
  name TEXT PRIMARY KEY,
  mom TEXT REFERENCES family,
  dad TEXT REFERENCES family,
  born DATETIME,
  died DATETIME, -- NULL if still alive
  -- other content
);

这个家庭表跟之前的组织表是相似的,除了每个成员都有两个父结点。我们想要知道ALICE所有健在的祖辈,从老到小。一个普通的公共表表达式,"parent_of",首先被定义
这个普通公共表表达式是一个视图,被用来查找每个人的所有父辈。普通公共表表达式在递归公共表表达式ancestor_of_alice中使用.
接着,递归公共表表达式在后面查询被使用:

WITH RECURSIVE
  parent_of(name, parent) AS
    (SELECT name, mom FROM family UNION SELECT name, dad FROM family),
  ancestor_of_alice(name) AS
    (SELECT parent FROM parent_of WHERE name=‘Alice‘
     UNION ALL
     SELECT parent FROM parent_of JOIN ancestor_of_alice USING(name))
SELECT family.name FROM ancestor_of_alice, family
WHERE ancestor_of_alice.name=family.name
   AND died IS NULL
ORDER BY born;

查询图表:
  版本控制系统通常存储每个工程的变化版本,作为一个有向无环图,调用项目的每个版本签入,一次签入可能是0或者有很多的父节点。
大部分签入,除了第一次,有一个父节点,但是,在合并情况下时,一个签入可能有两,三个或者更多的父节点。跟踪签入的和它们发生的顺序的模式,
就像如下所示:

CREATE TABLE checkin(
  id INTEGER PRIMARY KEY,
  mtime INTEGER -- timestamp when this checkin occurred
);
CREATE TABLE derivedfrom(
  xfrom INTEGER NOT NULL REFERENCES checkin, -- parent checkin
  xto INTEGER NOT NULL REFERENCES checkin,   -- derived checkin
  PRIMARY KEY(xfrom,xto)
);
CREATE INDEX derivedfrom_back ON derivedfrom(xto,xfrom);

此图是无环图,我们假定每个孩子签入不超过其所有父节点的修改时间,但是,与前面的例子不同的是,这个图在任何两次签入之间的可能有多条不同长度的路径。
我们想要知道在时间线上最近的20次签入,对于签入"@BASELINE,(在整个DAG有成千上万个祖先)这个查询类似于使用Fossil版本控制系统,显示最近的N个签入。
示例: http://www.sqlite.org/src/timeline?p=trunk&n=30

WITH RECURSIVE
  ancestor(id,mtime) AS (
    SELECT id, mtime FROM checkin WHERE [email protected]
    UNION
    SELECT derivedfrom.xfrom, checkin.mtime
      FROM ancestor, derivedfrom, checkin
     WHERE ancestor.id=derivedfrom.xto
       AND checkin.id=derivedfrom.xfrom
     ORDER BY checkin.mtime DESC
     LIMIT 20
  )
SELECT * FROM checkin JOIN ancestor USING(id);

在递归查询里按时间降序会使得查询执行得更快,通过防止它从很早之前合并签入的分支遍历。order by 使得递归查询把重点放在最近签入的记录上,刚好也是我们想要得到的。
如果在递归查询中没有使用order by, 一个可能是遍历所有成千上万的提交记录,按时间线重对它们进行排序。接着,返回前20条记录。在order by基础上建立一个优先级队列
使得递归查询首先查找最近的提交记录。允许使用LIMIT子句,使得查询范围限制在感兴趣的签入记录上。

通过使用ORDER BY,深度优先对比广度优先搜索遍历树,
ORDER BY子句的递归查询可以用来控制搜索树是否是深度优先或广度优先。为了说明这一点,我们将对上面示例中的ORG表进行一处修改,没有了Height列,
并且插入以下这些数据。

CREATE TABLE org(
  name TEXT PRIMARY KEY,
  boss TEXT REFERENCES org
) WITHOUT ROWID;
INSERT INTO org VALUES(‘Alice‘,NULL);
INSERT INTO org VALUES(‘Bob‘,‘Alice‘);
INSERT INTO org VALUES(‘Cindy‘,‘Alice‘);
INSERT INTO org VALUES(‘Dave‘,‘Bob‘);
INSERT INTO org VALUES(‘Emma‘,‘Bob‘);
INSERT INTO org VALUES(‘Fred‘,‘Cindy‘);
INSERT INTO org VALUES(‘Gail‘,‘Cindy‘);

这里有一个查询对树结构,采用深度优先策略

WITH RECURSIVE
  under_alice(name,level) AS (
    VALUES(‘Alice‘,0)
    UNION ALL
    SELECT org.name, under_alice.level+1
      FROM org JOIN under_alice ON org.boss=under_alice.name
     ORDER BY 2
  )
SELECT substr(‘..........‘,1,level*3) || name FROM under_alice;

在“ORDER BY2”(即等同于“ORDER BY under_alice.level+1”)会导致组织结构图中更高层次的成员(用较小的“级别”的值)能够得到优先处理,造成了广度优先搜索。
输出结果为:

Alice
...Bob
...Cindy
......Dave
......Emma
......Fred
......Gail

但是,如果我们改变了ORDER BY子句添加“DESC”修饰符,这将导致在组织中较低层次(与较大的“级别”的值)优先处理,造成了深度优先搜索:

WITH RECURSIVE
  under_alice(name,level) AS (
    VALUES(‘Alice‘,0)
    UNION ALL
    SELECT org.name, under_alice.level+1
      FROM org JOIN under_alice ON org.boss=under_alice.name
     ORDER BY 2 DESC
  )
SELECT substr(‘..........‘,1,level*3) || name FROM under_alice;

修改后的查询结果为:

Alice
...Bob
......Dave
......Emma
...Cindy
......Fred
......Gail

当在递归查询中省略OREDER BY时,队列就像是一个FIFO, 这造成了广度优先搜索

时间: 2024-07-31 01:12:35

sqlite3-树形递归查询-with的相关文章

sqlite3树形结构遍历效率对照測试

sqlite3树形结构遍历效率对照測试 一.缘起 项目数据结构:本人从事安防行业,视频监控领域.项目中会遇到监控点位的组织机构划分.暂时划分的巡逻点位等.这些相机点位.连同组织机构,它们在逻辑关系上构成了一个树形结构. 又因为不论什么一个点位属于一个组织机构,也可能属于一个被暂时创建的视频巡逻计划中,因此,能够看出,不论什么一个节点,包含相机节点和组织机构节点,都有可能有至少一个父级节点,且不论什么一个组织机构节点也会有多个下级子节点.这中逻辑关系又构成了图. 数据量规模:一个市级别的管理平台,

Oracle 内置sql函数大全

F.1字符函数--返回字符值 这些函数全都接收的是字符族类型的参数(CHR除外)并且返回字符值.除了特别说明的之外,这些函数大部分返回VARCHAR2类型的数值.字符函数的返回类型所受的限制和基本数据库类型所受的限制是相同的,比如: VARCHAR2数值被限制为2000字符(ORACLE 8中为4000字符),而CHAR数值被限制为255字符(在ORACLE8中是2000).当在过程性语句中使用时,它们可以被赋值给VARCHAR2或者CHAR类型的PL/SQL变量. ASCII 语法:ascii

报表开发实例——动态多层次KPI钻取报表(I)

在报表项目中有时会有动态层次报表,而且还需要层次钻取的场景,开发难度较大.这里记录了使润乾集算报表开发<各级部门KPI报表>的过程. <各级部门KPI报表>初始状态如下图: 当前节点是根节点"河北省",要求报表显示当前节点的下一级节点"地市"汇总的KPI数值.Kpi又分为普通指标和VIP指标两类,共四项.如果点击"石家庄"来钻取的时候,要求能够将石家庄下一级的KPI汇总指标显示出来,如下图: 点击"中心区&quo

mysql树形结构递归查询

之前一直用的是Oracle,对于树形查询可以使用start with ... connect by select * from menu start with id='130000' connect by id = prior parent_id; 没错,这是Oracle所支持的 现在公司用的是mysql,对于这种查询方式只能通过sql语句实现了 语言都是相通的,何况sql呢 mysql随没有自带的语法支持,不过可以通过创建函数来实现递归查询. 如下图所示... 直接上sql语句 create

Oracle树形表和递归查询

地址:https://blog.csdn.net/hellowordapi/article/details/75763432 在平常的业务系统开发中,我们经常需要设计数据层次关系,如在经典的user-role-permission权限设计中, 需要对权限表的数据设计成一种层次依赖关系,如最顶层的为系统管理,系统管理的下一层为角色 管理,角色管理的下一层又为角色的CRUD操作, 那么这种表就可以抽象成为数据结构里面的B树. 如下表 : CREATE TABLE "U_PERMISSION"

sqlserver实现树形结构递归查询(无限极分类)

SQL Server 2005开始,我们可以直接通过CTE来支持递归查询,CTE即公用表表达式 百度百科 公用表表达式(CTE),是一个在查询中定义的临时命名结果集将在from子句中使用它.每个CTE仅被定义一次(但在其作用域内可以被引用任意次),并且在该查询生存期间将一直生存.可以使用CTE来执行递归操作.创建的语法是: with <name of you cte>(<column names>) as( <actual query> ) select * from

SQL Server 树形表非循环递归查询

很多人可能想要查询整个树形表关联的内容都会通过循环递归来查...事实上在微软在SQL2005或以上版本就能用别的语法进行查询,下面是示例. --通过子节点查询父节点WITH  TREE AS(      SELECT * FROM Areas      WHERE id = 6  -- 要查询的子 id     UNION ALL      SELECT Areas.* FROM Areas, TREE      WHERE TREE.PId = Areas.Id )  SELECT Area

SqlServer :实现树形结构递归查询(无限极分类)

SQL Server 2005开始,我们可以直接通过CTE来支持递归查询,CTE即公用表表达式 公用表表达式(CTE),是一个在查询中定义的临时命名结果集将在from子句中使用它.每个CTE仅被定义一次(但在其作用域内可以被引用任意次),并且在该查询生存期间将一直生存.可以使用CTE来执行递归操作. 一.查询树状结构某节点下的所有子节点 with cte_child(id,areaName,pid,level) as ( --起始条件 select id,areaName,pid,0 as le

mysql递归查询树形表

DROP TABLE IF EXISTS `t_areainfo`; CREATE TABLE `t_areainfo` ( `id` int(11) NOT NULL AUTO_INCREMENT, `level` int(11) DEFAULT 0, `name` varchar(255), `parentId` int(11), `status` int(11) DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=65

MySQL递归查询所有子节点,树形结构查询

--表结构 CREATE TABLE `address` ( `id` int(11) NOT NULL AUTO_INCREMENT, `code_value` varchar(32) DEFAULT NULL COMMENT '区域编码', `name` varchar(128) DEFAULT NULL COMMENT '区域名称', `remark` varchar(128) DEFAULT NULL COMMENT '说明', `pid` varchar(32) DEFAULT NUL