2010-09-13 11 views
19

ich postgresql bin mit. Ich habe die Tabelle wie untenSuche Eltern Recursively Abfrage mit

parent_id child_id 
---------------------- 
101  102 
103  104 
104  105 
105  106 

Ich möchte eine SQL-Abfrage schreiben, die das endgültige Elternteil der Eingabe geben wird.

d.h i als Eingabe übergeben 106 annehmen, dann wird seine Ausgabe 103 sein

(106 --> 105 --> 104 --> 103) 
+0

Wenn Sie Postgres verwenden, SQL-Server und Oracle tags.Please entfernen. –

+0

auf @ Namen Avadhesh getan – spender

+0

linq-to-sql postgresql nicht unterstützt, so den Tag zu entfernen. – spender

Antwort

53

Hier ist ein vollständiges Beispiel. Zuerst werden die DDL:

test=> CREATE TABLE node (
test(> id SERIAL, 
test(> label TEXT NOT NULL, -- name of the node 
test(> parent_id INT, 
test(> PRIMARY KEY(id) 
test(>); 
NOTICE: CREATE TABLE will create implicit sequence "node_id_seq" for serial column "node.id" 
NOTICE: CREATE TABLE/PRIMARY KEY will create implicit index "node_pkey" for table "node" 
CREATE TABLE 

... und einige Daten ...

test=> INSERT INTO node (label, parent_id) VALUES ('n1',NULL),('n2',1),('n3',2),('n4',3); 
INSERT 0 4 
test=> INSERT INTO node (label) VALUES ('garbage1'),('garbage2'), ('garbage3'); 
INSERT 0 3 
test=> INSERT INTO node (label,parent_id) VALUES ('garbage4',6); 
INSERT 0 1 
test=> SELECT * FROM node; 
id | label | parent_id 
----+----------+----------- 
1 | n1  |   
2 | n2  |   1 
3 | n3  |   2 
4 | n4  |   3 
5 | garbage1 |   
6 | garbage2 |   
7 | garbage3 |   
8 | garbage4 |   6 
(8 rows) 

Dies führt eine rekursive Abfrage auf jeder ID in Knoten:

test=> WITH RECURSIVE nodes_cte(id, label, parent_id, depth, path) AS (
SELECT tn.id, tn.label, tn.parent_id, 1::INT AS depth, tn.id::TEXT AS path 
FROM node AS tn 
WHERE tn.parent_id IS NULL 
UNION ALL 
SELECT c.id, c.label, c.parent_id, p.depth + 1 AS depth, 
     (p.path || '->' || c.id::TEXT) 
FROM nodes_cte AS p, node AS c 
WHERE c.parent_id = p.id 
) 
SELECT * FROM nodes_cte AS n ORDER BY n.id ASC; 
id | label | parent_id | depth | path  
----+----------+-----------+-------+------------ 
1 | n1  |   |  1 | 1 
2 | n2  |   1 |  2 | 1->2 
3 | n3  |   2 |  3 | 1->2->3 
4 | n4  |   3 |  4 | 1->2->3->4 
5 | garbage1 |   |  1 | 5 
6 | garbage2 |   |  1 | 6 
7 | garbage3 |   |  1 | 7 
8 | garbage4 |   6 |  2 | 6->8 
(8 rows) 

Dies wird alle die Nachkommen WHERE node.id = 1:

test=> WITH RECURSIVE nodes_cte(id, label, parent_id, depth, path) AS (
SELECT tn.id, tn.label, tn.parent_id, 1::INT AS depth, tn.id::TEXT AS path 
FROM node AS tn 
WHERE tn.parent_id IS NULL 
UNION ALL 
SELECT c.id, c.label, c.parent_id, p.depth + 1 AS depth, 
     (p.path || '->' || c.id::TEXT) 
FROM nodes_cte AS p, node AS c 
WHERE c.parent_id = p.id 
) 
SELECT * FROM nodes_cte AS n WHERE n.id = 4; 
id | label | parent_id | depth | path  
----+-------+-----------+-------+------------ 
4 | n4 |   3 |  4 | 1->2->3->4 
(1 row) 

Und lassen Sie uns annehmen, dass Sie Ihre Suche auf Nachkommen mit einem depth weniger als drei (beachten Sie, dass depth wurde noch nicht erhöht) begrenzen wollen:Im Folgenden wird der Pfad des Knotens mit der ID 4 erhalten:

test=> WITH RECURSIVE nodes_cte(id, label, parent_id, depth, path) AS (
    SELECT tn.id, tn.label, tn.parent_id, 1::INT AS depth, tn.id::TEXT AS path 
    FROM node AS tn WHERE tn.id = 1 
UNION ALL 
    SELECT c.id, c.label, c.parent_id, p.depth + 1 AS depth, 
     (p.path || '->' || c.id::TEXT) 
    FROM nodes_cte AS p, node AS c 
    WHERE c.parent_id = p.id AND p.depth < 2 
) 
SELECT * FROM nodes_cte AS n; 
id | label | parent_id | depth | path 
----+-------+-----------+-------+------ 
    1 | n1 |   |  1 | 1 
    2 | n2 |   1 |  2 | 1->2 
(2 rows) 

ich würde empfehlen, für den Nachweis der „Pfad“ einen ARRAY Datentyp anstelle einer Zeichenfolge verwendet, aber der Pfeil ist illustrativ für die Eltern < => Kind-Beziehung.

+0

ist möglich, diese Methode in Selbst viele zu viele? Stellen Sie sich vor, es gibt Knoten, die viele Eltern haben, aber es gibt ein Feld wie subtree_id, um zu sagen, Knoten A ist Kind von B gerade in diesem Unterbaum, wenn Knoten B in einem anderen Unterbaum ist, aber A nicht darin ist, zeige A nicht unter B Ich hoffe ich habe meine Absicht gesagt! –

+0

Wie alle Eltern von den letzten chield ger? –

5

Verwenden WITH RECURSIVE einen allgemeinen Tabellenausdruck (CTE) zu erstellen. Für die nicht-rekursive Begriff, bekommen Sie die Zeilen, in denen das Kind unmittelbar unterhalb der Eltern ist:

SELECT 
    c.child_id, 
    c.parent_id 
FROM 
    mytable c 
LEFT JOIN 
    mytable p ON c.parent_id = p.child_id 
WHERE 
    p.child_id IS NULL 

child_id | parent_id 
----------+----------- 
     102 |  101 
     104 |  103 

Für die rekursive Begriff, um die Kinder dieser Kinder wollen.

WITH RECURSIVE tree(child, root) AS (
    SELECT 
     c.child_id, 
     c.parent_id 
    FROM 
     mytable c 
    LEFT JOIN 
     mytable p ON c.parent_id = p.child_id 
    WHERE 
     p.child_id IS NULL 
    UNION 
    SELECT 
     child_id, 
     root 
    FROM 
     tree 
    INNER JOIN 
     mytable on tree.child = mytable.parent_id 
) 
SELECT * FROM tree; 

child | root 
-------+------ 
    102 | 101 
    104 | 103 
    105 | 103 
    106 | 103 

Sie können die Kinder filtern, wenn der CTE Abfrage:

WITH RECURSIVE tree(child, root) AS (...) SELECT root FROM tree WHERE child = 106; 

root 
------ 
    103 
+0

Das ist wirklich elegant. Es scheint das ideale Lehrbeispiel für einen rekursiven CTE zu sein. Aus diesem Grund war es leicht für meinen Anwendungsfall zu modifizieren. – Noumenon