When making a SQL script to create a trigger on a table, I wanted to check that the trigger doesn't already exist before I create it. Otherwise the script cannot be run multiple times.
So I added a statement to first check whether the trigger exists. After adding that statement, the CREATE TRIGGER statement no longer works.
IF NOT EXISTS (SELECT name FROM sysobjects
WHERE name = 'tr_MyTable1_INSERT' AND type = 'TR')
BEGIN
CREATE TRIGGER tr_MyTable1_INSERT
ON MyTable1
AFTER INSERT
AS
BEGIN
...
END
END
GO
This gives:
Msg 156, Level 15, State 1, Line 5
Incorrect syntax near the keyword 'TRIGGER'.
The solution would be to drop the existing trigger and then create the new one:
IF EXISTS (SELECT name FROM sysobjects
WHERE name = 'tr_MyTable1_INSERT' AND type = 'TR')
DROP TRIGGER tr_MyTable1_INSERT
GO
CREATE TRIGGER tr_MyTable1_INSERT
ON MyTable1
AFTER INSERT
AS
BEGIN
...
END
GO
My question is: why is the first example failing? What is so wrong with checking the trigger exists?
Certain statements need to be the first in a batch (as in, group of statements separated by GO ).
Quote:
CREATE DEFAULT, CREATE FUNCTION, CREATE PROCEDURE, CREATE RULE, CREATE SCHEMA, CREATE TRIGGER, and CREATE VIEW statements cannot be combined with other statements in a batch. The CREATE statement must start the batch. All other statements that follow in that batch will be interpreted as part of the definition of the first CREATE statement.
It's simply one of the rules for SQL Server batches (see):
http://msdn.microsoft.com/en-us/library/ms175502.aspx
Otherwise you could change an object, say a table, and then refer to the change in the same batch, before the change was actually made.
Schema changes should always be seperate batch calls...I am guessing they do it to gaurantee your SELECT will succeed, if you modify schema in the same batch they may not be able to gaurantee that. Just a guess...
Related
I am having problems with some of my SQL scripts on SQL SERVER, indeed I am trying to transfer data from a column A to a column B of the same table and then drop the column B,
However my problem is that I have to check for the existence of A beforehand because the code is meant to be executed on a server where I don't have access (I work as a third party developper on a professionnal app)
Here is my code:
-- Export the data from the column name
-- Drop the column name
USE p_mynacellelocation_db_ChecklistWPF
GO
IF COL_LENGTH('model_trolley_part','name') IS NOT NULL
BEGIN
UPDATE model_trolley_part
SET name_en=[name];
ALTER TABLE model_trolley_part
DROP COLUMN [name];
END
In the case of the column name being non existent I would like not to do anything
However on execution of the code in a database where the column name is non existent SQL Server returns me:
Msg 207, Level 16, State 1, Line 12 Invalid column name 'name'.
Instead of jumping through all these hoops simply rename your original column.
https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-rename-transact-sql?view=sql-server-2017
exec sp_rename 'model_trolley_part.name', 'name_en', 'COLUMN'
You need to use dynamic SQL for this. The reason is that the parser will try to validate your code, even the code that won't be executed because its IF condition wouldn't be met.
The parser is smart enough to see there is no table named name, but it's not smart enough to realize that the code shouldn't get executed because of the IF, so it raises a parsing error.
Using dynamic SQL in the BEGIN..END block after the IF hides this from the parser so it will execute.
Try this:
IF EXISTS(SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'myTableName'
AND COLUMN_NAME = 'ColumnA')
BEGIN
// your update and drop code goes here
END
you might want to check your account privileges if you are modifying table structure etc..
I am implementing an auditing system on my database. It uses triggers on each table to log changes.
I need to make modifications to these triggers and so am producing ALTER scripts for each one.
What I'd like to do is only have these triggers be altered if they exist, ideally like so:
IF EXISTS (SELECT * FROM sysobjects WHERE type = 'TR' AND name = 'MyTable_Audit_Update')
BEGIN
ALTER TRIGGER [dbo].[MyTable_Audit_Update] ON [dbo].[MyTable]
AFTER Update
...
END
However when I do this I get an error saying "Invalid syntax near keyword TRIGGER"
The reason that these triggers may not exist is that auditing can be enabled/disabled on tables which the end user can specify. This involves either creating or dropping the triggers. I am unable to make the changes to the triggers upon creation as they are dynamically created and so I must still provide a way altering the triggers should they exist.
The alter statement has to be the first in the batch. So for sql server it would be:
IF EXISTS (SELECT * FROM sysobjects WHERE type = 'TR' AND name = 'MyTable_Audit_Update')
BEGIN
EXEC('ALTER TRIGGER [dbo].[MyTable_Audit_Update] ON [dbo].[MyTable]
AFTER Update
...')
END
This might be a similar issue to one that I found with Sybase years ago, where I found that when trying to execute a create table statement conditionally, the DDL is executed prior to assessing the conditional statement. The only workaround was to use execute immediate.
Unlike CREATE TRIGGER, I failed to find a reference that explicitly states that
CREATE TRIGGER must be the first statement in the batch
but it seems that this restriction applies to ALTER TABLE too.
The simple way to do this would be to DROP the TRIGGER and re-create it:
IF EXISTS (SELECT * FROM sysobjects WHERE type = 'TR' AND name = 'MyTable_Audit_Update')
DROP TRIGGER MyTable_Audit_Update
GO
CREATE TRIGGER [dbo].[MyTable_Audit_Update] ON [dbo].[MyTable]
AFTER Update
...
END
I'm trying to understand what specifically is happening in SQL that means the following syntax is not allowed (and I'm finding it hard to search for):
IF (OBJECT_ID('..sp_cake', 'P') is not null)
ALTER PROC sp_cake
as
select 1
I would expect the ALTER to be valid because T-SQL is wrapping it up in its own BEGIN-END block and nothing bad could happen with the rest of the script block.
This is what T-SQL is doing, wrapping everything up and keeping it cleanly separated:
IF (OBJECT_ID('..sp_cake', 'P') is not null)
BEGIN
ALTER PROC [dbo].[sp_cake]
as
BEGIN
select 1
END
END
And these examples would be the simplest expression of what I think I'm doing (and these are syntactically correct)
IF (OBJECT_ID('..sp_cake', 'P') is not null)
select 1
IF (OBJECT_ID('..sp_cake', 'P') is null)
select 1 -- i.e. this works and 1 is the output
I have read that the CREATE or ALTER must be the first statement in a query block, but I don't understand why.
I know that I can get around this problem by either:
creating a dummy sproc and then altering it outside of an IF block, or;
creating a string of the entire sproc and executing it as a statement;
but I don't see why it is not valid to test for existence and then ALTER.
I'm not sure what you mean by this statement:
I have read that the CREATE or ALTER must be the first statement in a
query block, but I don't understand why.
You are correct that these need to be the first statements in a batch. That is a property of the T-SQL language -- not something whose cause needs to be understood but something that you need to know to use the language properly. Typically, the structure to do what you want in SQL Server is:
IF (OBJECT_ID('..sp_cake', 'P') is not null)
BEGIN
DROP PROCEDURE dbo.sp_code
END;
GO
CREATE PROC [dbo].[sp_cake] as
BEGIN
select 1
END;
I do agree that it would be nice to have a create procedure if not exists or create or alter procedure. Getting that functionality requires lobbying Microsoft.
If I try to execute the following code, I get the errors
Msg 207, Level 16, State 1, Line 3 Invalid column name 'Another'. Msg
207, Level 16, State 1, Line 4 Invalid column name 'Another'.
even though the predicate for both IF statements always evaluates to false.
CREATE TABLE #Foo (Bar INT)
GO
IF (1=0)
BEGIN
SELECT Another FROM #Foo
END
GO
IF (1=0)
BEGIN
ALTER TABLE #Foo ADD Another INT
SELECT Another FROM #Foo
END
GO
DROP TABLE #Foo
This is probably over-simplified for the sake of the example; in reality what I need to do is select the values from a column, but only if the column exists. If it doesn't exist, I don't care about it. In the problem that drove me to ask this question, my predicate was along the lines of EXISTS (SELECT * FROM sys.columns WHERE object_id = #ID AND name = #Name). Is there a way to achieve this without resorting to my arch-enemy Dynamic SQL? I understand that my SQL must always be well-formed (i.e. conform to grammar) - even within a block that's never executed - but I'm flabbergasted that I'm also being forced to make it semantically correct too!
EDIT:
Though I'm not sure the code below adds much to the code above, it's a further example of the problem. In this scenario, I only want to set the value of Definitely (which definitely exists as a column) with the value from Maybe (which maybe exists as a column) if Maybe exists.
IF EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('dbo.TableName', 'U') AND name = 'Maybe')
BEGIN
UPDATE dbo.TableName SET Definitely = Maybe
END
SQL Server doesn't execute line by line. It isn't procedural like .net or Java code. So there is no "non-executed block"
The batch is compiled in one go. At this point, the column doesn't exist but it knows the table will be. Table does not have a column called "Another". Fail.
Exactly as expected.
Now, what is the real problem you are trying to solve?
Some options:
2 tables or one table with both columns
use Stored procedures to decouple scope
not use temp tables (maybe not needed; it could be your procedural thinking...)
dynamic SQL (from Mitch's deleted answer)
Edit, after comment;
Why not hide schema changes behind a view, rather than changing all code to work with columns that may/may not be there?
You can use EXEC to handle it. It's not really dynamic SQL if the code never actually changes.
For example:
CREATE TABLE dbo.Test (definitely INT NOT NULL)
INSERT INTO dbo.Test (definitely) VALUES (1), (2), (3)
IF EXISTS (SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.Test', 'U') AND
name = 'Maybe')
BEGIN
EXEC('UPDATE dbo.Test SET definitely = maybe')
END
SELECT * FROM dbo.Test
ALTER TABLE dbo.Test ADD maybe INT NOT NULL DEFAULT 999
IF EXISTS (SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.Test', 'U') AND
name = 'Maybe')
BEGIN
EXEC('UPDATE dbo.Test SET definitely = maybe')
END
SELECT * FROM dbo.Test
DROP TABLE dbo.Test
You can also try Martin Smith's "Workaround" using a non-existing table to get "deferred name resolution" for columns.
I had the same issue.
We are creating a script for all changes for years and this is the first time that we have this issue.
I've tried all your answers and didn't find the issue.
In my case it was because of temporary table within the script that I'm using also within a stored procedure, although every sentence has go.
I've found that if I'm adding if exists with drop to the temporary table after the script is using the temporary table, it is working correctly.
Best regards,
Chen
Derived from the answer by #gbn.
What i did to solve the issue was to use 'GO' between the ALTER query and the query that uses the column added by ALTER. This will make the 2 queries to be run as separate batches thereby ensuring your 'Another' column is there before the SELECT query.
Below is the code snippet with comments which describes the problem statement. We have an update trigger which internally calls another update trigger on the same table inspite of Recursive Trigger Enabled Property Set to false.
Would like to understand the reason for this as this is causing a havoc in my applications.
/* Drop statements for the table and triggers*/
IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'[dbo]. [t_upd_TestTrigger_002]'))
DROP TRIGGER [dbo].[t_upd_TestTrigger_002]
IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'[dbo].[t_upd_TestTrigger_002]'))
DROP TRIGGER [dbo].[t_upd_TestTrigger_001]
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TestTrigger]') AND type in (N'U'))
DROP TABLE [dbo].[TestTrigger]
CREATE TABLE [dbo].[TestTrigger] /*Creating a test table*/
(
[InternalKey] INT NOT NULL,
[UserModified] varchar(50) DEFAULT SUSER_SNAME()
)
/* Please run the snippet below as seperate batch, else you will get
an error that 'CREATE TRIGGER' must be the first statement in a
query batch.
CREATING A UPDATE TRIGGER FOR THE TEST TABLE
*/
CREATE TRIGGER [t_upd_TestTrigger_001] ON [dbo].[TestTrigger]
FOR UPDATE
AS
BEGIN
--This trigger has some business logic which gets executed
print 'In Trigger 001 '
END
/* Please run the snippet below as separate batch, else you will
get an error that 'CREATE TRIGGER' must be the first statement
in a query batch.
CREATING Another UPDATE TRIGGER FOR THE TEST TABLE
This trigger updates the audit fields in the table and it has to be
a separate trigger, We cannot combine this with other update triggers -
So table TestTrigger will have two FOR UPDATE triggers
*/
CREATE TRIGGER [t_upd_TestTrigger_002] ON [dbo].[TestTrigger]
FOR UPDATE
AS
print 'bad guy starts'
UPDATE SRC
SET UserModified = SUSER_SNAME()
FROM inserted AS INS
INNER JOIN dbo.[TestTrigger] AS SRC
ON INS.InternalKey = SRC.InternalKey
print 'bad guy ends'
/* INSERTING TEST VALUE IN THE TEST TRIGGER TABLE*/
INSERT INTO dbo.[TestTrigger](InternalKey,UserModified)
SELECT 1 ,'Tester1' UNION ALL
SELECT 2,'Tester2' UNION ALL
SELECT 3 ,'Tester3'
/* TestTrigger table has 3 records, we will update the InternalKey
of first record from 1 to 4. We would expect following actions
1) [t_upd_TestTrigger_001] to be executed once
2) [t_upd_TestTrigger_002] to be executed once
3) A message that (1 row(s) affected) only once.
On Execution, i find that [t_upd_TestTrigger_002] internally triggers
[t_upd_TestTrigger_001].
Please note Database level property Recursive Triggers enabled is
set to false.
*/
/*UPDATE THE TABLE SEE THE MESSAGE IN RESULT WINDOW*/
UPDATE dbo.[TestTrigger]
SET InternalKey = 4
WHERE InternalKey = 1
"Recursive Triggers enabled" does not affect transitive triggers.
Which means that if trigger A updates a table in a manner that activates trigger B, and trigger B updates the same table, so that trigger A is run again, SQL Server has no way of detecting and inhibiting this endless loop. Especially since trigger B can update other tables, and a trigger on them could update the original table again - this could become as complex as you like.
Eventually, the trigger nesting level limit will be reached, and the loop stops.
I suspect that both of your triggers update the source table in some way. SQL Server can only detect recursive triggers if a trigger is activating itself. I suppose that's not the case for you. Restructuring the triggers is the only clean way out.
As a (hackery) idea: You could append a field to the table (data-type and value is irrelevant) that is updated by no operation but by triggers. Then change your second-order triggers to update that field. Add an IF UPDATE() check for that field to your first-order trigger. Prevent the now redundant update if the field has been set. If that makes sense. ;-)
MSDN: Using Nested Triggers, see sections "Direct recursion" and "Indirect recursion".
You can use IF UPDATE(), as Tomalak described, to skip trigger logic if UserModified is being updated.
Another possibility is to move the UserModified column to a separate table to avoid recursion.
If you want to stop this kind of behaviour across the database totally, "To disable indirect recursion, set the nested triggers server option to 0 using sp_configure. For more information, see Using Nested Triggers."
Of course, there's always the consideration that you may want to actually use nested triggers.