T-SQL Update Trigger - sql-server

I'm trying to create the following trigger in SQL Server, but SSMS throws an error and I have no clue what it is. Any thoughts ?
Msg 156, Level 15, State 1, Line 2
Incorrect syntax near the keyword 'trigger'.
Code:
IF NOT EXISTS(SELECT * FROM sys.triggers
WHERE object_id = OBJECT_ID(N'[dbo].[trAfterUpdateInfoDoc]'))
CREATE TRIGGER [dbo].[trAfterUpdateInfoDoc]
ON [dbo].[InfoDocs]
AFTER UPDATE
AS
BEGIN
DECLARE #infodoctemplateid INT;
DECLARE #infodocid INT;
DECLARE #requireccount FLOAT(2);
DECLARE #filledcount FLOAT(2);
DECLARE #pcnt FLOAT(2);
DECLARE c CURSOR FOR
SELECT id
FROM InfoDocs ifd
WHERE exists (SELECT 1 FROM Inserted AS i WHERE i.id = ifd.id)
OPEN c
FETCH NEXT FROM c INTO #infodocid
WHILE ##Fetch_Status = 0
BEGIN
SELECT #infodoctemplateid = InfoDocTemplateId
FROM InfoDocs
WHERE id = #infodocid;
SELECT #requireccount = COUNT(*)
FROM InfoDocTemplateFields
WHERE InfoDocTemplateId = #infodoctemplateid
AND IsRequired = 1;
IF (#requireccount = 0)
BEGIN
set #pcnt = 100;
END
ELSE
BEGIN
select #filledcount = count(*) from InfoDocFields
where InfoDocId = #infodocid
and InfoDocTemplateFieldId in (select id from InfoDocTemplateFields where InfoDocTemplateId = #infodoctemplateid and IsRequired = 1)
and (BooleanValue is not null or (StringValue is not null and StringValue <> '') or IntValue is not null or DateValue is not null)
set #pcnt = #filledcount / #requireccount * 100.0;
END
update InfoDocs set PercentageCompleted = #pcnt Where id = #infodocid;
Fetch next From c into #infodocid
End
Close c
Deallocate c
END

Create Trigger (Limitations section) must be the first statement in a batch, so you can't use the IF exists check before it.
In SQL Server 2016 SP1 onwards, you can use CREATE OR ALTER TRIGGER... for the same behaviour.
Pre-SQL Server 2016 SP1, there's some suggestions here
I also second Zohar's comment that putting this logic into a trigger could well cause you many performance issues & possibly hard to track down unexpected behaviour/bugs.

Anytime a SQL object like a trigger is created, it needs to be the only object created in the batch. A batch is terminated by the keyword GO.
Try refactoring your code to fit this general structure and see if it works:
IF OBJECT_ID(N'[dbo].[trAfterUpdateInfoDoc]') IS NOT NULL
DROP TRIGGER [dbo].[trAfterUpdateInfoDoc]
GO
CREATE TRIGGER [dbo].[trAfterUpdateInfoDoc]
ON [dbo].[InfoDocs]
AFTER UPDATE
AS
BEGIN
--PLACE CODE HERE
END
GO

Related

SQL ELSE block still throws error when IF condition is true

I have the following T-SQL script that copies the value of an old column into a new one, then drops the old column. See here:
--step 1: create new column
IF NOT EXISTS(SELECT 1 from sys.columns
WHERE Name = N'UserColumn2'
AND Object_ID = Object_ID(N'Account'))
BEGIN
ALTER TABLE Account
ADD UserColumn2 int null
;
END
GO
;
--step 2: copy and drop
IF NOT EXISTS(SELECT 1 from sys.columns
WHERE Name = N'UserColumn1'
AND Object_ID = Object_ID(N'Account'))
BEGIN
PRINT 'Column ''UserColumn1'' does not exist.';
END
ELSE
BEGIN
UPDATE Account
SET UserColumn2 = UserColumn1
WHERE UserColumn1 is not null
;
BEGIN TRY
Declare #count int;
SELECT #count = Count(AccountID)
FROM Account
WHERE UserColumn2 <> UserColumn1
;
IF #count > 0
BEGIN
--PRINT 'Not all records were properly updated. UserColumn1 has not been dropped.';
THROW 50000,'Not all records were properly updated. UserColumn1 has not been dropped.',1
;
END
ELSE
BEGIN
ALTER TABLE Account
DROP Column UserColumn1
;
END
END TRY
BEGIN CATCH THROW; END CATCH
END
GO
;
The first step runs correctly but the second step still throws an error in the ELSE block even if the UserColumn1 column doesn't exist:
(note: this actually throws on line 24 for the code here. The code in my SSMS doesn't have the comments for 'step 1', etc.)
Why is this happening and how can I prevent it?
I've tried removing the NOT and moving the instructions out of the ELSE block but the behavior did not change. I've also tried writing the beginning of the second step like this:
IF (SELECT 1 from sys.columns
WHERE Name = N'UserColumn1'
AND Object_ID = Object_ID(N'Account')) <> null
and I get the same result.
The issue is that the entire sql text is parsed and compiled before it's executed, the error is being thrown at compile time.
You could workaround it by executing the update statement in its own process using dynamic sql - although there is nothing dynamic in this usage, it simply defers the compilation and execution of the update statement where it only happens in your else condition:
IF NOT EXISTS(SELECT 1 from sys.columns
WHERE Name = N'UserColumn1'
AND Object_ID = Object_ID(N'Account'))
BEGIN
PRINT 'Column ''UserColumn1'' does not exist.';
END
ELSE
BEGIN
EXEC sp_executesql N'
UPDATE Account
SET UserColumn2 = UserColumn1
WHERE UserColumn1 is not null;'
...
...

Trigger causes error (subquery return more than one value) on Bulk Insert

Alter Trigger [dbo].[DiscountUpdate]
on [dbo].[t_PromoDtl]
Instead of insert
as
begin
Declare #Barcode nvarchar(25);
Declare #disper decimal(18,0);
Declare #status int;
Declare #BranchID nvarchar(15);
set #Barcode = (Select barcodeFull from inserted); ---/// I think error happens in here.
set #disper = (Select disPer from inserted); ---/// I think error happens in here.
set #status = (Select p.status from inserted p); ---/// I think error happens in here.
begin
if #status = 2
begin
update t_Prd
set PrdDiscnt = #disper
where BarcodeFull = #Barcode;
end
else
begin
update t_Prd
set PrdDiscnt = 0
where BarcodeFull = #Barcode;
end
end
end
Here is my C# code..
using (var sqlBulk3 = new SqlBulkCopy(_connectionString, SqlBulkCopyOptions.FireTriggers | SqlBulkCopyOptions.CheckConstraints))
{
using (SqlConnection con6 = new SqlConnection(_connectionString))
{
con6.Open();
SqlCommand cmdtt = new SqlCommand("Truncate Table t_PromoDtl", con6);
cmdtt.CommandType = CommandType.Text;
cmdtt.ExecuteNonQuery();
con6.Close();
}
sqlBulk3.DestinationTableName = "t_PromoDtl";
sqlBulk3.WriteToServer(PromoDtl);
}
When Bulk insert starts, the trigger throws this error:
Sub query returns more than one value....
I looked at this trigger which updates t_Prd table instead of insert on t_PromoDtl table.
set #Barcode = (Select barcodeFull from inserted); ---/// I think error happens in here.
set #disper = (Select disPer from inserted); ---/// I think error happens in here.
set #status = (Select p.status from inserted p); ---/// I think error happens in here.
You seem to assume that the SQL Server trigger will be fired separately for each row - this is NOT the case - the trigger is fired only once for a statement. And if this is a BULK INSERT, then the Inserted pseudo table will contain multiple rows - so your statements like
set #Barcode = (Select barcodeFull from inserted);
are in fact the source of the problem - which one of the 250 rows inserted are you selecting here? It's not determined - you'll get back one arbitrary row - and what happens to the other 249 rows also inserted?? They're just plain ignored and not handled.
You need to rewrite your entire trigger logic to be set-based and handle the fact that the Inserted pseudo table will most likely contain multiple rows.
Try something like this:
ALTER TRIGGER [dbo].[DiscountUpdate]
ON [dbo].[t_PromoDtl]
INSTEAD OF INSERT
AS
BEGIN
-- update "dbo.T_Prd.PrdDiscnt" to "disPer" when status is 2
UPDATE p
SET PrdDiscnt = i.disPer
FROM dbo.T_Prd p
INNER JOIN Inserted i ON i.BarcodeFull = p.BarcodeFull
WHERE i.Status = 2;
-- update "dbo.T_Prd.PrdDiscnt" to "0" when status is not 2
UPDATE p
SET PrdDiscnt = 0
FROM dbo.T_Prd p
INNER JOIN Inserted i ON i.BarcodeFull = p.BarcodeFull
WHERE i.Status <> 2;
I'm assuming here that BarcodeFull is your primary key column that uniquely identifies each row in your table - if that's not the case, you might need to adapt the JOIN condition to match your situation.

Insert trigger for duplicate records not working sql server

ALTER TRIGGER [dbo].[STOK_HARKETLERI_Insert]
ON [dbo].[STOK_HAREKETLERI]
FOR INSERT
AS BEGIN
declare #tip int
declare #miktar float
declare #stokkod nvarchar
declare #tarih datetime
declare #counter int
Select
#tip = sth_tip, #miktar = sth_miktar,
#stokkod = sth_stok_kod, #tarih = sth_tarih
from inserted
select #Counter = COUNT(sth_tip)
from STOK_HAREKETLERI
where sth_evraktip = 6
and sth_tip = #tip
and sth_miktar = #miktar
and #stokkod = sth_stok_kod
and #tarih = sth_tarih
if (#counter>=1)
begin
rollback
RAISERROR ('Record already exists', 17, -1) with log
end
END
GO
The trigger is not being triggered on insert statements, however if I remove the variables and fill the data and run it on SQL Server it is running fine.
Any suggestions?
One more thing if I change the line (#counter >= 1) to (#counter >= 0) it starts working again.
If you insert more than one row, there will be more than one row in "inserted", but you're only checking the last of them. It might be easier to make a check constraint, depending on what the rules regarding "sth_evraktip = 6 " actually are (can there be more rows done with update later etc).
With insert trigger something like this might work:
if exists (select 1 from inserted i
where exists(select 1 from STOK_HAREKETLERI S
where S.sth_evraktip = 6
and S.sth_tip = i.sth_tip
and S.sth_miktar = i.sth_miktar
and S.sth_stok_kod = i.sth_stok_kod
and S.sth_tarih = i.sth_tarih
and S.sth_RECno < i.sth_RECno)) begin
rollback
RAISERROR ('Record already exists', 17, -1) with log
end
If any of the columns can contain NULL, then you'll have to add more logic to handle that.
If i skip the variable declaration and value passing to variable trigger works flawlessly.FOR EACH LINE in my case
Edited code is posted below.
Create TRIGGER [dbo].[STOK_HARKETLERI_Insert]
ON [dbo].[STOK_HAREKETLERI]
FOR INSERT
AS
BEGIN
Declare #counter int
select #counter = COUNT(sth_tip) from STOK_HAREKETLERI
where sth_evraktip = 6
and sth_tip = (select sth_tip from inserted i)
and sth_miktar =(select sth_miktar from inserted)
and sth_stok_kod =(select sth_stok_kod from inserted)
and sth_tarih = (select sth_tarih from inserted)
and sth_RECno < (select sth_RECno from inserted)
if (#counter>=1)
begin
rollback
RAISERROR ('Record already exists', 17, -1) with log
end
END

Trying to select database based on server

Can anyone tell me why this doesn't work?
if ((select ##servername) = 'ServerA')
begin
use DatabaseA
select top 5 * from dbo.SignUPRequest
end
else if ((select ##servername) = 'ServerB')
begin
use DatabaseB
select top 5 * from dbo.SignUPRequest
end
When I run this on ServerA, I get a message that DatabaseB doesn't exist on ServerA, which it doesnt, but I don't understand why it's trying to read if the second if evaluates to false.
Msg 911, Level 16, State 1, Line 8
Database 'DatabaseB' does not exist. Make sure that the name is entered correctly.
The code is parsed before it is run. When it is parsed SQL Server checks that it can access everything in the code, it cannot access the database that exists on the other server so the parsing step of running the code fails. As a result, you get the error message you've shown.
If you want to get around that you can put the code in the IF blocks as dynamically executed code (I always feel this is a bit of a hack/workaround).
DECLARE #sql NVARCHAR(4000);
if ((select ##servername) = 'ServerA')
begin
SET #sql = 'use DatabaseA; select top 5 * from dbo.SignUPRequest;'
end
else if ((select ##servername) = 'ServerB')
begin
SET #sql = 'use DatabaseB; select top 5 * from dbo.SignUPRequest'
end
EXEC (#sql)
So, what happens here is that you defer the parsing & running of the code that uses the appropriate database for the server to run time, as that what the EXEC statement at the end does.
UPDATE
Based on the additional comment below you could also rewrite this as:
DECLARE #sql NVARCHAR(4000);
if ((select ##servername) = 'ServerA')
begin
select top 5 * from DatabaseA.dbo.SignUPRequest;
end
else if ((select ##servername) = 'ServerB')
begin
select top 5 * from DatabaseB.dbo.SignUPRequest;
end
So, instead of putting in a USE <database-name> at the start, you can also more fully qualify the name of the table in your select statement. If you only have one line of SQL to deal with this could be more effective.
You get the error when the query is compiled, not on execution. You can execute the statements with exec to get them in a batch that compiles only if the database exists.
if ((select ##servername) = 'ServerA')
begin
exec(N'use DatabaseA
select top 5 * from dbo.SignUPRequest')
end
else if ((select ##servername) = 'ServerB')
begin
exec(N'use DatabaseB
select top 5 * from dbo.SignUPRequest')
end

Cross-database query with database name in variable and cursor

I have a problem with name of database in a cursor.
Here the current code
DECLARE #IDES INT
DECLARE #IDPROD INT
DECLARE #count INT
SET #count = 0
DECLARE CUR_CONSO CURSOR LOCAL fast_forward FOR
SELECT E2.id_es ,P3.id_prod FROM
[gpto_v004p001].[dbo].[GPTO_PRODETAB] P1
INNER JOIN
[fer_v008].[dbo].[T_PRODUIT] P3
ON P3.GPTO_PRODUIT_ID = P1.GPTO_PRODUIT_ID
INNER JOIN [gpto_v004p001].[dbo].[GPTO_ETAB] E1
ON E1.ETABID = P1.ETABID
INNER JOIN
[fer_v008].[dbo].[t_etablissement] E2
ON E1.ETABUC = LEFT(E2.code_es,5)
LEFT JOIN
[fer_v008].[dbo].[t_produit_etablissement] PE1
ON PE1.id_prod = P3.id_prod AND PE1.id_es = E2.id_es
WHERE PE1.id_es IS NULL AND GPTO_PRODUIT_ETAPE = 4
OPEN CUR_CONSO
FETCH CUR_CONSO INTO #IDES , #IDPROD
WHILE ##FETCH_STATUS = 0
BEGIN
IF NOT EXISTS (Select * from [fer_v008].[dbo].[t_produit_etablissement] where id_es=#IDES and id_prod=#IDPROD) -- Pas d'enregistrements
BEGIN
INSERT INTO [fer_v008].[dbo].[t_produit_etablissement]
([id_es],[id_prod],[gest_prod])
VALUES
(#IDES,#IDPROD,0)
SET #count = #count + 1
END
FETCH CUR_CONSO INTO #IDES , #IDPROD
END
CLOSE CUR_CONSO
DEALLOCATE CUR_CONSO
As the database is versionned, I need to use database name as variable saved in parameter table.
For easy script, I use Execute command as this
DECLARE #base_travail varchar(128)
SELECT #base_travail = val_str_par FROM t_parametre WHERE nom_par = 'base_travail'
DECLARE #execcmd varchar(max)
SET #execcmd = 'insert into #tmpfiltres SELECT TOP 1 filtre_exu FROM '
+ #base_travail + '.dbo.t_export_util WHERE id_exu =' + convert(varchar,#id_exu)
Execute (#execcmd)
But how do this when I have a cursor ? The 1st sample code is just a sample, whole script go over 400 lines, so I can't switch all the script in string mode.
Thanks for your help.
I had the same issue. Had to do with Database Compatibility Level.
This Code:
DECLARE #VARSql varchar(2000), #ID int;
SET #VARSql = 'USE [SomeOtherDatabase]; DECLARE cur CURSOR GLOBAL for
SELECT Max(SomeTableID) FROM [dbo].[SomeTable];';
Exec(#VARSql); open cur; fetch next from cur into #ID; close cur; deallocate cur;
PRINT #ID
Generated this error:
Msg 16958, Level 16, State 3, Line 3
Could not complete cursor operation because the set options have changed since the cursor was declared.
Running on SQL 2008 server.
Calling database has Compatibility Level 90 (Sql Svr 2005).
SomeOtherDatabase in code above has Compatibility Level 100 (Sql Svr 2008).
If you alter compatibility level so calling and called databases are the same, problem resolved.
select * from sys.databases
ALTER DATABASE [CallingDatabase] SET COMPATIBILITY_LEVEL = 100;
I had a same problem. Here is my solution;
DECLARE CUR_CORSO CURSOR **FAST_FORWARD FORWARD_ONLY** FOR
...

Resources