SQL Найти всех прямых потомков в дереве - PullRequest
2 голосов
/ 17 июля 2009

В моей базе данных есть дерево, которое хранится с использованием родительских ссылок.

Пример того, что у меня есть для данных в таблице:

id  |  name      | parent id
---+-------------+-----------
0  | root          | NULL
1  | Node 1      | 0
2  | Node 2      | 0
3  | Node 1.1   | 1
4  | Node 1.1.1| 3
5  | Node 1.1.2| 3

Теперь я хотел бы получить список всех прямых потомков данного узла, но если его не существует, я бы хотел, чтобы он просто возвращал сам узел.

Я хочу, чтобы возвращение для запроса для детей с id = 3 было:

children
--------
4
5

Тогда запрос для детей с id = 4 будет:

children
--------
4

Я могу изменить способ хранения дерева во вложенный набор, но я не понимаю, как это могло бы сделать возможным запрос, который я хочу.

Ответы [ 2 ]

6 голосов
/ 17 июля 2009

В новом PostgreSQL 8.4 вы можете сделать это с CTE:

WITH RECURSIVE q AS
        (
        SELECT  h, 1 AS level, ARRAY[id] AS breadcrumb
        FROM    t_hierarchy h
        WHERE   parent = 0
        UNION ALL
        SELECT  hi, q.level + 1 AS level, breadcrumb || id
        FROM    q
        JOIN    t_hierarchy hi
        ON      hi.parent = (q.h).id
        )
SELECT  REPEAT('  ', level) || (q.h).id,
        (q.h).parent,
        (q.h).value,
        level,
        breadcrumb::VARCHAR AS path
FROM    q
ORDER BY
        breadcrumb

См. Эту статью в моем блоге для деталей:

В 8.3 или более ранних версиях вам нужно написать функцию:

CREATE TYPE tp_hierarchy AS (node t_hierarchy, level INT);

CREATE OR REPLACE FUNCTION fn_hierarchy_connect_by(INT, INT)
RETURNS SETOF tp_hierarchy
AS
$$
        SELECT  CASE
                WHEN node = 1 THEN
                        (t_hierarchy, $2)::tp_hierarchy
                ELSE
                        fn_hierarchy_connect_by((q.t_hierarchy).id, $2 + 1)
                END
        FROM    (
                SELECT  t_hierarchy, node
                FROM    (
                        SELECT  1 AS node
                        UNION ALL
                        SELECT  2
                        ) nodes,
                        t_hierarchy
                WHERE   parent = $1
                ORDER BY
                        id, node
                ) q;
$$
LANGUAGE 'sql';

и выберите из этой функции:

SELECT  *
FROM    fn_hierarchy_connect_by(4, 1)

Первый параметр - корень id, второй должен быть 1.

См. Эту статью в моем блоге для более подробной информации:

Обновление:

Чтобы отобразить только дочерние элементы первого уровня или сам узел, если дочерние элементы не существуют, введите следующий запрос:

SELECT  *
FROM    t_hierarchy
WHERE   parent = @start
UNION ALL
SELECT  *
FROM    t_hierarchy
WHERE   id = @start
        AND NOT EXISTS
        (
        SELECT  NULL
        FROM    t_hierarchy
        WHERE   parent = @start
        )

Это более эффективно, чем JOIN, поскольку второй запрос займет не более двух сканирований индекса: первый, чтобы убедиться, что существует дочерний элемент, второй - выбор родительской строки, если нет дети существуют.

0 голосов
/ 18 июля 2009

Нашел запрос, который работает так, как я хотел.

SELECT * FROM
   ( SELECT id FROM t_tree WHERE name = '' ) AS i,
   t_tree g
WHERE
  ( ( i.id = g.id ) AND 
       NOT EXISTS ( SELECT * FROM t_tree WHERE parentid = i.id ) ) OR
  ( ( i.id = g.parentid ) AND 
       EXISTS ( SELECT * FROM t_tree WHERE parentid = i.id ) )
...