клонирование иерархических данных - PullRequest
2 голосов
/ 27 сентября 2008

давайте предположим, что у меня есть самоссылающаяся иерархическая таблица, построенная таким классическим способом:

CREATE TABLE test
(name text,id serial primary key,parent_id integer
references test);

insert into test (name,id,parent_id) values
('root1',1,NULL),('root2',2,NULL),('root1sub1',3,1),('root1sub2',4,1),('root
2sub1',5,2),('root2sub2',6,2);

testdb=# select * from test;

   name    | id | parent_id
-----------+----+-----------
 root1     |  1 |  
 root2     |  2 |  
 root1sub1 |  3 |         1
 root1sub2 |  4 |         1
 root2sub1 |  5 |         2
 root2sub2 |  6 |         2

Теперь мне нужна функция (желательно на простом sql), которая бы принимала идентификатор записи теста и клонировать все прикрепленные записи (включая данную). Конечно, клонированные записи должны иметь новые идентификаторы. Желаемый результат хотел бы это например:

Select * from cloningfunction(2);

   name    | id | parent_id    
-----------+----+-----------
 root2     |  7 |  
 root2sub1 |  8 |         7
 root2sub2 |  9 |         7

Есть указатели? Я использую PostgreSQL 8.3.

Ответы [ 4 ]

6 голосов
/ 27 сентября 2008

Рекурсивно получить этот результат сложно (хотя и возможно). Однако, как правило, он не очень эффективен, и есть намного лучший способ решить эту проблему.

По сути, вы дополняете таблицу дополнительным столбцом, который прослеживает дерево до вершины - я назову его «Upchain». Это просто длинная строка, которая выглядит примерно так:

name | id | parent_id | upchain
root1 | 1 | NULL | 1:
root2 | 2 | NULL | 2:
root1sub1 | 3 | 1 | 1:3:
root1sub2 | 4 | 1 | 1:4:
root2sub1 | 5 | 2 | 2:5:
root2sub2 | 6 | 2 | 2:6:
root1sub1sub1 | 7 | 3 | 1:3:7:

Очень легко обновлять это поле, используя триггер на столе. (Извиняюсь за терминологию, но я всегда делал это с SQL Server). Каждый раз, когда вы добавляете или удаляете запись, или обновляете поле parent_id, вам просто нужно обновить поле upchain в этой части дерева. Это тривиальная работа, потому что вы просто берете цепочку родительской записи и добавляете идентификатор текущей записи. Все дочерние записи легко идентифицируются с помощью LIKE для проверки записей со стартовой строкой в ​​цепочке.

То, что вы делаете эффективно, - это обмен небольшого количества дополнительных операций записи на большое сохранение, когда вы приходите, чтобы прочитать данные.

Если вы хотите выбрать полную ветку в дереве, это тривиально. Предположим, вы хотите ветвь под узлом 1. Узел 1 имеет цепочку «1:», поэтому вы знаете, что любой узел в ветке дерева под этим узлом должен иметь цепочку, начинающуюся с «1: ...». Итак, вы просто делаете это:

SELECT *
FROM table
WHERE upchain LIKE '1:%'

Это чрезвычайно быстро (конечно, индексируйте поле восходящей цепи). В качестве бонуса он также делает чрезвычайно простыми многие действия, такие как поиск частичных деревьев, уровня внутри дерева и т. Д.

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

Примечания (для всех, кому интересно):

  • Я не дал пошагового кода SQL, но как только вы поймете принцип, его довольно просто реализовать. Я не большой программист, поэтому я говорю по своему опыту.
  • Если у вас уже есть данные в таблице, вам нужно выполнить однократное обновление, чтобы изначально синхронизировать цепочки операций. Опять же, это не сложно, так как код очень похож на код UPDATE в триггерах.
  • Этот метод также является хорошим способом определения круговых ссылок, которые в противном случае сложно обнаружить.
3 голосов
/ 27 сентября 2008

Метод Джо Селко, который похож на ответ njreed , но более общий, можно найти здесь:

1 голос
/ 28 сентября 2008

@ Максимилиан : Вы правы, мы забыли ваше фактическое требование Как насчет рекурсивной хранимой процедуры? Я не уверен, возможно ли это в PostgreSQL, но вот рабочая версия SQL Server:

CREATE PROCEDURE CloneNode
    @to_clone_id int, @parent_id int
AS
    SET NOCOUNT ON
    DECLARE @new_node_id int, @child_id int

    INSERT INTO test (name, parent_id) 
        SELECT name, @parent_id FROM test WHERE id = @to_clone_id
    SET @new_node_id = @@IDENTITY

    DECLARE @children_cursor CURSOR
    SET @children_cursor = CURSOR FOR 
        SELECT id FROM test WHERE parent_id = @to_clone_id

    OPEN @children_cursor
    FETCH NEXT FROM @children_cursor INTO @child_id
    WHILE @@FETCH_STATUS = 0
    BEGIN
        EXECUTE CloneNode @child_id, @new_node_id
        FETCH NEXT FROM @children_cursor INTO @child_id
    END
    CLOSE @children_cursor
    DEALLOCATE @children_cursor

Ваш пример выполняется с помощью EXECUTE CloneNode 2, null (второй параметр - новый родительский узел).

0 голосов
/ 27 сентября 2008

Это звучит как упражнение из "SQL For Smarties" Джо Селко ...

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...