İşte başka bir seçenek: çok satırlı güncellemelere izin veren ve hiçbir döngüyü zorlamayan bir tetikleyici. Bir kök eleman (ana NULL ile) bulana kadar ata zincirini çevirerek çalışır, böylece döngü olmadığını kanıtlar. Tabii ki bir döngü sonsuz olduğu için 10 nesil ile sınırlıdır.
Yalnızca geçerli değiştirilmiş satır kümesiyle çalışır, bu nedenle güncellemeler tablodaki çok sayıda çok derin öğeye dokunmadığı sürece, performans çok kötü olmamalıdır. O olacak, böylece tüm yol, her element için zincire kadar gitmek zorunda mı bazı performans etkisini.
Gerçekten "akıllı" bir tetikleyici, bir öğenin kendisine ulaşıp ulaşmadığını kontrol ederek ve sonra kefaletle doğrudan döngüleri arayacaktır. Bununla birlikte, bu, her döngü sırasında önceden bulunan tüm düğümlerin durumunu kontrol etmeyi gerektirir ve bu nedenle bir WHILE döngüsü ve şu anda yapmak istediğimden daha fazla kodlama gerektirir. Bu gerçekten daha pahalı olmamalı çünkü normal işlem döngüleri olmayacaktı ve bu durumda her döngü sırasında önceki tüm düğümlerden ziyade sadece önceki nesil ile çalışmak daha hızlı olacaktır.
@AlexKuznetsov ya da bu anlık görüntü izolasyonu nasıl olacağını başka birinden girdi isterim. Çok iyi olmayacağından şüpheleniyorum, ancak daha iyi anlamak istiyorum.
CREATE TRIGGER TR_Foo_PreventCycles_IU ON Foo FOR INSERT, UPDATE
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
IF EXISTS (
SELECT *
FROM sys.dm_exec_session
WHERE session_id = @@SPID
AND transaction_isolation_level = 5
)
BEGIN;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
END;
DECLARE
@CycledFooId bigint,
@Message varchar(8000);
WITH Cycles AS (
SELECT
FooId SourceFooId,
ParentFooId AncestorFooId,
1 Generation
FROM Inserted
UNION ALL
SELECT
C.SourceFooId,
F.ParentFooId,
C.Generation + 1
FROM
Cycles C
INNER JOIN dbo.Foo F
ON C.AncestorFooId = F.FooId
WHERE
C.Generation <= 10
)
SELECT TOP 1 @CycledFooId = SourceFooId
FROM Cycles C
GROUP BY SourceFooId
HAVING Count(*) = Count(AncestorFooId); -- Doesn't have a NULL AncestorFooId in any row
IF @@RowCount > 0 BEGIN
SET @Message = CASE WHEN EXISTS (SELECT * FROM Deleted) THEN 'UPDATE' ELSE 'INSERT' END + ' statement violated TRIGGER ''TR_Foo_PreventCycles_IU'' on table "dbo.Foo". A Foo cannot be its own ancestor. Example value is FooId ' + QuoteName(@CycledFooId, '"') + ' with ParentFooId ' + Quotename((SELECT ParentFooId FROM Inserted WHERE FooID = @CycledFooId), '"');
RAISERROR(@Message, 16, 1);
ROLLBACK TRAN;
END;
Güncelleme
Eklenen tabloya fazladan birleşmeyi nasıl önleyebileceğimi anladım. Herkes bir BOŞ içermeyenleri tespit etmek için GROUP BY yapmak için daha iyi bir yol görürse lütfen bana bildirin.
Geçerli oturum SNAPSHOT İZOLASYON seviyesindeyse OKUYU DEVRE DIŞI bir anahtar da ekledim. Bu maalesef tutarsızlıkları önleyecek olsa da maalesef engellemenin artmasına neden olacaktır. Bu, eldeki görev için kaçınılmazdır.