2012-04-26 50 views
7

Como parte de algunas tareas administrativas, tenemos muchas tablas en las que cada una necesita un activador. El desencadenador establecerá un indicador y la fecha en la base de datos de auditoría cuando se modificó un objeto. Para simplificar, tengo una tabla con todos los objetos que necesitan triggers creados.error de sql dinámico: 'CREATE TRIGGER' debe ser la primera instrucción en un lote de consulta

Estoy tratando de generar algo de SQL dinámico para hacer esto para cada objeto, pero yo estoy consiguiendo este error:
'CREATE TRIGGER' must be the first statement in a query batch.

Este es el código para generar el 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 

Si uso PRINT y pegar el código para una nueva ventana de consulta, el código se ejecuta sin ningún problema.

He eliminado las declaraciones GO ya que esto también estaba dando errores.

¿Qué me estoy perdiendo?
¿Por qué me sale un error al usar EXEC(@sql); o incluso EXEC sp_executesql @sql;?
¿Tiene esto algo que ver con el contexto dentro de EXEC()?
Muchas gracias por cualquier ayuda.

Respuesta

17

Si utiliza SSMS (u otros similares herramienta) para ejecutar el código producido por esta secuencia de comandos, obtendrá exactamente el mismo error. Podría funcionar perfectamente cuando haya insertado delimitadores de lote (GO), pero ahora que no lo hace, también tendrá el mismo problema en SSMS.

Por otro lado, la razón por la que no puede poner GO en sus scripts dinámicos es porque GO no es una declaración SQL, es simplemente un delimitador reconocido por SSMS y algunas otras herramientas. Probablemente ya estés consciente de eso.

De todos modos, el punto de GO es que la herramienta sepa que el código debe dividirse y sus partes ejecutar por separado. Y eso, por separado, es lo que debe hacer en su código también.

Por lo tanto, tiene las siguientes opciones:

  • inserción EXEC sp_execute @sql justo después de la parte que cae el gatillo, a continuación, restablecer el valor de @sql a continuación, almacenar y ejecutar la parte de definición a su vez;

  • uso dos variables, @sql1 y @sql2, almacenan la SI EXISTE/parte DROP en @sql1, el CREATE TRIGGER uno en @sql2, a continuación, ejecutar ambas secuencias de comandos (de nuevo, por separado).

Pero entonces, como ya se ha averiguado, que se enfrentará otro problema: no se puede crear un disparador en otra base de datos, sin correr el comunicado en el contexto de esa base de datos.

Ahora, hay 2 maneras de proporcionar el contexto necesario:

1) utilizar una declaración USE;

2) ejecuta la (s) declaración (es) como una consulta dinámica usando EXEC targetdatabase..sp_executesql N'…'.

Obviamente, la primera opción no va a funcionar aquí: no podemos agregar USE … antes de CREATE TRIGGER, porque este último debe ser la única declaración en el lote.

La segunda opción puede ser utilizado, pero se requerirá una capa adicional de dinamicidad (no estoy seguro si se trata de una palabra). Es porque el nombre de la base de datos es un parámetro aquí, por lo que debemos ejecutar EXEC targetdatabase..sp_executesql N'…'como un script dinámico, y dado que se supone que el script real para ejecutarse es un script dinámico, se anidará dos veces.

Por lo tanto, antes de la (segunda) EXEC sp_executesql @sql; línea añadir lo siguiente:

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

Como se puede ver, para integrar el contenido de @sql como un script dinámico anidada correctamente, deben estar encerrados entre comillas simples. Por la misma razón, cada comilla en@sql se debe doblar (por ejemplo, usando REPLACE() function, como en la declaración anterior).

+0

Muchas gracias por esto. Ahora he dividido el código en dos "piezas" como sugiere en su primera opción anterior, de la siguiente manera: – MarkusBee

+0

[EDIT agotó el tiempo de espera en el comentario anterior] Muchas gracias. He dividido el código en dos "piezas" como sugieres en tu primera opción. La primera parte se ejecuta perfectamente. Aclararé que el procedimiento se ejecuta desde la base de datos 'Auditoría' y que los objetos que requieren desencadenantes se encuentran en otras bases de datos. Al exceder la instrucción 'CREATE TRIGGER' ahora se produce el siguiente error, incluso cuando se utiliza el nombre de tabla totalmente calificado: " No se puede crear el desencadenador [...] ya que el destino no está en la base de datos actual. " ¿Hay alguna forma de evitar esto? ¿Cómo puedo hacer que se ejecute dentro del contexto de otra base de datos? Gracias. – MarkusBee

+0

@markb: Por favor, mira mi actualización. No estoy seguro si todo está tan claro como me gustaría, así que no dude en preguntar. –

0

la creación de un disparador debe realizarse en su propio lote de ejecución. Estás dentro de un procedimiento por lo que no podrás crearlo.

Sugiero añadir el @sql en una tabla temporal y luego una vez que el proc se termina la generación de todas las declaraciones, bucle de esta tabla temporal para ejecutar y crear los disparadores

Cuestiones relacionadas