2012-04-26 35 views
7

Là một phần của một số tác vụ quản trị, chúng tôi có nhiều bảng mà mỗi trình kích hoạt cần tạo. Trình kích hoạt sẽ thiết lập một cờ và ngày trong cơ sở dữ liệu Kiểm toán khi một đối tượng đã được sửa đổi. Để đơn giản, tôi có một bảng với tất cả các đối tượng cần kích hoạt được tạo ra.lỗi sql động: 'TẠO TRIGGER' phải là câu lệnh đầu tiên trong lô truy vấn

Tôi cố gắng để tạo ra một số sql năng động để làm điều này cho từng đối tượng, nhưng tôi nhận được lỗi này:
'CREATE TRIGGER' must be the first statement in a query batch.

Đây là đoạn mã để tạo ra 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 

Nếu tôi sử dụng PRINT và dán đoạn mã vào một cửa sổ truy vấn mới, các đoạn mã thực thi mà không cần bất kỳ vấn đề.

Tôi đã xóa các tuyên bố GO vì điều này cũng gây ra lỗi.

Tôi đang thiếu gì?
Tại sao tôi gặp lỗi khi sử dụng EXEC(@sql); hoặc thậm chí EXEC sp_executesql @sql;?
Đây có phải là điều cần làm với ngữ cảnh trong phạm vi EXEC() không?
Cảm ơn rất nhiều vì đã giúp đỡ.

Trả lời

17

Nếu bạn sử dụng SSMS (hoặc tương tự khác công cụ) để chạy mã được tạo bởi đoạn mã này, bạn sẽ nhận được chính xác lỗi tương tự. Nó có thể chạy tất cả các quyền khi bạn chèn các dấu phân cách hàng loạt (GO), nhưng bây giờ bạn không, bạn sẽ phải đối mặt với cùng một vấn đề trong SSMS quá. Mặt khác, lý do tại sao bạn không thể đặt GO trong các tập lệnh động là vì GO không phải là một câu lệnh SQL, nó chỉ là dấu phân tách được công nhận bởi SSMS và một số công cụ khác. Có lẽ bạn đã nhận thức được điều đó.

Dù sao, điểm của GO là để công cụ biết rằng mã sẽ được tách ra và các phần của nó sẽ được tách riêng riêng. Và điều đó, riêng biệt, cũng là những gì bạn nên làm trong mã của mình.

Vì vậy, bạn có các tùy chọn này:

  • chèn EXEC sp_execute @sql ngay sau phần mà giọt kích hoạt, sau đó thiết lập lại giá trị của @sql để sau đó lưu trữ và chạy các phần định nghĩa lần lượt của nó;

  • sử dụng hai biến, @sql1@sql2, lưu trữ IF EXISTS/phần thả vào @sql1, CREATE TRIGGER một thành @sql2, sau đó chạy cả hai kịch bản (một lần nữa, riêng).

Nhưng sau đó, như bạn đã phát hiện ra, bạn sẽ phải đối mặt với một vấn đề khác: bạn không thể tạo một kích hoạt trong cơ sở dữ liệu khác mà không cần chạy báo cáo kết quả trong bối cảnh cơ sở dữ liệu.

Bây giờ, có 2 cách cung cấp bối cảnh cần thiết:

1) sử dụng một tuyên bố USE;

2) chạy (các) câu lệnh dưới dạng truy vấn động sử dụng EXEC targetdatabase..sp_executesql N'…'.

Rõ ràng, tùy chọn đầu tiên sẽ không hoạt động ở đây: chúng tôi không thể thêm USE … trước CREATE TRIGGER, vì sau này phải là câu lệnh duy nhất trong lô.

Tùy chọn thứ hai có thể được sử dụng, nhưng nó sẽ yêu cầu thêm một lớp năng động (không chắc chắn đó là một từ). Đó là vì tên cơ sở dữ liệu là thông số ở đây và do đó chúng tôi cần chạy EXEC targetdatabase..sp_executesql N'…'làm một tập lệnh động và do tập lệnh thực tế được chạy chính nó là một tập lệnh động nên sẽ được lồng hai lần.

Vì vậy, trước khi (thứ hai) EXEC sp_executesql @sql; dòng thêm như sau:

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

Như bạn thấy, để tích hợp các nội dung của @sql như một kịch bản động lồng nhau đúng cách, họ phải được đặt trong dấu nháy đơn. Vì lý do tương tự, mỗi dấu ngoặc đơn trong@sql phải được tăng gấp đôi (ví dụ: sử dụng REPLACE() function, như trong tuyên bố ở trên).

+0

Rất cám ơn vì điều này. Giờ đây tôi đã chia mã thành hai phần '' như bạn đề xuất trong tùy chọn đầu tiên ở trên, như sau: – MarkusBee

+0

[EDIT đã hết thời gian trên nhận xét trước.] Rất cám ơn. Tôi đã chia mã thành hai phần '' như bạn đề xuất trong tùy chọn đầu tiên của mình. Phần đầu tiên thực hiện hoàn hảo. Tôi sẽ làm rõ rằng quy trình được thực hiện từ cơ sở dữ liệu 'Kiểm toán' và các đối tượng yêu cầu trình kích hoạt nằm trong các cơ sở dữ liệu khác. Việc thi hành câu lệnh 'CREATE TRIGGER' bây giờ ném lỗi sau, ngay cả khi sử dụng tên bảng đầy đủ: " Không thể tạo trình kích hoạt trên [...] vì đích không nằm trong cơ sở dữ liệu hiện tại. " Có cách nào xung quanh vấn đề này không? Làm thế nào tôi có thể làm cho nó thực thi trong bối cảnh của cơ sở dữ liệu khác? Cảm ơn. – MarkusBee

+0

@markb: Vui lòng xem cập nhật của tôi. Tôi không chắc liệu mọi thứ có rõ ràng như tôi muốn hay không, vì vậy xin đừng ngần ngại hỏi. –

0

tạo trình kích hoạt phải được thực hiện theo lô thực hiện của riêng nó. Bạn đang ở trong một thủ tục để bạn không thể tạo ra nó.

Tôi đề nghị thêm @sql vào một bảng tạm thời và sau đó một lần proc được hoàn thành tạo ra tất cả các báo cáo, vòng bảng temp này để thực hiện chúng và tạo ra các trigger

Các vấn đề liên quan