How to ignore the GO statements in IF EXISTS block? - sql

I am trying to execute a script based on IF NOT EXISTS condition. But, the block within BEGIN and END gets executed even if IF NOT EXISTS returns 1. I cannot change statements within BEGIN and END blocks. How to handle this situation?
IF NOT EXISTS(SELECT 1 FROM [dbo].[UPGRADEHISTORY] WHERE SCRIPTNAME='001-MarkSubmitted.sql' AND RELEASENUMBER= '1')
BEGIN
IF NOT EXISTS(SELECT 1 FROM [dbo].[Action] WHERE Name='mark As Submitted')
BEGIN
SET IDENTITY_INSERT [dbo].[Action] ON
INSERT INTO [dbo].[Action](Id,Name,CreatedBy,CreatedOn) VALUES (6,'mark As Submitted',1,getdate())
SET IDENTITY_INSERT [dbo].[Action] OFF
END
GO
INSERT INTO [dbo].[StatusActionMapping](ArtifactType,StatusId,ActionId,RoleId) VALUES ('Report',11,6,1)
GO
INSERT INTO [dbo].[UpgradeHistory] ([ReleaseNumber],[ScriptNumber],[ScriptName],[ExecutionDate]) VALUES (1, (SELECT FORMAT(MAX(SCRIPTNUMBER) + 1, 'd3') FROM UpgradeHistory WHERE ReleaseNumber= 1),'001-MarkSubmitted.sql',GETDATE());
END
GO

As I mention in the comment, GO is not a Transact-SQL operator, it's interpreted by your IDE/CLI as a batch separator.SQL Server Utilities Statements - GO:
SQL Server provides commands that are not Transact-SQL statements, but are recognized by the sqlcmd and osql utilities and SQL Server Management Studio Code Editor. These commands can be used to facilitate the readability and execution of batches and scripts.
GO signals the end of a batch of Transact-SQL statements to the SQL Server utilities.
The answer is simply, remove the GOs. There is no need for them here; it's because they are there that you are getting the error because separating the statements into different batches makes no sense here. What you have would be equivalent to having the following 3 "files" and trying to run them independently:
File 1:
IF NOT EXISTS(SELECT 1 FROM [dbo].[UPGRADEHISTORY] WHERE SCRIPTNAME='001-MarkSubmitted.sql' AND RELEASENUMBER= '1')
BEGIN
IF NOT EXISTS(SELECT 1 FROM [dbo].[Action] WHERE Name='mark As Submitted')
BEGIN
SET IDENTITY_INSERT [dbo].[Action] ON
INSERT INTO [dbo].[Action](Id,Name,CreatedBy,CreatedOn) VALUES (6,'mark As Submitted',1,getdate())
SET IDENTITY_INSERT [dbo].[Action] OFF
END
Would error due to a BEGIN with out END.
File 2:
INSERT INTO [dbo].[StatusActionMapping](ArtifactType,StatusId,ActionId,RoleId) VALUES ('Report',11,6,1)
This would run fine.
File 3:
INSERT INTO [dbo].[UpgradeHistory] ([ReleaseNumber],[ScriptNumber],[ScriptName],[ExecutionDate]) VALUES (1, (SELECT FORMAT(MAX(SCRIPTNUMBER) + 1, 'd3') FROM UpgradeHistory WHERE ReleaseNumber= 1),'001-MarkSubmitted.sql',GETDATE());
END
Would error due to an END without a BEGIN.
There's nothing in your query that will causing a parsing error like you're adding a new column to an existing table and reference it later in the batch, so there's no need to separate the batches. Just remove the GOs in the middle of your BEGIN...ENDs and this works as you require:
IF NOT EXISTS (SELECT 1
FROM [dbo].[UPGRADEHISTORY]
WHERE SCRIPTNAME = '001-MarkSubmitted.sql'
AND RELEASENUMBER = '1')
BEGIN
IF NOT EXISTS (SELECT 1
FROM [dbo].[Action]
WHERE Name = 'mark As Submitted')
BEGIN
SET IDENTITY_INSERT [dbo].[Action] ON;
INSERT INTO [dbo].[Action] (Id, Name, CreatedBy, CreatedOn)
VALUES (6, 'mark As Submitted', 1, GETDATE());
SET IDENTITY_INSERT [dbo].[Action] OFF;
END;
INSERT INTO [dbo].[StatusActionMapping] (ArtifactType, StatusId, ActionId, RoleId)
VALUES ('Report', 11, 6, 1);
INSERT INTO [dbo].[UpgradeHistory] ([ReleaseNumber], [ScriptNumber], [ScriptName], [ExecutionDate])
VALUES (1, (SELECT FORMAT(MAX(SCRIPTNUMBER) + 1, 'd3') --This is a REALLY bad idea. Use an IDENTITY or SEQUENCE
FROM UpgradeHistory
WHERE ReleaseNumber = 1), '001-MarkSubmitted.sql', GETDATE());
END;
GO
Also note my point on your final INSERT. FORMAT(MAX(SCRIPTNUMBER) + 1, 'd3') is going to end up with race conditions. Use an IDENTITY or SEQUENCE.

Related

tsql rowcount without executing update [duplicate]

This question already has answers here:
how to know how many rows will be affected before running a query in microsoft sql server 2008
(5 answers)
Closed 4 years ago.
I have got any TSQL UPDATE statement and I'd like to know the number of rows that would be affected or the ##ROWCOUNT before really executing the updates.
I think I could replace the UPDATE part of the query with a SELECT COUNT(1), but it seems to me there should be an easier way.
Is there an easier way?
If there is no easy solution in SQL, a solution in .NET/C# would be ok for me too; I'm using System.Data.SqlClient.SqlCommand to execute the command anyway.
I'm using MSSQL 2008.
Example:
create table T (
A char(5),
B int
)
insert into T values ('a', 1)
insert into T values ('A', 1)
insert into T values ('B', 2)
insert into T values ('X', 1)
insert into T values ('D', 4)
-- I do not want to execute this query
-- update T set A = 'X' where B = 1
-- but I want ot get the ##ROWCOUNT of it, in this case
-- the wanted result would be 3.
One method would be to use transactions:
begin transaction;
declare #rc int;
update T set A = 'X' where B = 1;
set #rc = ##rowcount;
. . .
commit; -- or rollback
Just about any other method would have race conditions, if other threads might be updating the table.
However, I am suspicious that this solves a real problem. I suspect that your real problem might have a better solution. Perhaps you should ask another question explaining what you are really trying to do.
You could wrap your script in a transaction and use ROLLBACK at the end to forgo saving your changes.
create table T (
A char(5),
B int
)
insert into T values ('a',
insert into T values ('A', 1)
insert into T values ('B', 2)
insert into T values ('X', 1)
insert into T values ('D', 4)
BEGIN TRANSACTION;
update T set A = 'X' where B = 1
SELECT ##RowCount;
ROLLBACK TRANSACTION;
When you are ready to save your changes, switch ROLLBACK to COMMIT and execute, or you can simply remove those lines. You can also name your transactions. Ref: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/transactions-transact-sql
You could add a SELECT * FROM T; after the rest of the script to confirm that your object was not actually updated.

Why is the ##ERROR always 0 and ##ROWCOUNT always 1?

I create a table like:
then I execute this statement:
update test
set tname = 'Joker'
where tid % 2 = 0
it shows:
which means 'there are two rows affected' in Chinese.
But if execute print ##rowcount immediately, the result is:
What else, if execute insert into test values('Paul','foo'), the result is:
which means:
Message 8101, Level 16, Status 1, Line 21.
Only when column list is used and IDENTITY_INSERT is ON, you can set values in the identity column of 'test' table explicitly'.
But if then execute print ##ERROR, it shows:
which I think should be 8101.
Could someone tell me why? Thanks
First issue is not replicable, I have prepared demo script
create table test(tid int identity(1,1), tname varchar(100))
insert into test values ('James'),('Jake'),('Tom'),('Mary')
update test
set tname = 'Joker'
where tid % 2 = 0
select ##ROWCOUNT --returns 2
Second issue is because you are running the select #error statement separately. That's why you are getting 0. If you run the last two statements together you will get 545 as result
set identity_insert test on
insert into test values ('James'),('Jake'),('Tom'),('Mary')
select ##ERROR --returns 545
For the second question (it would have been a good idea to post these as 2 separate questions), your SQL statement is trying to insert 'Paul' into the tid column. You need to separate the values like this:
INSERT INTO test VALUES ('Paul'), ('foo')
Better yet, be explicit with the column names:
INSERT INTO test (tname) VALUES ('Paul'), ('foo')

instead of trigger result output

I have created my first trigger. Please see the code section for the trigger below.
The triggers and the results are as expected, except for one thing.
So when I run the code below it will not insert the values into my table so the number of records remains unchanged.
insert into MatlabSearchPath(directory, userName)
values('madeup', 'default')
In the messages window though I get two lines. I don't understand why I see two lines and in particular 1 row affected - the number of records in my table hasn't changed?
(0 row(s) affected)
(1 row(s) affected)
Trigger
create trigger trDefaultPathInsert on DVLP_QES.dbo.MatlabSearchPath
instead of insert
as
begin
declare #defCount int
declare #retVal int
select #defCount = count(userName) from inserted where userName = 'Default'
if (#defCount > 0)
begin
select #retVal = count(HostName) from DVLP_QES.dbo.UserHostName where HostName = HOST_NAME()
if (#retVal > 0)
begin
insert into MatlabSearchPath select * from inserted
end
else
begin
insert into MatlabSearchPath select * from inserted where inserted.userName <> 'Default'
end
end
end
Update
I should mention that there a 3 triggers on this table, one is the trigger above the other one is a delete & the last one is an update
Your trigger does the following:
Counts records you are trying to insert, where userName equals 'Default'
In your case, count is 1.
Pay attention to your collation - if it's case sensitive, you are going to skip that whole branch of code.
If you enter the if branch, next thing trigger checks is if there are rows in UserHostName table where HostName equals host name of your client; pay attention that you don't think it should be host name of your server or something like that
If you enter the TRUE-branch, it should insert everything to the table; however, if not, it shouldn't insert anything. Of course, except if the collation is case sensitive, then revert the logic.
I I were you, I would add PRINT statements into trigger, just to make sure how does it execute.
create trigger trDefaultPathInsert on DVLP_QES.dbo.MatlabSearchPath
instead of insert
as
begin
declare #defCount int
declare #retVal int
select #defCount = count(userName) from inserted where userName = 'Default'
PRINT '#defCount'
PRINT #defCount
if (#defCount > 0)
begin
select #retVal = count(HostName) from DVLP_QES.dbo.UserHostName where HostName = HOST_NAME()
PRINT '#retVal'
PRINT #retVal
if (#retVal > 0)
begin
PRINT 'TRUE-BRANCH'
insert into MatlabSearchPath select * from inserted
end
else
begin
PRINT 'FALSE-BRANCH'
insert into MatlabSearchPath select * from inserted where inserted.userName <> 'Default'
end
end
EDIT
It seems that the message about rows affected can't be controlled inside the trigger. Even the standard SET NOCOUNT ON on the trigger beginning won't stop it from showing. This gave me notion that the message is a result of the trigger being successfully finished by calling it with X rows, where X will eventually be in the X row(s) affected message.
This SO question furtherly confirms the problem.
The situation here if the first message indicating cero is because the instead of trigger is uses to ignore the insert you sent and do instead whats in the trigger
You can debug your code with management studio

SQL: Duplicate trigger msg 3609

Good evening.
I had a task to create a trigger which will compare records inserted by
insert into tbl(row1, row2)
values('val1', 'val2')
So I wrote:
CREATE TRIGGER duplikat_miejsce ON miejsce
AFTER INSERT
AS
if exists ( select * from miejsce i
inner join inserted t on i.ulica=t.ulica and i.numer=t.numer and i.miasto=t.miasto and i.kod=t.kod)
begin
RAISERROR ('Adres juz istnieje',1,2)
rollback
end
go
Trigger itself creates. But it doesn't work properly. It gives messages:
Adres juz istnieje
Msg 50000, Level 1, State 2
Msg 3609, Level 16, State 1, Line 1
The transaction ended in the trigger. The batch has been aborted.
And what is most important, it gives the errors, when i DON'T DUPLICATE ANY OF COLUMNS AT ALL. It deny try of inserting ANY record to the table "miejsce"
Insert command I used:
insert into miejsce(id_miejsce, ulica, numer, miasto, kod, telefon, uwagi)
values (6, 'Widmowa', '14', 'Warszawka', '88-800', null, null)
You have created an AFTER Trigger which fire when the changes has been made to the database. you need to create an Instead of trigger so you can roll back any invalid operations before it is committed to the disk.
Something like this......
CREATE TRIGGER duplikat_miejsce ON miejsce
INSTEAD OF INSERT
AS
BEGIN
IF EXISTS (select * from miejsce i
inner join inserted t
on i.ulica = t.ulica
and i.numer = t.numer
and i.miasto = t.miasto
and i.kod = t.kod)
BEGIN
RAISERROR ('Adres juz istnieje',16,1)
END
ELSE
BEGIN
INSERT INTO miejsce (id_miejsce, ulica, numer, miasto, kod, telefon, uwagi)
SELECT t.id_miejsce, t.ulica, t.numer, t.miasto, t.kod, t.telefon, t.uwagi
FROM inserted t
WHERE NOT EXISTS (select 1
from miejsce i
WHERE i.ulica = t.ulica
and i.numer = t.numer
and i.miasto = t.miasto
and i.kod = t.kod)
END
END
If you want to raise error , error severity level must be above 10 because any error under severity level 11 are considered as warring messages not error.
The newly inserted row is already in the table when check is made inside the trigger. Here's a simplified sample to demonstrate it:
create table t (i int, j int);
go
insert t values (1,1);
go
create trigger tr
on t
after insert
as
select * from t;
if exists(select * from t inner join inserted i on t.i = i.i and t.j = i.j)
begin
raiserror ('Adres juz istnieje',1,2);
rollback;
end
go
insert t values(2,2)
go
drop table t
go
If you have a proper primery key, use it in the where clause.

Get ROWCOUNT from INSTEAD OF TRIGGER

A legacy app does an INSERT on a table with an instead of trigger and subsequently uses the rowcount for further processing.
We now need to opt out of certain INSERTs with the use of an INSTEAD OF INSERT trigger.
The problem is that ##ROWCOUNT still returns the number of attempted inserts.
For example, a fictitious trigger that will never complete an insert might be
ALTER TRIGGER [dbo].[trig_ACCOUNT_CREDITS_RunningTotalINSERT]
ON [dbo].[ACCOUNT_CREDITS]
INSTEAD OF INSERT
AS
BEGIN
--tried with NOCOUNT ON and OFF
SET NOCOUNT OFF;
--This is an example of the branching logic that might determine
--whether or not to do the INSERT
IF 1=2 --no insert will ever occur (example only)
BEGIN
INSERT INTO dbo.ACCOUNT_CREDITS (COL1, COL2)
SELECT COL1, COL2 from INSERTED
END
END
and some INSERT statements might be
--No rows will be inserted because value of COL1 < 5
INSERT INTO dbo.ACCOUNT_CREDITS (COL1, COL2) VALUES ( 3, 3)
--We would assume row count to be 0, but returns 1
select ##ROWCOUNT
--No rows will be inserted because value of COL1 < 5
INSERT INTO dbo.ACCOUNT_CREDITS (COL1, COL2)
SELECT 1, 1
union all
SELECT 2, 2
--We would assume row count to be 0, but returns 2
select ##ROWCOUNT
I can work around the issue, but it bothers me that I can't trust ##ROWCOUNT. I can find no reference to this issue on SO or those other knowledge banks. Is this simply a case of TRIGGERS ARE EVIL?
Can I affect ##ROWCOUNT?
Some statements may change ##ROWCOUNT inside the trigger.
Statement
SELECT * FROM INSERTED WHERE COL1 < 5
executes and set ##ROWCOUNT to 1
Put statement
SET NOCOUNT ON;
then
IF NOT EXISTS (SELECT * FROM INSERTED WHERE COL1 < 5)
BEGIN
SET NOCOUNT OFF;
INSERT INTO dbo.ACCOUNT_CREDITS (COL1, COL2)
SELECT COL1, COL2 from INSERTED
END
The Problem
I need information in the context of the main process that is only available in the context of the trigger.
The Solution
Whether getting ##ROWCOUNT or anything else from a trigger, or even passing information to a trigger, there are two methods that allow for sharing information with triggers:
SET CONTEXT_INFO / CONTEXT_INFO()
Local Temporary Tables (i.e. tables with names starting with a single #: #tmp)
I posted an example of using CONTEXT_INFO in an answer on a related question over at DBA.StackExchange: Passing info on who deleted record onto a Delete trigger. There was a discussion in the comments on that answer related to possible complications surrounding CONTEXT_INFO, so I posted another answer on that question using a temporary table instead.
Since that example dealt with sending information to a trigger, below is an example of getting information from a trigger as that is what this question is about:
First: Create a simple table
CREATE TABLE dbo.InsteadOfTriggerTest (Col1 INT);
Second: Create the trigger
CREATE TRIGGER dbo.tr_InsteadOfTriggerTest
ON dbo.InsteadOfTriggerTest
INSTEAD OF INSERT
AS
BEGIN
PRINT 'Trigger (starting): ' + CONVERT(NVARCHAR(50), ##ROWCOUNT);
SET NOCOUNT ON; -- do AFTER the PRINT else ##ROWCOUNT will be 0
DECLARE #Rows INT;
INSERT INTO dbo.InsteadOfTriggerTest (Col1)
SELECT TOP (5) ins.Col1
FROM inserted ins;
SET #Rows = ##ROWCOUNT;
PRINT 'Trigger (after INSERT): ' + CONVERT(NVARCHAR(50), #Rows);
-- make sure temp table exists; no error if table is missing
IF (OBJECT_ID('tempdb..#TriggerRows') IS NOT NULL)
BEGIN
INSERT INTO #TriggerRows (RowsAffected)
VALUES (#Rows);
END;
END;
Third: Do the test
SET NOCOUNT ON;
IF (OBJECT_ID('tempdb..#TriggerRows') IS NOT NULL)
BEGIN
DROP TABLE #TriggerRows;
END;
CREATE TABLE #TriggerRows (RowsAffected INT);
INSERT INTO dbo.InsteadOfTriggerTest (Col1)
SELECT so.[object_id]
FROM [master].[sys].[objects] so;
PRINT 'Final ##ROWCOUNT (what we do NOT want): ' + CONVERT(NVARCHAR(50), ##ROWCOUNT);
SELECT * FROM #TriggerRows;
Output (in Messages tab):
Trigger (starting): 91
Trigger (after INSERT): 5
Final ##ROWCOUNT (what we do NOT want): 91
Results:
RowsAffected
5