Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / Microsoft SQL Server [игнор отключен] [закрыт для гостей] / Зацикливание функции (а также процедуры) в дереве / 10 сообщений из 10, страница 1 из 1
20.11.2002, 14:13:30
    #32070172
Антон
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Зацикливание функции (а также процедуры) в дереве
Уважаемые товарищи гуру!
Может кто сталкивался?
Типичная древовидная структура 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
...
Рейтинг: 0 / 0
20.11.2002, 14:21:55
    #32070176
Pavel
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Зацикливание функции (а также процедуры) в дереве
А зачем такая сложная проверка? Делай ее прямо в триггере (привожу кусок своего кода, но и так все понятно):

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
...
Рейтинг: 0 / 0
20.11.2002, 14:29:07
    #32070186
Pavel
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Зацикливание функции (а также процедуры) в дереве
И в догонку - строить дерево во временной таблице далеко не самое оптимальное решение. На форуме уже неоднократно обсуждались различные способы построения деревьев. Вот ссылочка для относительно небольших струтур: http://sdm.viptop.ru/articles/sqltrees.html
Существуют и другие, более оптимальные для разных условий методы.
...
Рейтинг: 0 / 0
20.11.2002, 15:10:39
    #32070209
Антон
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Зацикливание функции (а также процедуры) в дереве
для Pavel:
Попробовали твой вариант. Недопустимые апдейты он отсекает, все правильно, в себя скопировать не дает. Но при попытке переместить узел в соседнюю ветку, то есть, произвести допустимую операцию, триггер опять зациклился.
конструкция Select @Parent_category_id = Parent_category_id From Categories Where Category_id = @Parent_category_id почему-то никогда не устанавливает @parent_category_id в NULL.
Похоже, что проблема все-таки не в том, как мы работаем с деревьями, а где-то в другом месте.
Другие варианты деревьев нам не подходят, это мы все уже читали.
...
Рейтинг: 0 / 0
20.11.2002, 15:51:37
    #32070242
alexeyvg
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Зацикливание функции (а также процедуры) в дереве
2Антон
Почему вы пишете, что триггер опять зациклился?
Я вот запустил ваш скрипт и профалер. Там видно, что происходит зацикливание WHILE в процедуре; просто там неправильная логика. Триггер здесь ни при чём.
...
Рейтинг: 0 / 0
20.11.2002, 16:05:57
    #32070258
Антон
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Зацикливание функции (а также процедуры) в дереве
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, если где-то до этого ей было присвоено какое-то значение.
То есть, проблема вроде решена, но почему зацикливался наш вариант, не понятно.
...
Рейтинг: 0 / 0
20.11.2002, 16:34:37
    #32070296
sysop
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Зацикливание функции (а также процедуры) в дереве
2 Антон

>Другие варианты деревьев нам не подходят, это мы все уже читали.

Ну-ну.
...
Рейтинг: 0 / 0
20.11.2002, 16:57:45
    #32070320
Антон
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Зацикливание функции (а также процедуры) в дереве
for SySop
Вот-Вот
...
Рейтинг: 0 / 0
20.11.2002, 18:07:02
    #32070364
alexeyvg
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Зацикливание функции (а также процедуры) в дереве
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.
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

UPDATE actPack_1 SET id_parent =  3  WHERE id_actPack= 1 
go

exec MakeTree  1 ,  3  ,null
...
Рейтинг: 0 / 0
20.11.2002, 23:57:44
    #32070477
Антон
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Зацикливание функции (а также процедуры) в дереве
2Alexeyvg:
Исправление указанной тобой ошибки (добавление OUTPUT) к исправлению ситуации не приводит, все равно зацикливается.
Однако, благодаря тебе я разгадал загадку! Только что вдруг осенило. Глянул на твой скрипт и все понял. Триггера нет, но по-прежнему есть UPDATE. То есть, изменение, которое должен откатить триггер, на момент вего выполнения УЖЕ ВНЕСЕНО в таблицу ActPack, и процедура MakeTree пытается построить дерево, опираясь уже на неправильные данные, где корень дерева скопирован сам в себя, вот и цикл!!! Ведь это AFTER UPDATE триггер - сначала меняет данные, потом проверяет, и в случае неправильности - откатывает. Вот почему при INSTEAD OF все работало - изменения в таблицу ActPack не вносились!!!
Век живи, век учись. Всем спасибо, все понятно. :)
...
Рейтинг: 0 / 0
Форумы / Microsoft SQL Server [игнор отключен] [закрыт для гостей] / Зацикливание функции (а также процедуры) в дереве / 10 сообщений из 10, страница 1 из 1
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]