【问题标题】:Sql recursion without recursion没有递归的sql递归
【发布时间】:2010-11-22 08:02:37
【问题描述】:

我有四张桌子

create table entities{
integer id;
string name;
}

create table users{
integer id;//fk to entities
string email;
}

create table groups{
integer id;//fk to entities
}

create table group_members{
integer group_id; //fk to group
integer entity_id;//fk to entity
}

我想做一个查询,直接或间接返回用户所属的所有组。显而易见的解决方案是在应用程序级别进行递归。我想知道我可以对我的数据模型进行哪些更改以减少数据库访问并因此获得更好的性能。

【问题讨论】:

  • 您能否定义一个实体以及它是如何关联的?
  • 你用的是什么数据库引擎?
  • 实体只是用户和组之间的一种通用抽象,那么成员既可以是组也可以是用户。我正在使用 PostgreSQL

标签: sql postgresql data-modeling


【解决方案1】:

种方法可以避免树层次结构查询中的递归(与人们在这里所说的相反)。

我用得最多的是Nested Sets

然而,与所有生命和技术决策一样,也需要做出权衡。嵌套集的更新速度通常较慢,但查询速度要快得多。有一些巧妙而复杂的方法可以提高更新层次结构的速度,但还有另一个权衡;性能与代码复杂度。

嵌套集合的简单示例...

树视图:

 -Electronics
 |
 |-Televisions
 | |
 | |-Tube
 | |-LCD
 | |-Plasma
 |
 |-Portable Electronics
   |
   |-MP3 Players
   | |
   | |-Flash
   |
   |-CD Players
   |-2 Way Radios

嵌套集表示

+-------------+----------------------+-----+-----+
| category_id | name                 | lft | rgt |
+-------------+----------------------+-----+-----+
|           1 | ELECTRONICS          |   1 |  20 |
|           2 | TELEVISIONS          |   2 |   9 |
|           3 | TUBE                 |   3 |   4 |
|           4 | LCD                  |   5 |   6 |
|           5 | PLASMA               |   7 |   8 |
|           6 | PORTABLE ELECTRONICS |  10 |  19 |
|           7 | MP3 PLAYERS          |  11 |  14 |
|           8 | FLASH                |  12 |  13 |
|           9 | CD PLAYERS           |  15 |  16 |
|          10 | 2 WAY RADIOS         |  17 |  18 |
+-------------+----------------------+-----+-----+

您需要阅读article I linked 以完全理解这一点,但我会尝试给出一个简短的解释。

如果(子项的“lft”(Left)值大于父项的“ltf”值)并且(子项的“rgt”值小于父项的“rgt”值),则一个项是另一个项的成员

“Flash”因此是“MP3 PLAYERS”、“Portable Electronics”和“Electronics”的成员

或者,conversley,“便携式电子产品”的成员是:
- MP3 播放器
- 闪光
- CD 播放器
- 2路收音机

Joe Celko 有一整本关于“SQL 中的树和层次结构”的书。有比您想象的更多的选择,但需要做出很多权衡。

注意:永远不要说某事不能做,一些 mofo 会出现在可以告诉你的。

【讨论】:

  • Nested sets 当您想要查找一个类别中的所有项目时,查询确实更快,但是当您想要一个项目所属的所有类别时它会更慢(这是@op 要求的功能)。
  • 好吧,你的名字我认识并尊重,但你确定吗?嵌套集在向下看树时执行禁食(我的孩子是什么),而在向上看树时则较慢(我的父母是什么)。但是,根据我的经验,即使在 SQL Server 2005+ 中使用公共表表达式,在嵌套集中查找树也比使用递归更快。我会对任何文章真正感兴趣,等等,你必须证明差异是相反的。
  • @Dems:写一篇关于这个的文章是个好主意(我可能会在这周做)。只是一些大纲:当您搜索孩子所属的所有类别时,您需要发出以下查询:SELECT * FROM sets WHERE lft <= @myid and rgt >= @myid。没有单个索引可以服务于该查询。您将需要两个索引上的INDEX MERGE,这将需要过滤可能数千条记录,然后加入它们。具有100,000 类别的树很常见。另一方面,Adjacency list 最多需要与项目深度一样多的索引查找,很少超过 10
  • @Dems:显然你对自己的能力充满信心。我说的是真的,因为嵌套集不是实际的树数据结构。正如您在答案中承认的那样,它在功能上相似(并且在许多情况下相同),但它不是一棵树并且有权衡。不需要因为你不喜欢就放弃另一个答案。
【解决方案2】:

Oracle:

SELECT  group_id
FROM    group_members
START WITH
        entity_id = :user_id
CONNECT BY
        entity_id = PRIOR group_id

SQL Server:

WITH    q AS
        (
        SELECT  group_id, entity_id
        FROM    group_members
        WHERE   entity_id = @user_id
        UNION ALL
        SELECT  gm.group_id, gm.entity_id
        FROM    group_members gm
        JOIN    q
        ON      gm.entity_id = q.group_id
        )
SELECT  group_id
FROM    q

PostgreSQL 8.4:

WITH RECURSIVE
        q AS
        (
        SELECT  group_id, entity_id
        FROM    group_members
        WHERE   entity_id = @user_id
        UNION ALL
        SELECT  gm.group_id, gm.entity_id
        FROM    group_members gm
        JOIN    q
        ON      gm.entity_id = q.group_id
        )
SELECT  group_id
FROM    q

PostgreSQL 8.3 及以下:

CREATE OR REPLACE FUNCTION fn_group_members(INT)
RETURNS SETOF group_members
AS
$$
        SELECT  group_members
        FROM    group_members
        WHERE   entity_id = $1
        UNION ALL
        SELECT  fn_group_members(group_members.group_id)
        FROM    group_members
        WHERE   entity_id = $1;
$$
LANGUAGE 'sql';

SELECT  group_id
FROM    group_members(:myuser) gm

【讨论】:

  • 确实是非常优雅的解决方案,但事实上,OP 曾询问是否可能没有递归。您的解决方案显然是功能性的并且相当简单,但它仍然使用递归。
  • 来自问题:“显而易见的解决方案是在应用程序级别进行递归”。我认为这是@op 真正想要避免的,而不是递归本身。
  • 感谢您的回答!。尽管该解决方案仍然使用递归,但这种方法比在应用程序级别编写递归更有效。我只需要升级我的 postgres 版本:D
  • @Dani:稍加努力,您也可以在8.3 及以下版本上实现此功能。
  • @Quassnoi:非常感谢您快速而有帮助的回答!
【解决方案3】:

我认为这里不需要递归,因为 barry-brown 发布的解决方案似乎足够了。如果你需要一个组才能成为一个组的成员,那么 Dems 提供的树遍历方法效果很好。使用这种方案,插入、删除和更新非常简单,检索整个层次结构只需一次选择即可完成。

我建议在您的 group_members 表中包含一个 parent_id 字段(假设这是您的递归关系发生的点)。在导航编辑器中,我创建了一个节点表,如下所示:

tbl_nodes     
----------
node_id   
parent_id 
left      
right
level

...

我的编辑器从 C# 节点类创建层次相关的对象

    class node {
      public int NodeID { get; set; } 
      public Node Parent { get; set; }
      public int Left { get; set; }
      public int Right { get; set; }
      public Dictionary<int,Node> Nodes { get; set; } 
      public int Level {
         get { 
            return (Parent!=null) ? Parent.Level+1 : 1;
         }
      }
}

Nodes 属性包含一个子节点列表。当业务层加载层次结构时,它会纠正父/子关系。导航编辑器保存时,我递归设置左右属性值,然后保存到数据库。这让我能够以正确的顺序获取数据,这意味着我可以在检索期间设置父/子引用,而不必进行第二次传递。也意味着需要显示层次结构的任何其他内容(例如报表)都可以轻松地以正确的顺序获取节点列表。

如果没有 parent_id 字段,您可以检索到当前节点的面包屑路径

select n1.* 
from nodes n1, nodes n2
where d1.lft <= d2.lft and d1.rgt >= d2.rgt
and d2.id = @id
order by lft;

其中@id 是您感兴趣的节点的ID。

非常明显的东西,真的,但它适用于可能不明显的嵌套组成员资格等项目,并且正如其他人所说,消除了降低递归 SQL 的需要。

【讨论】:

    【解决方案4】:

    您可以执行以下操作:

    • 使用 START WITH / CONNECT BY PRIOR constructs
    • 创建一个 PL/SQL 函数。

    【讨论】:

      【解决方案5】:

      如果您想要一个真正理论上无限的嵌套级别,那么递归是唯一的选择,它排除了任何健全的 SQL 版本。如果您愿意限制它,那么还有许多其他选择。

      查看this question

      【讨论】:

      • 有明确的表示树的方法,而不需要递归来询问它们。他们只需要一点“开箱即用的思维”,在某些情况下还需要良好的数学思维。搜索“嵌套集”,如果您继续阅读您发现的内容,您也会发现其他可能性......
      • @Dems:如果你真的需要理论上无限级别的嵌套,这就是为什么我在这句话前言的原因。所有这些方法都是对理论集的妥协,以利于查询。说“绝对有方法”是没有意义的。有一些方法,但没有一个完全满足条件,并且 OP 没有提供允许选择妥协的信息。
      【解决方案6】:

      您能否阐明实体和用户之间的区别?否则,您的表格看起来不错。您假设组和实体之间存在多对多关系。

      无论如何,对于标准 SQL,请使用以下查询:

      SELECT name, group_id
      FROM entities JOIN group_members ON entities.id = group_members.entity_id;
      

      这将为您提供名称和 group_id 的列表,每行一对。如果一个实体是多个组的成员,则该实体将被多次列出。

      如果您想知道为什么没有 JOIN 到 groups 表,那是因为没有来自 groups 表的数据不在 group_members 表中。例如,如果您在组表中包含一个组名,并且希望显示该组名,那么您也必须加入组。

      某些 SQL 变体具有与报告相关的命令。它们将允许您在同一行上列出多个组作为单个实体。但它不是标准的,不能在所有平台上运行。

      【讨论】:

        猜你喜欢
        • 2011-09-16
        • 2011-11-09
        • 2010-09-08
        • 1970-01-01
        • 1970-01-01
        • 2013-09-21
        • 2017-06-28
        • 2012-04-06
        • 1970-01-01
        相关资源
        最近更新 更多