2013-08-24 13 views
2

考慮下面的PostgreSQL表屬性給出的所有祖先的節點的完整路徑:獲得最後一個節點使用CTE

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

+0

的問題還不是很清楚。什麼是「最後節點」?如果您已經擁有「完整路徑」,您需要搜索什麼?你的表格定義也不是很有用。也許添加一些示例數據的問題? – wildplasser

+0

額外的問題:1)i1上面是否有其他_unmatched_節點?在i1和i2之間,在i2和i3之間?低於i3?如果是這樣:你是否也想要檢索這些文件? 2)是重要的路徑中的{節假日,圖像,西班牙}的_order_,或者可能以任何順序出現?順便說一句:'parent_id'上的唯一索引是不可能的(並且是錯誤的) – wildplasser

+0

我剛剛添加了一個sqlfiddle。最後一個節點是路徑的最後一項(a,b,c,d - > d)。我只有名字,而不是任何ID。節點的名稱不是全球唯一的,這就是爲什麼我必須檢查所有父母。我現在正在使用連接來完成此操作,但連接的數量等於路徑元素的數量。哪個不好。 – gucki

回答

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

+0

'where path = array ...'在這裏冗餘 –

+0

@羅曼皮卡爾:是的,那是一個神器。 –

+0

不錯的工作!順便說一句:我不喜歡'ORDER BY ... LIMIT 1';我更喜歡'lvl> = 3'(我認爲OP需要路徑的後代,不確定) – wildplasser

2

UPDATE2這裏是一個函數,它peforms好,因爲搜索那張只能在路徑,從父母開始:

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,所以這是一個有點亂,因爲熱膨脹係數,但它是可以寫一個函數:

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爲和accumuate您的路徑到數組或字符串:

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

+0

我認爲它會有非常差的性能,因爲它會構建一個包含所有路徑的巨大臨時表? – gucki

+0

是的,在更多的perfomance明智的版本工作。如果你需要展示所有元素的路徑,這一個是很好的 –

+0

@gucki查看更新的答案 –

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') 
     ; 
相關問題