【问题标题】:get last node given the full path of all ancestor's node attributes using cte使用 cte 获取给定所有祖先节点属性的完整路径的最后一个节点
【发布时间】:2013-08-24 12:34:45
【问题描述】:

给定以下 PostgreSQL 表:

items
  integer id
  integer parent_id
  string name

unique key on [parent_id, name]

parent_id is null for all root nodes

目前我手动构建 sql 查询,对每个路径元素进行连接。但这对我来说似乎很丑陋,当然它限制了可能的深度。

例子:

path: holiday,images,spain
SELECT i3.* 
FROM items AS i1
   , items AS i2
   , items AS i3
WHERE i1.parent_id IS NULL AND i1.name = 'holiday'
  AND i2.parent_id=i1.id AND i2.name = 'images'
  AND i3.parent_id=i2.id AND i3.name = 'spain'

我想知道是否有更好的方法,可能使用 CTE?

你可以在这里看到我当前的代码是如何工作的以及预期的输出是什么:
http://sqlfiddle.com/#!1/4537c/2

【问题讨论】:

  • 问题不是很清楚。什么是“最后一个节点”?如果您已经拥有“完整路径”,您需要搜索什么?您的表定义也不是很有帮助。也许在问题中添加一些示例数据?
  • 额外问题:1) i1 之上是否还有其他 unmatched 节点?在 i1 和 i2 之间,在 i2 和 i3 之间?低于 i3 ?如果是这样:您是否也希望检索这些内容? 2) 路径中 {holiday,images,spain} 的 order 是否重要,或者这些是否可以按任何顺序出现?顺便说一句:parent_id 上的唯一索引是不可能的(而且是错误的)
  • 我刚刚添加了一个 sqlfiddle。最后一个节点是路径的最后一项 (a,b,c,d -> d)。我只有名字,没有id。节点的名称不是全局唯一的,这就是我必须检查所有父母的原因。我现在正在使用连接来执行此操作,但是连接的数量等于路径元素的数量。哪个不好。
  • 在我的查询中,i1 始终是根节点,因为 parent_id 为空。补丁是完整的,所以没有丢失的元素。我只需要最后一个节点(id 就足够了)。路径元素的顺序很重要。将其视为文件系统的目录结构。

标签: sql postgresql common-table-expression recursive-cte


【解决方案1】:

update2 这是一个函数,它执行得很好,因为搜索只在路径内进行,从父节点开始:

create or replace function get_item(path text[])
returns items
as
$$
    with recursive cte as (
        select i.id, i.name, i.parent_id, 1 as level
        from items as i
        where i.parent_id is null and i.name = $1[1]

        union all

        select i.id, i.name, i.parent_id, c.level + 1
        from items as i
            inner join cte as c on c.id = i.parent_id
        where i.name = $1[level + 1]
    )
    select c.id, c.parent_id, c.name
    from cte as c
    where c.level = array_length($1, 1)
$$
language sql;

sql fiddle demo

更新我认为您可以进行递归遍历。这个我写过sql版本的,所以因为cte有点乱,不过可以写个函数:

with recursive cte_path as (
    select array['holiday', 'spain', '2013'] as arr
), cte as (
    select i.id, i.name, i.parent_id, 1 as level
    from items as i
        cross join cte_path as p
    where i.parent_id is null and name = p.arr[1]

    union all

    select i.id, i.name, i.parent_id, c.level + 1
    from items as i
        inner join cte as c on c.id = i.parent_id
        cross join cte_path as p
    where i.name = p.arr[level + 1]
)
select c.*
from cte as c
    cross join cte_path as p
where level = array_length(p.arr, 1)

sql fiddle demo

或者您可以使用递归 cte 为所有元素构建路径并将路径累积到数组或字符串中:

with recursive cte as (
    select i.id, i.name, i.parent_id, i.name::text as path
    from items as i
    where i.parent_id is null

    union all

    select i.id, i.name, i.parent_id, c.path || '->' || i.name::text as path
    from items as i
        inner join cte as c on c.id = i.parent_id
)
select *
from cte
where path = 'holiday->spain->2013';

with recursive cte as (
    select i.id, i.name, i.parent_id, array[i.name::text] as path
    from items as i
    where i.parent_id is null

    union all

    select i.id, i.name, i.parent_id, c.path || array[i.name::text] as path
    from items as i
        inner join cte as c on c.id = i.parent_id
)
select *
from cte
where path = array['holiday', 'spain', '2013']

sql fiddle demo

【讨论】:

  • 我认为这会产生非常糟糕的性能,因为它会构建一个包含所有路径的巨大临时表?
  • 是的,正在开发更注重性能的版本。如果您需要显示所有元素的路径,这个很好
  • 非常感谢您的努力!太糟糕了,我不能接受两个答案,因为您的最终版本似乎与 Erwin 的相同。
  • 不完全相等,但思路是一样的
【解决方案2】:

这应该表现得非常好,因为它会立即消除不可能的路径:

WITH RECURSIVE cte AS (
   SELECT id, parent_id, name
         ,'{holiday,spain,2013}'::text[] AS path  -- provide path as array here
         ,2 AS lvl                                -- next level
   FROM   items
   WHERE  parent_id IS NULL
   AND    name = 'holiday'                        -- being path[1]

   UNION ALL
   SELECT i.id, i.parent_id, i.name
         ,cte.path, cte.lvl + 1
   FROM   cte 
   JOIN   items i ON i.parent_id = cte.id AND i.name = path[lvl]
)
SELECT id, parent_id, name
FROM   cte
ORDER  BY lvl DESC
LIMIT  1;

假设您提供了唯一的路径(只有 1 个结果)。

->SQLfiddle demo

【讨论】:

  • where path = array... 在这里是多余的
  • 干得好!顺便说一句:我不喜欢 ORDER BY ... LIMIT 1 ;我更喜欢lvl >=3(我认为OP想要路径的后代。不确定)
  • @wildplasser:这取决于具体要求。如果我们知道总是存在三个级别,那么在我的示例中它必须是 lvl > 3lvl = 4,因为每一行都带有 next 级别(少一个计算)。
  • 啊!我的坏(我的中间名是一对一)如果允许中间(不匹配的)节点,或者没有强加排序,问题会变得更加复杂;请参阅我对 OQ 的评论。
  • 太棒了!非常感谢您的努力!
【解决方案3】:

发布我的答案为时已晚(非常等同于 Roman 和 Erwin 的)但是对表格定义进行了改进:

CREATE TABLE items
        ( id integer NOT NULL PRIMARY KEY
        , parent_id integer REFERENCES items(id)
        , name varchar
        , UNIQUE (parent_id,name) -- I don't actually like this one
        );                        -- ; UNIQUE on a NULLable column ...

INSERT INTO items (id, parent_id, name) values
        (1, null, 'holiday')
        , (2, 1, 'spain'), (3, 2, '2013')
        , (4, 1, 'usa'), (5, 4, '2013')
        ;

【讨论】:

    猜你喜欢
    • 2016-10-18
    • 1970-01-01
    • 1970-01-01
    • 2021-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-06
    相关资源
    最近更新 更多