2012-04-26 8 views
7

W ramach niektórych zadań administracyjnych mamy wiele tabel, z których każda wymaga utworzenia wyzwalacza. Spust ustawi flagę i datę w bazie danych audytu, gdy obiekt zostanie zmodyfikowany. Dla uproszczenia, mam tabelę ze wszystkimi obiektami, które wymagają utworzonych wyzwalaczy.dynamiczny błąd sql: 'CREATE TRIGGER' musi być pierwszą instrukcją w grupie zapytań

Próbuję wygenerować dynamicznego SQL, aby to zrobić dla każdego obiektu, ale ja dostaję ten błąd:
'CREATE TRIGGER' must be the first statement in a query batch.

Oto kod, aby wygenerować SQL.

CREATE PROCEDURE [spCreateTableTriggers] 
AS 

BEGIN 

DECLARE @dbname  varchar(50), 
     @schemaname varchar(50), 
     @objname varchar(150), 
     @objtype varchar(150), 
     @sql  nvarchar(max), 
     @CRLF  varchar(2) 

SET  @CRLF = CHAR(13) + CHAR(10); 

DECLARE ObjectCursor CURSOR FOR 
SELECT DatabaseName,SchemaName,ObjectName 
FROM Audit.dbo.ObjectUpdates; 

SET NOCOUNT ON; 

OPEN ObjectCursor ; 

FETCH NEXT FROM ObjectCursor 
INTO @dbname,@schemaname,@objname; 

WHILE @@FETCH_STATUS=0 
BEGIN 

    SET @sql = N'USE '+QUOTENAME(@dbname)+'; ' 
    SET @sql = @sql + N'IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'''+QUOTENAME(@schemaname)+'.[Tiud_'[email protected]+'_AuditObjectUpdates]'')) ' 
    SET @sql = @sql + N'BEGIN DROP TRIGGER '+QUOTENAME(@schemaname)+'.[Tiud_'[email protected]+'_AuditObjectUpdates]; END; '[email protected] 
    SET @sql = @sql + N'CREATE TRIGGER '+QUOTENAME(@schemaname)+'.[Tiud_'[email protected]+'_AuditObjectUpdates] '[email protected] 
    SET @sql = @sql + N' ON '+QUOTENAME(@schemaname)+'.['[email protected]+'] '[email protected] 
    SET @sql = @sql + N' AFTER INSERT,DELETE,UPDATE'[email protected] 
    SET @sql = @sql + N'AS '[email protected] 
    SET @sql = @sql + N'IF EXISTS(SELECT * FROM Audit.dbo.ObjectUpdates WHERE DatabaseName = '''[email protected]+''' AND ObjectName = '''[email protected]+''' AND RequiresUpdate=0'[email protected] 
    SET @sql = @sql + N'BEGIN'[email protected] 
    SET @sql = @sql + N' SET NOCOUNT ON;'[email protected] 
    SET @sql = @sql + N' UPDATE Audit.dbo.ObjectUpdates'[email protected] 
    SET @sql = @sql + N' SET RequiresUpdate = 1'[email protected] 
    SET @sql = @sql + N' WHERE DatabaseName = '''[email protected]+''' '[email protected] 
    SET @sql = @sql + N'  AND ObjectName = '''[email protected]+''' '[email protected] 

    SET @sql = @sql + N'END' [email protected] 
    SET @sql = @sql + N'ELSE' [email protected] 
    SET @sql = @sql + N'BEGIN' [email protected] 
    SET @sql = @sql + N' SET NOCOUNT ON;' [email protected] 
    SET @sql = @sql + @CRLF 
    SET @sql = @sql + N' -- Update ''SourceLastUpdated'' date.'[email protected] 
    SET @sql = @sql + N' UPDATE Audit.dbo.ObjectUpdates'[email protected] 
    SET @sql = @sql + N' SET SourceLastUpdated = GETDATE() '[email protected] 
    SET @sql = @sql + N' WHERE DatabaseName = '''[email protected]+''' '[email protected] 
    SET @sql = @sql + N'  AND ObjectName = '''[email protected]+''' '[email protected] 
    SET @sql = @sql + N'END; '[email protected] 

    --PRINT(@sql); 
    EXEC sp_executesql @sql; 

    FETCH NEXT FROM ObjectCursor 
    INTO @dbname,@schemaname,@objname; 

END 

CLOSE ObjectCursor ; 
DEALLOCATE ObjectCursor ; 

END 

Jeśli używam PRINT kod i wklej go w nowym oknie zapytań, kod wykonuje bez żadnego problemu.

Usunąłem oświadczenia GO ponieważ to również dawało błędy.

Czego mi brakuje?
Dlaczego pojawia się błąd przy użyciu EXEC(@sql); lub nawet EXEC sp_executesql @sql;?
Czy ma to coś wspólnego z kontekstem w ramach EXEC()?
Wielkie dzięki za pomoc.

Odpowiedz

17

Jeśli używasz SSMS (lub inny podobny narzędzie) do uruchomienia kodu wyprodukowanego przez ten skrypt, otrzymasz dokładnie ten sam błąd. Może działać poprawnie po wstawieniu ograniczników wsadowych (GO), ale teraz, gdy tego nie zrobisz, napotkasz ten sam problem także w SSMS.

Z drugiej strony, powodem, dla którego nie można umieścić GO w skryptach dynamicznych, jest to, że GO nie jest instrukcją SQL, jest jedynie ogranicznikiem rozpoznawanym przez SSMS i niektóre inne narzędzia. Prawdopodobnie już to wiesz.

W każdym razie, punkt GO jest dla narzędzia wiedzieć, że kod powinien być podzielony, a jego części uruchamiać oddzielnie . I to, oddzielnie, jest tym, co powinieneś zrobić również w swoim kodzie.

Tak, masz następujące opcje:

  • wkładkę EXEC sp_execute @sql tylko po części, która spada na spust, a następnie zresetować wartość @sql następnie przechowywanie i uruchomić część definicji kolei;

  • zastosować dwie zmienne @sql1 i @sql2 przechowywać IF istnieje/upuszczenie części w @sql1, CREATE wyzwolić w @sql2, a następnie uruchomić obu scenariuszy (ponownie, oddzielnie).

Ale potem, jak już dowiedziałem się, będziesz twarz inny problem: nie można utworzyć wyzwalacz w innej bazie danych bez uruchamiania oświadczenie w kontekście tej bazy.

Teraz istnieją 2 sposoby zapewniania niezbędnego kontekstu:

1) używać USE oświadczenie;

2) Uruchom instrukcje jako zapytanie dynamiczne, używając EXEC targetdatabase..sp_executesql N'…'.

Oczywiście pierwsza opcja nie zadziała tutaj: nie możemy dodać USE … przed CREATE TRIGGER, ponieważ ta ostatnia musi być jedyną instrukcją w partii.

Druga opcja może być używane, ale będzie to wymagało dodatkowej warstwy dynamiczności (nie wiem, czy to słowo). Dzieje się tak, ponieważ nazwa bazy danych jest tu parametrem, więc musimy uruchomić skrypt dynamiczny, a ponieważ sam skrypt do wykonania jest skryptem dynamicznym, będzie on zagnieżdżony dwa razy.

Więc przed (drugiego) EXEC sp_executesql @sql; linii dodać następujące:

SET @sql = N'EXEC ' + @dbname + '..sp_executesql N''' 
      + REPLACE(@sql, '''', '''''') + ''''; 

Jak widać, aby zintegrować zawartość @sql jak zagnieżdżonego dynamiczny skrypt poprawnie, muszą być ujęte w apostrofy. Z tego samego powodu, każdy pojedynczy znak cudzysłowu musi być podwojony (na przykład przy użyciu REPLACE() function, jak w powyższym stwierdzeniu).

+0

Wielkie dzięki za to. Teraz podzieliłem kod na dwie części, jak sugerujesz w pierwszej opcji powyżej, w następujący sposób: – MarkusBee

+0

[EDYTOWAJ limit czasu dla poprzedniego komentarza.] Wielkie dzięki. Podzielę kod na dwie części, jak sugerujesz w pierwszej opcji. Pierwsza część wykonuje się doskonale. Wyjaśnię, że procedura jest wykonywana z bazy danych "Audyt", a obiekty wymagające wyzwalaczy znajdują się w innych bazach danych. Wykonywanie instrukcji "CREATE TRIGGER" powoduje zgłoszenie następującego błędu, nawet przy użyciu w pełni kwalifikowanej nazwy tabeli: "Nie można utworzyć wyzwalacza w [...], ponieważ obiekt docelowy nie znajduje się w bieżącej bazie danych." Czy jest jakiś sposób obejścia tego? Jak mogę go uruchomić w kontekście innej bazy danych? Dzięki. – MarkusBee

+0

@markb: Zobacz moją aktualizację. Nie jestem pewien, czy wszystko jest tak jasne, jak bym chciał, więc proszę, nie wahaj się zapytać. –

0

Tworzenie wyzwalacza musi być wykonywane we własnej partii wykonania. Jesteś wewnątrz procedury, więc nie będziesz w stanie jej stworzyć.

Proponuję dodanie @sql do tabeli temp, a następnie po zakończeniu proc jest generowanie wszystkich sprawozdań, pętla ta tabela temp je wykonywać i tworzyć wyzwalacze

Powiązane problemy