I've been googled about my problem, but I didn't find anything relevant.
I'm executing a Bulk Insert with flag FIRE_TRIGGERS enabled.
So, I need pass each row in my Bulk Insert to a procedure.
When I execute a insert row by row, my triggers works fine but when I execute a Bulk Insert my triggers don't work.
My trigger code:
ALTER TRIGGER [dbo].[tgSetDetails]
ON [PORTALSQLDB].[dbo].[BurnTimeRawData]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #ServiceTag varchar(10);
DECLARE #Platform varchar(50);
SELECT
#ServiceTag = ServiceTag,
#Platform = Platform
FROM
inserted
EXEC spBurnTimeInsertData #ServiceTag, #Platform
END
Any idea how I can do this job?
Thanks :)
This trigger will fire once for each insert statement or bulk insert batch. To call a proc for each row, you'll need a cursor.
It would be better to refactor the proc code as a set-based operation to include in the trigger code. That will perform much better.
Related
Why not exists recompile option for trigger?
Suddenly the performance of one of our procedure (multiple SELECTs, multiple tables, insert into table) went from returning data in around 1 secs to 10-30secs.
After adding various debugging and logging we noticed that the performance would increase from the slow 10-30secs, back to sub-second speeds. (because alter trigger one of the table)
Just to clarify. The sequence of events:
Slow performance of Insert
Alter trigger table
Fast performance of Insert
I think slow performance associated with create wrong plan cash. because, before call insert command on the procedure, I write print datetime and the beginning of the the trigger command, add print datetime, so when call the procedure before alter trigger, The time difference between the first print and the second print is 20 sec, but when alter trigger, back to sub-second speeds. It should be noted that the commands in the trigger are not complicated
so, I need to add recompile option to trigger like procedure
it is trigger Script sample:
create trigger t_test on tbl AFTER insert
as
begin
begin try
declare #yearid int,
#id int
select #id = id,#yearid = yearid
from inserted
if exists(select * from FinancialYear where id = #yearid and flag = 0)
begin
raiserror('year not correct',16,1)
end
DECLARE #PublicNo BIGINT=(SELECT ISNULL(MAX(PublicNo),0)+1 FROM tbl)
update tbl
set PublicNo = #PublicNo
where #id
insert into tbl2
values (...)
end try
begin catch
print error_message()
end catch
end
I need to execute a query before every insert into the table. I try to use instead of
--insert into tbl_Exlog(ActionName) values('Insert')
--select * from tbl_Exlog
ALTER Trigger [dbo].[trgExLogTest] on [dbo].[tbl_ExLog]
Instead of Insert
as
begin
insert into tbl_ExLog (ActionName) values('trigger')
end
but it restricts the actual insert which I don't want.
Output :
ActionName
trigger
Insert is missing
The INSERT is missing, yes, in your TRIGGER. You never told SQL Server to INSERT the data, so it doesn't INSERT it. An INSTEAD OF INSERT "does exactly what it says on the tin"; 'do this instead of the INSERT'. You tell SQL Server to INSERT a row into tbl_ExLog instead but don't tell it to INSERT into the table you were actually inserting again.
Define the INSERT in your trigger:
ALTER TRIGGER [dbo].[trgExLogTest] ON [dbo].[tbl_ExLog]
INSTEAD OF INSERT AS
BEGIN
INSERT INTO dbo.tbl_ExLog (ActionName) VALUES ('trigger');
INSERT INTO dbo.trgExLogTest ({Column List})
SELECT {Column List}
FROM inserted;
END;
I've got a stored procedure that is coded similarly to the following:
USE [database]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROC [dbo].[procedure_name]
#record_id int
, #record_value VARCHAR(MAX)
AS
BEGIN
UPDATE dbo.table_1
SET table_1_record_value = #record_value
WHERE table_1_record_int = #record_int
END
BEGIN
INSERT INTO table_2 COLUMNS (table_2_record_id, table_2_record_value) VALUES (#record_id, #record_value)
END
And I'm getting a syntax error. I've never had to write a stored procedure for an application that would accomplish both an UPDATE and an INSERT statement together.
The answer was provided by bbaird in the comments. Removing the BEGIN/END keywords fixed the problem. Thank you!
bbaird's full comment below:
If the procedure isn't created yet, you will need to do CREATE PROCEDURE. 2. The update and insert statements are independent, no need to put them in their own BEGIN...END block unless there is a conditional. 3. COLUMNS in the insert statement might also be throwing things off - it is not necessary so remove it.
The answers of Jake and bbard are correct.
Below the code of your stored procedure:
USE [database]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE OR ALTER PROC [dbo].[procedure_name]
#record_id int
, #record_value VARCHAR(MAX)
AS
BEGIN
UPDATE dbo.table_1
SET table_1_record_value = #record_value
WHERE table_1_record_int = #record_int
INSERT INTO table_2 COLUMNS (table_2_record_id, table_2_record_value)
VALUES (#record_id, #record_value)
END
For documentation of BEGIN and END look here
Is there a way of getting the SQL code that fired a trigger from inside the fired trigger, without using DBCC INPUTBUFFER or sys.dm_exec_input_buffer?
I need this for a trigger that logs the new value, the old value and the statement that made the change in that table.
Even though DBCC INPUTBUFFER resolves the challenge, I cannot use it because I need to use "INSERT INTO ... EXEC" in order to get the query that fired the trigger and the trigger is fired by many statements that already use "INSERT INTO ... EXEC", so I will get the error
An INSERT EXEC statement cannot be nested
From my research, sys.dm_exec_input_buffer might do the trick, but I cannot use it since it is available only for SQL Server 2014 SP4 and newer (as mentioned here: Get last command in SQL Server without DBCC INPUTBUFFER), and I am using an older version.
I have tried several ways of solving the problem but without success. I cannot get the SQL statement that fired the trigger but only the last executing statement which is the trigger.
To see the problem, take a look at the following code:
--Create the table that will have the trigger
CREATE TABLE [dbo].[___testTrigger]
(
[text] [NVARCHAR!(50) NOT NULL
) ON [PRIMARY]
GO
CREATE TRIGGER dbo.TestTriggerAuditLog
ON dbo.___testTrigger
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON;
--Version 1: without "INSERT INTO ... EXEC" but does not get the text of the statement that fired the trigger. Instead, it gets the current running query, which is the trigger
SELECT sqltext.TEXT,
req.session_id,
req.status,
req.command,
req.cpu_time,
req.total_elapsed_time
FROM sys.dm_exec_requests req
CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS sqltext
WHERE req.session_id = ##SPID
--Version 2: gets the statement that fired the trigger, but we need to use "INSERT INTO ... EXEC"
DECLARE #inputbuffer TABLE (EventType NVARCHAR(30),Parameters INT,EventInfo NVARCHAR(4000))
INSERT INTO #inputbuffer EXEC('dbcc inputbuffer('+##Spid+') WITH NO_INFOMSGS')
SELECT * FROM #inputbuffer AS I
END
I know that in a trigger is not ok to have SELECT statements! I did it just to make the example simpler.
Now, we can insert some data to see what we get:
--test
INSERT INTO dbo.___testTrigger (text)
VALUES (N'This is a test test')
We will get the 2 selects returning different results, as can be seen in the bellow image.
Any ideas of what could I use to get the same result as DBCC INPUTBUFFER but without using "INSERT INTO ... EXEC" and without using sys.dm_exec_input_buffer as it is not available in my SQL Server version?
create table dbo.abcd(id int);
go
create trigger dbo.triggerabc on dbo.abcd for insert, update, delete
as
begin
declare #t table(query nvarchar(4000));
insert into #t (query)
select EventInfo
from OPENROWSET('SQLNCLI', 'Server=localhost;Trusted_Connection=yes;',
'
declare #spid nvarchar(10), #sql nvarchar(1000);
select #spid = cast(session_id as nvarchar(10))
from sys.dm_exec_requests
where session_id > 50
and wait_type = ''OLEDB''
and wait_resource like ''SQLNCLI%(SPID='' + cast(##spid as varchar(10)) + '')'';
select #sql = ''dbcc inputbuffer('' + #spid + '') WITH NO_INFOMSGS'';
exec(#sql) with result sets( (EventType NVARCHAR(30),Parameters SMALLINT,EventInfo NVARCHAR(4000)) );
'
) ;
select * from #t;
end
go
insert into abcd(id) values(123)
go
insert into abcd(id)
exec('select 456')
go
drop table abcd
go
Here's a very simple solution.
But first, since triggers don't fire on select it probably isn't very accurate to refer to "queries" firing the trigger. It would probably be more accurate to call them "statements."
Anyway, add a column to your table such as StatementName varchar(10) and then in each insert statement that will fire the trigger, add a value such as 'Statement1', 'Statement2', etc.
Then the trigger can just check the inserted row and know what statement fired the trigger.
I'm executing stored procedures using SET FMTONLY ON, in order to emulate what our code generator does. However, it seems that the results are cached when executed like this, as I'm still getting a Conversion failed error from a proc that I have just dropped! This happens even when I execute the proc without SET FMTONLY ON.
Can anyone please tell me what's going on here?
Some statements will still be executed, even with SET FMTONLY ON. You "Conversion failed" error could be from something as simple as a set variable statement in the stored proc. For example, this returns the metadata for the first query, but throws an exception when it runs the last statement:
SET FMTONLY on
select 1 as a
declare #a int
set #a = 'a'
As for running a dropped procedure, that's a new one to me. SQL Server uses the system tables to determine the object to execute, so it doesn't matter if the execution plan is cached for that object. If you drop it, it is deleted from the system tables, and should never be executable. Could you please query sysobjects (or sys.objects) just before you execute the procedure? I expect you'll find that you haven't dropped it.
This sounds like a client-side error. Do you get the same message when running through SQL Management Studio?
Have you confirmed that there isn't another procedure with the same name that's owned by a different schema/user?
DDL statements are parsed, but ignored when run if SET FMTONLY ON has been executed on the connection. So if you drop a proc, table, etc when FMTONLY is ON, the statement is parsed, but the action is not executed.
Try this to verify
SET FMTONLY OFF
--Create table to test on
CREATE TABLE TestTable (Column1 INT, Column2 INT)
--insert 1 record
INSERT INTO TestTable (Column1, Column2)
VALUES (1,2)
--validate the record was inserted
SELECT * FROM TestTable
--now set format only to ON
SET FMTONLY ON
--columns are returned, but no data
SELECT * FROM TestTable
--perform DDL statement with FMTONLY ON
DROP TABLE TestTable
--Turn FMTONLY OFF again
SET FMTONLY OFF
--The table was dropped above, so this should not work
SELECT * FROM TestTable
DROP TABLE TestTable
SELECT * FROM TestTable