使用cte获取给定所有祖先节点属性的完整路径的最后一个节点

时间:2013-08-24 12:34:45

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

给出以下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

3 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

发布我的答案太迟了(非常相当于罗曼和欧文的)但是改进了表定义:

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')
        ;