|
|
|
Зацикливание функции (а также процедуры) в дереве
|
|||
|---|---|---|---|
|
#18+
Уважаемые товарищи гуру! Может кто сталкивался? Типичная древовидная структура child-parent, нужно написать триггер на UPDATE, который исключал бы возможность перемещения ветви дерева в саму себя (или в своего потомка). Процедура MakeTree строит список всех потомков данного узла и проверяет присутствует ли новое значение родителя для перемещаемого узла в его потомках и возвращает единицу, если да. Далее строится сам триггер на UPDATE (для простоты разрешаем изменять только одну запись в один момент времени, то есть inserted должен иметь не более одной записи). Вроде все прозрачно, процедура MakeTree, будучи вызванной отдельно, по-честному строит дерево и возвращает правильный результат. А вот в случае, когда она вызывается из триггера, она наглухо зацикливается по совершенно непонятной причине на цикле while @level>0. Однако, если сделать триггер INSTEAD OF вместо FOR UPDATE, то все нормально. Можете пояснить, в чем тут дело? Запустите прилагаемый скрипт, все должно зациклиться CREATE TABLE [ActPack_1] ( [Id_ActPack] [INT] IDENTITY (1, 1) NOT NULL , [Id_Parent] [INT] NOT NULL , [Num] [int] NULL , [Name] Char(10) NOT NULL ) GO -- формируем простое деревце Insert Into ActPacK_1 (id_parent,name) values (0,'a') GO Insert Into ActPacK_1 (id_parent,name) values (1,'b') GO Insert Into ActPacK_1 (id_parent,name) values (2,'c') GO --Процедура, CREATE PROCEDURE MakeTree @current int, @newid int ,@ret bit output as SET NOCOUNT ON DECLARE @level int, @line char(20) CREATE TABLE #stack (id int, level int) CREATE TABLE #result (id int) INSERT INTO #stack VALUES (@current, 1) SELECT @level = 1 WHILE @level > 0 BEGIN IF EXISTS (SELECT * FROM #stack WHERE level = @level) BEGIN SELECT @current = id FROM #stack WHERE level = @level INSERT #result VALUES (@current) DELETE FROM #stack WHERE level = @level AND id = @current INSERT #stack SELECT id_actpack, @level + 1 FROM actpack_1 WHERE id_parent = @current IF @@ROWCOUNT > 0 SELECT @level = @level + 1 END ELSE SELECT @level = @level - 1 END -- WHILE if exists (SELECT id FROM #result where id in (@newid)) set @ret=1 else set @ret=0 --Триггер go CREATE TRIGGER TUB_ActPack ON ActPack_1 FOR UPDATE AS DECLARE @ID_CUR INT DECLARE @NEWPARENT INT DECLARE @NCOUNT INT SET NOCOUNT ON if @@ROWCOUNT>1 begin RAISERROR ('ИЗМЕНЯТЬ НЕ БОЛЬШЕ ОДНОЙ ЗАПИСИ',1,11) ROLLBACK TRAN RETURN end if update(id_parent) begin select @id_cur=i.id_actPack from deleted i --ID изменяемой записи select @newparent=i.id_parent from inserted i --ID нового родителя exec maketree @id_cur, @newparent , @ncount if @ncount = 1 begin RAISERROR ('НЕЛЬЗЯ ПЕРЕМЕЩАТЬ В САМОГО СЕБЯ',1,11) ROLLBACK TRAN RETURN end end commit go --Теперь, собственно, команда, котрая все зацикливает UPDATE actPack_1 SET id_parent = 3 WHERE id_actPack=1 ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 20.11.2002, 14:13:30 |
|
||
|
Зацикливание функции (а также процедуры) в дереве
|
|||
|---|---|---|---|
|
#18+
А зачем такая сложная проверка? Делай ее прямо в триггере (привожу кусок своего кода, но и так все понятно): Select @Category_id = Category_id, @Parent_category_id = Parent_category_id From Inserted While @Parent_category_id Is Not Null Begin If @Category_id = @Parent_Category_id Begin Rollback Tran Raiserror('Невозможно переместить категорию в ее подчиненную категорию', 16, 1) Return End Select @Parent_category_id = Parent_category_id From Categories Where Category_id = @Parent_category_id End ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 20.11.2002, 14:21:55 |
|
||
|
Зацикливание функции (а также процедуры) в дереве
|
|||
|---|---|---|---|
|
#18+
И в догонку - строить дерево во временной таблице далеко не самое оптимальное решение. На форуме уже неоднократно обсуждались различные способы построения деревьев. Вот ссылочка для относительно небольших струтур: http://sdm.viptop.ru/articles/sqltrees.html Существуют и другие, более оптимальные для разных условий методы. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 20.11.2002, 14:29:07 |
|
||
|
Зацикливание функции (а также процедуры) в дереве
|
|||
|---|---|---|---|
|
#18+
для Pavel: Попробовали твой вариант. Недопустимые апдейты он отсекает, все правильно, в себя скопировать не дает. Но при попытке переместить узел в соседнюю ветку, то есть, произвести допустимую операцию, триггер опять зациклился. конструкция Select @Parent_category_id = Parent_category_id From Categories Where Category_id = @Parent_category_id почему-то никогда не устанавливает @parent_category_id в NULL. Похоже, что проблема все-таки не в том, как мы работаем с деревьями, а где-то в другом месте. Другие варианты деревьев нам не подходят, это мы все уже читали. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 20.11.2002, 15:10:39 |
|
||
|
Зацикливание функции (а также процедуры) в дереве
|
|||
|---|---|---|---|
|
#18+
2Антон Почему вы пишете, что триггер опять зациклился? Я вот запустил ваш скрипт и профалер. Там видно, что происходит зацикливание WHILE в процедуре; просто там неправильная логика. Триггер здесь ни при чём. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 20.11.2002, 15:51:37 |
|
||
|
Зацикливание функции (а также процедуры) в дереве
|
|||
|---|---|---|---|
|
#18+
Alexeyvg: Читай внимательно первый постинг, я писал, что зацикливается не триггер, а процедура, и именно на WHILE, и именно при вызове ее из триггера, сама по себе она работает нормально, там правильная логика. Pavel: Твой вариант таки заработал, но только после того, как мы после Select @Parent_category_id = Parent_category_id From Categories Where Category_id = @Parent_category_id поставили if @@rowcount=0 break. То есть, насколько я понял, @parent_category_id не устанавливается в NULL, если где-то до этого ей было присвоено какое-то значение. То есть, проблема вроде решена, но почему зацикливался наш вариант, не понятно. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 20.11.2002, 16:05:57 |
|
||
|
Зацикливание функции (а также процедуры) в дереве
|
|||
|---|---|---|---|
|
#18+
2 Антон >Другие варианты деревьев нам не подходят, это мы все уже читали. Ну-ну. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 20.11.2002, 16:34:37 |
|
||
|
Зацикливание функции (а также процедуры) в дереве
|
|||
|---|---|---|---|
|
#18+
for SySop Вот-Вот ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 20.11.2002, 16:57:45 |
|
||
|
Зацикливание функции (а также процедуры) в дереве
|
|||
|---|---|---|---|
|
#18+
2Антон 1. При вызове из цикла допущена ошибка exec maketree @id_cur, @newparent , @ncount if @ncount = 1 ... Нужно вызывать: exec maketree @id_cur, @newparent , @ncount output 2. Если удалить триггер, выполнить update, и затем вызвать процедуру так, как она вызывается из триггера, то точно так-же происходит зацикливание: exec MakeTree 1, 3 ,null Вот скрипт: Код: plaintext 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 20.11.2002, 18:07:02 |
|
||
|
Зацикливание функции (а также процедуры) в дереве
|
|||
|---|---|---|---|
|
#18+
2Alexeyvg: Исправление указанной тобой ошибки (добавление OUTPUT) к исправлению ситуации не приводит, все равно зацикливается. Однако, благодаря тебе я разгадал загадку! Только что вдруг осенило. Глянул на твой скрипт и все понял. Триггера нет, но по-прежнему есть UPDATE. То есть, изменение, которое должен откатить триггер, на момент вего выполнения УЖЕ ВНЕСЕНО в таблицу ActPack, и процедура MakeTree пытается построить дерево, опираясь уже на неправильные данные, где корень дерева скопирован сам в себя, вот и цикл!!! Ведь это AFTER UPDATE триггер - сначала меняет данные, потом проверяет, и в случае неправильности - откатывает. Вот почему при INSTEAD OF все работало - изменения в таблицу ActPack не вносились!!! Век живи, век учись. Всем спасибо, все понятно. :) ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 20.11.2002, 23:57:44 |
|
||
|
|

start [/forum/topic.php?fid=46&msg=32070242&tid=1818631]: |
0ms |
get settings: |
8ms |
get forum list: |
9ms |
check forum access: |
2ms |
check topic access: |
2ms |
track hit: |
26ms |
get topic data: |
8ms |
get forum data: |
2ms |
get page messages: |
39ms |
get tp. blocked users: |
1ms |
| others: | 262ms |
| total: | 359ms |

| 0 / 0 |
