2012-04-26 15 views
7

Come parte di alcune attività amministrative, abbiamo molti tavoli che ogni bisogno di un innesco creato. Il trigger imposta un flag e la data nel database Audit quando un oggetto è stato modificato. Per semplicità, ho una tabella con tutti gli oggetti che hanno bisogno di trigger creati.dinamica degli errori di SQL: 'CREATE TRIGGER' deve essere la prima istruzione in un batch di query

Sto cercando di generare un certo SQL dinamico per fare questo per ogni oggetto, ma sto ottenendo questo errore:
'CREATE TRIGGER' must be the first statement in a query batch.

Ecco il codice per generare il codice 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 

Se uso PRINT e incollare il codice per una nuova finestra di query, il codice viene eseguito senza alcun problema.

ho rimosso i GO dichiarazioni come questo è stato anche dà errori.

Cosa mi manca?
Perché ricevo un errore utilizzando EXEC(@sql); o addirittura EXEC sp_executesql @sql;?
Si tratta di qualcosa che ha a che fare con il contesto all'interno di EXEC()?
Molte grazie per qualsiasi aiuto.

risposta

17

Se si utilizza SQL Server Management Studio (o altri simili strumento) per eseguire il codice prodotto da questo script, si otterrà esattamente lo stesso errore. Potrebbe andare bene quando hai inserito dei delimitatori di lotti (GO), ma ora che non lo fai, dovrai affrontare lo stesso problema anche in SSMS.

D'altra parte, il motivo per cui non è possibile inserire GO negli script dinamici è perché GO non è un'istruzione SQL, è semplicemente un delimitatore riconosciuto da SSMS e alcuni altri strumenti. Probabilmente lo sai già.

In ogni caso, il punto di GO è per lo strumento per sapere che il codice dovrebbe essere diviso e le sue parti gestito separatamente . E questo, separatamente, è quello che dovresti fare anche nel tuo codice.

Quindi, ci sono queste opzioni:

  • inserto EXEC sp_execute @sql subito dopo la parte che scende il grilletto, quindi reimpostare il valore di @sql per poi memorizzare ed eseguire la parte definizione a sua volta;

  • uso due variabili, @sql1 e @sql2, conservano l'IF EXISTS/parte cadere nel @sql1, CREATE TRIGGER uno nell'altro @sql2, quindi eseguire entrambi gli script (di nuovo, separatamente).

Ma poi, come hai già scoperto, dovrai affrontare un altro problema: non è possibile creare un trigger in un altro database, senza eseguire l'istruzione nel contesto di quel database.

Ora, ci sono 2 modi di fornire il contesto necessario:

1) utilizzare un'istruzione USE;

2) eseguire le istruzioni come una query dinamica utilizzando EXEC targetdatabase..sp_executesql N'…'.

Ovviamente, la prima opzione non funzionerà qui: non è possibile aggiungere USE … prima di CREATE TRIGGER, perché quest'ultimo deve essere l'unica istruzione nel batch.

La seconda opzione può essere usato, ma richiederà un ulteriore livello di dinamicità (non so se è una parola). È perché il nome del database è un parametro qui e quindi è necessario eseguire EXEC targetdatabase..sp_executesql N'…'come uno script dinamico e, poiché lo script effettivo da eseguire è esso stesso presumibilmente uno script dinamico, pertanto, verrà nidificato due volte.

Quindi, prima della (seconda) EXEC sp_executesql @sql; linea di aggiungere il seguente:

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

Come si può vedere, per integrare i contenuti di @sql come uno script dinamica nidificato correttamente, devono essere racchiusi tra virgolette singole. Per lo stesso motivo, ogni singola virgoletta in@sql deve essere raddoppiata (ad esempio utilizzando REPLACE() function, come nella precedente dichiarazione).

+0

Mille grazie per questo. Ora ho diviso il codice in due "pezzi" come suggerito nella prima opzione sopra riportata, come segue: – MarkusBee

+0

[EDIT scaduto nel commento precedente.] Molte grazie. Ho diviso il codice in due "pezzi" come suggerisci nella tua prima opzione. La prima parte viene eseguita perfettamente. Spiegherò che la procedura viene eseguita dal database 'Audit' e gli oggetti che richiedono trigger si trovano in altri database. L'esecuzione dell'istruzione 'CREATE TRIGGER' ora genera il seguente errore, anche quando si utilizza il nome completo della tabella: " Impossibile creare trigger su [...] poiché la destinazione non si trova nel database corrente. " C'è un modo per aggirare questo? Come posso farlo eseguire nel contesto di un altro database? Grazie. – MarkusBee

+0

@markb: vedere il mio aggiornamento. Non sono sicuro che tutto sia chiaro come vorrei, quindi non esitare a chiedere. –

0

una creazione di attivazione deve essere fatto da sola esecuzione batch. Sei all'interno di una procedura in modo da non essere in grado di crearla.

Suggerisco di aggiungere il @sql in una tabella temporanea e quindi una volta che il proc è finire la generazione di tutte le dichiarazioni, cappio questa tabella temporanea per eseguire loro e creare i trigger

Problemi correlati