ХП & Дерево

IBX, FIBPlus, UIB, ADO, .Net и прочее-прочее-прочее, в общем все, что относится к созданию приложений, работающих с InterBase, Firebird и Yaffil - клиент-серверных, трехзвенных, консольных и т.п.

Модератор: kdv

Ответить
santilaas
Сообщения: 51
Зарегистрирован: 27 авг 2005, 21:05

ХП & Дерево

Сообщение santilaas » 02 июн 2006, 12:28

Всем привет!
Помогите, кому не трудно!
Скажем, у меня в БД имеется таблица типа дерева (Id_Good, Id_Parent, Good_Name, FullPath).
Мне нужно, чтобы при вставке - изменении данных в этой таблице автоматически формировалось поле FullPath, которое есть объединение всех полей Name вплоть до редактируемого элемента.
(т.е полный путь к редактируемогу элементу)
В книге "Мир Interbase 3" на стр. 71-74 есть очень хороший пример рекурсивной процедуры, в которой входным параметром является
идентификатор категории, начиная с которой и формируется полный путь. Эту процедуру я взял себе.
Но у меня проблема:
Как все-таки из триггера обратиться к этой процедуре и взять из неё путь только к данному-текущему редактируемому элементу?
Делаю так, но что-то не то:

Код: Выделить всё

CREATE TRIGGER STRUCT_EL_BI_BU_TRIG FOR STRUCT_EL
ACTIVE BEFORE INSERT OR UPDATE POSITION 1 //для этой таблицы
    //имеется также триггер BeforeInsert, формирующий new.id_good
as
declare variable full_goods_name varchar(251);
begin
   select g.full_goods_name from Getfullname(1) g
   where g.Id_child_Good = NEW.Id_good
   into :full_goods_name;

   new.FullPath = :full_goods_name;
end
- ничего не заносится.
Только, пожалуйста, не отправляйте читать доку - и так над книжкой провел много времени. Хотя, не знаю - может фигу увидел?!

Заранее благодарен

Dimitry Sibiryakov
Заслуженный разработчик
Сообщения: 1436
Зарегистрирован: 15 сен 2005, 09:05

Сообщение Dimitry Sibiryakov » 02 июн 2006, 13:13

А зачем тебе каждый раз проходить весь путь? Взял FullPath парента и добавил к нему свое. Все.
SELECT FullPath||:new.Name from struct_el where Id_good=:new.Parent_Id into New.FullPath;

Zhur
Сообщения: 125
Зарегистрирован: 01 мар 2006, 18:17

Сообщение Zhur » 02 июн 2006, 15:27

Dimitry Sibiryakov писал(а):А зачем тебе каждый раз проходить весь путь? Взял FullPath парента и добавил к нему свое. Все.
SELECT FullPath||:new.Name from struct_el where Id_good=:new.Parent_Id into New.FullPath;
А если у парента парент изменится??? Тогда придется наоборот, всех чайлдов искать и изменять у них...тут даже посложнее будет

santilaas
Сообщения: 51
Зарегистрирован: 27 авг 2005, 21:05

Сообщение santilaas » 02 июн 2006, 15:37

Я вот тоже думал-думал. Скорее всего так и надо, как в книге. Вот только как из процедуры получить нужное значение?

Zhur
Сообщения: 125
Зарегистрирован: 01 мар 2006, 18:17

Сообщение Zhur » 02 июн 2006, 15:51

santilaas писал(а):Я вот тоже думал-думал. Скорее всего так и надо, как в книге. Вот только как из процедуры получить нужное значение?
А как в книге? Поподробнее.. а то я эту гнигу не видал. Я только по статьям тренируюсь

santilaas
Сообщения: 51
Зарегистрирован: 27 авг 2005, 21:05

Сообщение santilaas » 02 июн 2006, 20:28

Код: Выделить всё

CREATE PROCEDURE GETFULLNAME (ID_GOOD2SHOW INTEGER)
RETURNS (FULL_GOODS_NAME VARCHAR(250), ID_CHILD_GOOD INTEGER)
AS
DECLARE VARIABLE CURR_CHILD_NAME VARCHAR(35);
begin
   /*организуем внешний цикл For Select по непосредственным потомкам товара с ID_GOOD = ID_GOOD2SHOW*/
   for select gt.id_good, gt.good_name
       from GoodsTree gt
       where gt.id_parent=:Id_Good2Show
       into :ID_CHILD_GOOD, :full_goods_name
   do
       begin
       /*если у найденного узла с ID_PARENT_GOOD = ID_CHILD_GOOD нет потомков, то он является "листом" дерева
и попадает в результаты*/
          if (not exists (select * from GoodsTree
                          where GoodsTree.Id_parent_good=:id_child_good))
            then
               begin
                 /* Передаём лист дерева в результаты */
                 suspend;
               end
            else
               begin
                 /* сохраняем имя узла-родителя во временной переменной */
                 curr_child_name = full_goods_name;
                 /* рекурсивно запускаем эту процедуру */
                 for
                    select ID_CHILD_GOOD, full_goods_name
                    from GETFULLNAME (:ID_CHILD_GOOD)
                    into :ID_CHILD_GOOD, :full_goods_name
                 do
                    begin
                       /*добавляем имя узла-родителя к найденному имени потомка*/
                       full_goods_name = curr_child_name||'/'||full_goods_name;
                       suspend; /* возвращаем полное имя товара*/
                    end
               end
       end
end
- да и ещё разовью мысль -
1) во-первых,получается, если добавляем запись в мою таблицу, то ей должно автоматом в поле Fullpath
записаться полное название вплоть до текущего элемента (опять же - как вышеописанную процедуру вызвать, что получить
значение для path и собственно записать его?????)
(т.е. используем триггер Before insert)

2) Во-вторых, если изменяем элемент, а у него есть потомки, то и у них также должно обновиться значение поля FullPath
(т.е. используем триггер Before Update) - вопрос тот же: какой вызов процедуры?

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 02 июн 2006, 21:22

то ей должно автоматом в поле Fullpath
записаться полное название вплоть до текущего элемента (опять же - как вышеописанную процедуру вызвать, что получить
значение для path и собственно записать его?????)
(т.е. используем триггер Before insert)
где логика? я уже на sql.ru ответил. Все это напоминает здесь вопрос mila про "обработку вставленных записей в udf, которая вызывается из триггера на after insert".
Во-вторых, если изменяем элемент, а у него есть потомки, то и у них также должно обновиться значение поля FullPath
ну и накой тогда такая геморройная реализация? неужто тебе в статьях про "деревья" www.ibase.ru/develop.htm ничего не понравилось?

например, стандартное "дерево", описанное в моей статье, не требует никаких таких жутких телодвижений. Например, при переносе ветки дерева у переносимого элемента меняется parent_id и всё!

santilaas
Сообщения: 51
Зарегистрирован: 27 авг 2005, 21:05

Сообщение santilaas » 03 июн 2006, 09:03

Дело в том, что я и сам не любитель хранить полный путь (посоветовали мне).
И в принципе оно скорее всего и не нужно.
На самом деле ситуация такая:
в БД есть таблица№1 и таблица№2(дерево), в таблице№1 есть внешний ключ на таблицу№2. В приложении есть соответствующие этим таблицам формы. Из формы№1 я открываю форму№2, дважды кликаю на определённой записи таблицы-дерева и полный путь (т.е. Lookup-поле на ключ таблицы№2) должен отобразиться (в поле Edit или DbGrid-e) на форме№1.
Ну и соответственно если, просто открываем форму№1 - это lookup-поле уже должно отображать полный путь.

Zhur
Сообщения: 125
Зарегистрирован: 01 мар 2006, 18:17

Сообщение Zhur » 03 июн 2006, 12:05

Да и мне прищлось хранить полный путь... только не текстовые выражения типа 'NAME1\NAME2\NAME3', а их идентификаторы '1~1253~123'... короче как в той статье про деревья.
Связано это с тем, что у меня будут выборки чайлдрв, НЕ принадлежащих узлу. Без хранения поля FULLPATH такие запросы у меня долго работали (я использовал NOT IN ХП).
Кстати, про это даже недавно мой топик был.

santilaas
Сообщения: 51
Зарегистрирован: 27 авг 2005, 21:05

Сообщение santilaas » 03 июн 2006, 20:01

Спасибо всем, кто откликнулся.
В общем я послушав и подумав, решил путь в таблице оставить,
и для моей реальной таблицы (Kod, Kod_parent, Name, Path, Type, Power) получил три триггера:

Код: Выделить всё

----------------------------------------------
CREATE TRIGGER STRUCT_EL_BI_TRIG FOR STRUCT_EL
ACTIVE BEFORE INSERT POSITION 1
as
declare variable new_path varchar(251);
begin
   /* если элемент находится в самой верхушке дерева,
    тогда путь будет равен названию этого элемента*/
   if (new.kod_parent = 0) then
       new.path = new.name;
   /* иначе новый путь будет складываться из пути родителя
    + '\' + название нового элемента*/
   else
      begin
         select path
         from struct_el
         where kod = new.kod_parent
         into :new_path;
         new.path = :new_path||'\'||new.name;
      end
end
-----------------------------------------------
CREATE TRIGGER STRUCT_EL_BU_TRIG FOR STRUCT_EL
ACTIVE BEFORE UPDATE POSITION 0
as
declare variable new_path varchar(251);
begin
   /* если у данного элемента сменился родитель или название, тогда ...*/
   if ((new.kod_parent <> old.kod_parent) or
      (new.name <> old.name)) then
       if (new.kod_parent = 0) then
           new.path = new.name;
       else
          begin
             select path
             from struct_el
             where kod = new.kod_parent
             into :new_path;
             new.path = :new_path||'\'||new.name;
          end
end
-----------------------------------------------
CREATE TRIGGER STRUCT_EL_AU_TRIG FOR STRUCT_EL
ACTIVE AFTER UPDATE POSITION 0
as
begin
   if (new.path <> old.path) then
   update struct_el
   set path = new.path||'\'||name
   where kod_parent = new.kod;
end
------------------------------------------------
- вроде все работает, на быстродействие не проверял, но думаю для ограниченного в 7 уровней дерева - попрет.

Ответить