I have the below trigger:
CREATE Trigger enroll_limit on Enrollments
Instead of Insert
As
Declare @Count int
Declare @Capacity int
Select @Count = COUNT(*) From Enrollments
Select @Capacity = Capacity From CourseSections
If @Count < @Capacity
Begin
Insert Into Enrollments Select * From Inserted
End
GO
I’m getting an error msg saying:
‘CREATE TRIGGER’ must be the first statement in a query batch.
Mitch Wheat
296k43 gold badges465 silver badges541 bronze badges
asked Nov 13, 2012 at 23:12
1
The error message «‘CREATE TRIGGER’ must be the first statement in a query batch.» usually occurs when a preceding group (batch) of statements does not have a terminating GO
So, I would suggest adding add a GO
to the end of the preceding batch’s statements.
answered Nov 14, 2012 at 4:55
Mitch WheatMitch Wheat
296k43 gold badges465 silver badges541 bronze badges
1
If you are trying this from SQL Server Management Studio, here is another option which worked for me:
In the left pane, right-click on the database and select «New Query».
This connects you to the specific database. Now you can enter your create trigger statement as the first statement in the query window which opens. There is no need for a «use» command.
answered Jun 17, 2019 at 18:55
jdhildebjdhildeb
3,3223 gold badges17 silver badges25 bronze badges
If you use SSMS (or other similar tool) to run the code produced by this script, you will get exactly the same error. It could run all right when you inserted batch delimiters (GO
), but now that you don’t, you’ll face the same issue in SSMS too.
On the other hand, the reason why you cannot put GO
in your dynamic scripts is because GO
isn’t a SQL statement, it’s merely a delimiter recognised by SSMS and some other tools. Probably you are already aware of that.
Anyway, the point of GO
is for the tool to know that the code should be split and its parts run separately. And that, separately, is what you should do in your code as well.
So, you have these options:
-
insert
EXEC sp_execute @sql
just after the part that drops the trigger, then reset the value of@sql
to then store and run the definition part in its turn; -
use two variables,
@sql1
and@sql2
, store the IF EXISTS/DROP part into@sql1
, the CREATE TRIGGER one into@sql2
, then run both scripts (again, separately).
But then, as you’ve already found out, you’ll face another issue: you cannot create a trigger in another database without running the statement in the context of that database.
Now, there are 2 ways of providing the necessary context:
1) use a USE
statement;
2) run the statement(s) as a dynamic query using EXEC targetdatabase..sp_executesql N'…'
.
Obviously, the first option isn’t going to work here: we cannot add USE …
before CREATE TRIGGER
, because the latter must be the only statement in the batch.
The second option can be used, but it will require an additional layer of dynamicity (not sure if it’s a word). It’s because the database name is a parameter here and so we need to run EXEC targetdatabase..sp_executesql N'…'
as a dynamic script, and since the actual script to run is itself supposed to be a dynamic script, it, therefore, will be nested twice.
So, before the (second) EXEC sp_executesql @sql;
line add the following:
SET @sql = N'EXEC ' + @dbname + '..sp_executesql N'''
+ REPLACE(@sql, '''', '''''') + '''';
As you can see, to integrate the contents of @sql
as a nested dynamic script properly, they must be enclosed in single quotes. For the same reason, every single quotation mark in @sql
must be doubled (e.g. using the REPLACE()
function, as in the above statement).
в рамках некоторых административных задач у нас есть много таблиц, каждая из которых нуждается в создании триггера. Триггер установит флаг и дату в базе данных аудита, когда объект был изменен. Для простоты у меня есть таблица со всеми объектами, которые нужно создать триггеры.
Я пытаюсь создать динамический sql, чтобы сделать это для каждого объекта, но я получаю эту ошибку:'CREATE TRIGGER' must be the first statement in a query batch.
вот код для создания 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_'+@objname+'_AuditObjectUpdates]'')) '
SET @sql = @sql + N'BEGIN DROP TRIGGER '+QUOTENAME(@schemaname)+'.[Tiud_'+@objname+'_AuditObjectUpdates]; END; '+@CRLF
SET @sql = @sql + N'CREATE TRIGGER '+QUOTENAME(@schemaname)+'.[Tiud_'+@objname+'_AuditObjectUpdates] '+@CRLF
SET @sql = @sql + N' ON '+QUOTENAME(@schemaname)+'.['+@objname+'] '+@CRLF
SET @sql = @sql + N' AFTER INSERT,DELETE,UPDATE'+@CRLF
SET @sql = @sql + N'AS '+@CRLF
SET @sql = @sql + N'IF EXISTS(SELECT * FROM Audit.dbo.ObjectUpdates WHERE DatabaseName = '''+@dbname+''' AND ObjectName = '''+@objname+''' AND RequiresUpdate=0'+@CRLF
SET @sql = @sql + N'BEGIN'+@CRLF
SET @sql = @sql + N' SET NOCOUNT ON;'+@CRLF
SET @sql = @sql + N' UPDATE Audit.dbo.ObjectUpdates'+@CRLF
SET @sql = @sql + N' SET RequiresUpdate = 1'+@CRLF
SET @sql = @sql + N' WHERE DatabaseName = '''+@dbname+''' '+@CRLF
SET @sql = @sql + N' AND ObjectName = '''+@objname+''' '+@CRLF
SET @sql = @sql + N'END' +@CRLF
SET @sql = @sql + N'ELSE' +@CRLF
SET @sql = @sql + N'BEGIN' +@CRLF
SET @sql = @sql + N' SET NOCOUNT ON;' +@CRLF
SET @sql = @sql + @CRLF
SET @sql = @sql + N' -- Update ''SourceLastUpdated'' date.'+@CRLF
SET @sql = @sql + N' UPDATE Audit.dbo.ObjectUpdates'+@CRLF
SET @sql = @sql + N' SET SourceLastUpdated = GETDATE() '+@CRLF
SET @sql = @sql + N' WHERE DatabaseName = '''+@dbname+''' '+@CRLF
SET @sql = @sql + N' AND ObjectName = '''+@objname+''' '+@CRLF
SET @sql = @sql + N'END; '+@CRLF
--PRINT(@sql);
EXEC sp_executesql @sql;
FETCH NEXT FROM ObjectCursor
INTO @dbname,@schemaname,@objname;
END
CLOSE ObjectCursor ;
DEALLOCATE ObjectCursor ;
END
если Я использую PRINT
и вставить код в новое окно запроса, код выполняется без каких-либо проблем.
Я удалил GO
заявления, поскольку это также давало ошибки.
что я упустил?
Почему я получаю ошибку, используя EXEC(@sql);
или даже EXEC sp_executesql @sql;
?
Это как-то связано с контекстом EXEC()
?
Большое спасибо за любую помощь.
2 ответов
если вы используете SSMS (или другой подобный инструмент) для запуска кода, созданного этой скрипт, вы получите точно такую же ошибку. Он может работать нормально, когда вы вставили разделители пакетов (GO
), но теперь, когда вы этого не сделаете, вы столкнетесь с той же проблемой в SSMS.
С другой стороны, причина, почему вы не можете поставить GO
в ваших динамических скриптах это потому, что GO
не является оператором SQL, это просто разделитель, распознанный SSMS и некоторыми другими инструментами. Возможно вы уже знаете об этом.
в любом случае, точки GO
для инструмента, чтобы знать, что код должен быть разделен и его части работать отдельно. И это,отдельно, это то, что вы должны сделать в своем коде.
Итак, у вас есть следующие варианты:
-
вставить
EXEC sp_execute @sql
сразу после части, которая сбрасывает триггер, затем сбросьте значение@sql
затем сохранить и запустить часть определения в его свою очередь; -
использовать две переменные,
@sql1
и@sql2
, сохранить если существует / падение часть в@sql1
создать триггер в@sql2
, затем запустить оба скрипта (опять же, отдельно).
но тогда, как вы уже выяснили, вы столкнетесь с другой проблемой: вы не можете создать триггер в другой базе данных без запуска оператора в контексте этой базы данных.
теперь, 2 пути обеспечивать необходимый контекст:
1) использовать USE
отчетность;
2) Запустите инструкцию(ы) как динамический запрос, используя EXEC targetdatabase..sp_executesql N'…'
.
очевидно, что первый вариант здесь не работает: мы не можем добавить USE …
до CREATE TRIGGER
, потому что последний должен быть единственным оператором в пакете.
второй вариант can используется, но для этого потребуется дополнительный слой динамичность (не уверен, что это слово). Это потому что имя базы данных является параметром здесь, и поэтому нам нужно запустить EXEC targetdatabase..sp_executesql N'…'
as динамический скрипт, и поскольку фактический скрипт для запуска сам по себе должен быть динамическим скриптом, он, следовательно, будет вложен дважды.
Итак, перед (второй) EXEC sp_executesql @sql;
строка добавить следующее:
SET @sql = N'EXEC ' + @dbname + '..sp_executesql N'''
+ REPLACE(@sql, '''', '''''') + '''';
как вы можете видеть, интегрировать содержание @sql
как вложенный динамический скрипт, они должны быть заключены в одинарные кавычки. По той же причине, каждый одинарная кавычка на @sql
должно быть удвоено (например, с помощью REPLACE()
функции, как и в приведенном выше утверждении).
Создание триггера должно выполняться в собственном пакете выполнения. Вы находитесь внутри процедуры, поэтому вы не сможете ее создать.
Я предлагаю добавить @sql в временную таблицу, а затем, как только proc закончит генерировать все операторы, зациклите эту временную таблицу, чтобы выполнить их и создать триггеры
Если вы используете SSMS (или другой аналогичный инструмент) для запуска кода, созданного этим script, вы получите точно такую же ошибку. При включении разделителей партий (GO
) он может работать нормально, но теперь, когда вы этого не сделаете, вы столкнетесь с той же проблемой в SSMS тоже.
С другой стороны, причина, по которой вы не можете поставить GO
в свои динамические сценарии, состоит в том, что GO
не является оператором SQL, а просто разделителем, распознанным SSMS и некоторыми другими инструментами. Возможно, вы уже знаете об этом.
В любом случае, точка GO
предназначена для того, чтобы инструмент знал, что код должен быть разделен, а его части выполняются отдельно. И это отдельно, это то, что вы должны делать и в своем коде.
Итак, у вас есть следующие опции:
вставьте EXEC sp_execute @sql
сразу после части, которая сбрасывает триггер, затем reset значение @sql
, чтобы затем сохранить и запустить часть определения в свою очередь;
используйте две переменные, @sql1
и @sql2
, сохраните часть IF EXISTS/DROP в @sql1
, CREATE TRIGGER один в @sql2
, затем запустите оба сценария (опять же, отдельно).
Но тогда, как вы уже выяснили, вы столкнетесь с другой проблемой: вы не можете создать триггер в другой базе данных без запуска инструкции в контексте этой базы данных.
Теперь существует два способа предоставления необходимого контекста:
1) используйте инструкцию USE
;
2) запустите оператор как динамический запрос, используя EXEC targetdatabase ..sp_executesql N'…'
.
Очевидно, что первый вариант не будет работать здесь: мы не можем добавить USE …
до CREATE TRIGGER
, потому что последнее должно быть единственным утверждением в пакете.
Можно использовать второй вариант, но для этого потребуется дополнительный уровень динамичности (не уверен, что это слово). Это потому, что здесь имя базы данных является параметром, поэтому нам нужно запустить EXEC targetdatabase ..sp_executesql N'…'
как динамический script, а так как фактический script для запуска сам должен быть динамическим script, он, следовательно, будет быть вложенными дважды.
Итак, перед строкой (второй) EXEC sp_executesql @sql;
добавьте следующее:
SET @sql = N'EXEC ' + @dbname + '..sp_executesql N'''
+ REPLACE(@sql, '''', '''''') + '''';
Как вы можете видеть, для правильного включения содержимого @sql
в качестве вложенного динамического script они должны быть заключены в одинарные кавычки. По той же причине каждая отдельная кавычка в @sql
должна быть удвоена (например, с помощью REPLACE()
function, как в приведенном выше заявлении).
MurCode
- Форумы
- Поиск
- О проекте
jek77
Дата: 15.04.2013 16:13:40
Добрый день, я в sql server работаю в первые и не совсем смог разобраться. Я создал базу данных, заполнил ее таблицами, но в этих таблицах не могу прописать триггеры. у меня есть запросы
select * from age_group
create trigger Deleting_Delo on Delo
after delete
as
begin
delete from krujok
where delo_number=(select delo_number from deleted)
end
create trigger Deleting_Child on Child
for delete as
begin
delete from info
where info_number=(select ch_info_number from deleted)
end
create trigger Deleting_Parent on Parent
for delete as
begin
delete from info
where (info_number=(select p_info_number from deleted)) and (count(select * from delo where))
end
помогите по этим запросам прописать триггер. Заранее спасибо!!!!
PaulYoung
Дата: 15.04.2013 16:16:50
jek77,
вопрос в том, зачем Вам триггеры?
Glory
Дата: 15.04.2013 16:17:14
jek77 |
---|
помогите по этим запросам прописать триггер. |
Это и есть скрипты создания триггеров
Надо только выполнить их
PaulYoung
Дата: 15.04.2013 16:17:59
Glory,
последний, кстати, не выполнится…
jek77
Дата: 15.04.2013 16:18:57
PaulYoung,
Для того что бы далее работать с приложением, и выполнять удаление.
Гость333
Дата: 15.04.2013 16:19:13
jek77 |
---|
помогите по этим запросам прописать триггер |
Ровно один триггер?
jek77
Дата: 15.04.2013 16:20:04
Glory,
Они не выполняются, выдает ошибку-Сообщение 111, уровень 15, состояние 1, строка 30
CREATE TRIGGER должна быть первой инструкцией в пакетном запросе.
Сообщение 111, уровень 15, состояние 1, строка 40
CREATE TRIGGER должна быть первой инструкцией в пакетном запросе.
Сообщение 111, уровень 15, состояние 1, строка 47
CREATE TRIGGER должна быть первой инструкцией в пакетном запросе.
Сообщение 156, уровень 15, состояние 1, строка 51
PaulYoung
Дата: 15.04.2013 16:20:14
jek77 |
---|
PaulYoung,
Для того что бы далее работать с приложением, и выполнять удаление. |
Это можно сделать и без триггеров
jek77
Дата: 15.04.2013 16:20:49
PaulYoung,
а как это еще сделать?
Glory
Дата: 15.04.2013 16:21:05
jek77 |
---|
Glory,
Они не выполняются, выдает ошибку |
Ну так раз ошибка, то значит скрипт надо исправить