powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Microsoft SQL Server [игнор отключен] [закрыт для гостей] / Зацикливание функции (а также процедуры) в дереве
10 сообщений из 10, страница 1 из 1
Зацикливание функции (а также процедуры) в дереве
    #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
Зацикливание функции (а также процедуры) в дереве
    #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
Зацикливание функции (а также процедуры) в дереве
    #32070186
Фотография Pavel
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
И в догонку - строить дерево во временной таблице далеко не самое оптимальное решение. На форуме уже неоднократно обсуждались различные способы построения деревьев. Вот ссылочка для относительно небольших струтур: http://sdm.viptop.ru/articles/sqltrees.html
Существуют и другие, более оптимальные для разных условий методы.
...
Рейтинг: 0 / 0
Зацикливание функции (а также процедуры) в дереве
    #32070209
Антон
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
для Pavel:
Попробовали твой вариант. Недопустимые апдейты он отсекает, все правильно, в себя скопировать не дает. Но при попытке переместить узел в соседнюю ветку, то есть, произвести допустимую операцию, триггер опять зациклился.
конструкция Select @Parent_category_id = Parent_category_id From Categories Where Category_id = @Parent_category_id почему-то никогда не устанавливает @parent_category_id в NULL.
Похоже, что проблема все-таки не в том, как мы работаем с деревьями, а где-то в другом месте.
Другие варианты деревьев нам не подходят, это мы все уже читали.
...
Рейтинг: 0 / 0
Зацикливание функции (а также процедуры) в дереве
    #32070242
Фотография alexeyvg
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
2Антон
Почему вы пишете, что триггер опять зациклился?
Я вот запустил ваш скрипт и профалер. Там видно, что происходит зацикливание WHILE в процедуре; просто там неправильная логика. Триггер здесь ни при чём.
...
Рейтинг: 0 / 0
Зацикливание функции (а также процедуры) в дереве
    #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
Зацикливание функции (а также процедуры) в дереве
    #32070296
sysop
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
2 Антон

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

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


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