SQL Server row level triggers - sql-server

I tried to achieve row level delete trigger by using cursor but when in trying yo delete the any row from table it tooks so long time.
I could not understand where exactly it stuck.
/****** Object: Trigger [delStudent] Script Date: 06/24/2010 12:33:33 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [delStudent]
ON [dbo].[Student]
FOR DELETE
AS
DECLARE #Roll as varChar(50);
DECLARE #Name as varChar(50);
DECLARE #Age as int;
DECLARE #UserName as varChar(50);
SELECT #UserName=SYSTEM_USER;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION;
declare CurD cursor for select roll, Sname, age from deleted
open CurD
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO [dbo].[Audit]
(roll,sname,age,userId)
VALUES
(#Roll,#Name,#Age,#UserName)
END
COMMIT TRANSACTION;
Close CurD
DEALLOCATE CurD

I think you should transform your cursor in an insert-select sentence. I'm not sure this will solve your problem, but it's a good best practice anyway.
INSERT [dbo].[Audit] (roll,sname,age,userId)
SELECT 'FIELDS FROM DELETED', SYSTEM_USER
FROM deleted
Try to avoid cursors, and this will result in better performance.

Related

SQL Server / T-SQL : Raiserror cancels prior inserts

In a procedure, I want to make a test then Raiserror when it's actually the case. But before that, I want to log the error in a table. My code is like this
CREATE PROCEDURE proc
#val VARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT OFF;
DECLARE #test VARCHAR(50)
SELECT #test = test
FROM test_table
WHERE ...
IF #test IS NULL
BEGIN
INSERT INTO log_table VALUES (#val);
RAISERROR ('Invalid value : %i', 16, 1, #val);
END
END
The code compiles. When executed with a bad value, the error is raised, but the insert is cancelled.
I tried turning xact_abort and nocount on and off but had no luck.
I tried encapsulating the insert request in BEGIN TRANSACTION/COMMIT but still get the same result.
What I noticed, my log_table which has an auto-increment id, gets incremented even when those inserts are being cancelled.
How can I raise and error but still persist the insert request?
Thanks
Consider using THROW instead:
CREATE TABLE dbo.log_table (val varchar(50));
GO
CREATE PROCEDURE dbo.[proc] #val varchar(50)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT OFF;
DECLARE #test varchar(50); --As i never set this, it'll go into the IF
IF (#test IS NULL)
BEGIN
INSERT INTO log_table
VALUES (#val);
THROW 51000, N'Invalid value.', 1;
END;
END;
GO
EXEC dbo.[proc] #val = 'Some Value';
GO
SELECT *
FROM dbo.log_table;
GO
DROP PROC dbo.[proc];
DROP TABLE dbo.log_table;
DB<>Fiddle
In order to write to a log table you have to rollback any pending transaction. Otherwise your log table INSERT may be rolled back by the calling code, or may fail because the transaction is doomed.
So something like:
CREATE Procedure myproc
#val varchar(50)
as
begin
set nocount on
set xact_abort on
begin transaction;
begin try
-- do stuff
commit transaction;
end try
begin catch
if ##trancount > 0 rollback;
declare #error_message varchar(max) = error_message()
INSERT INTO log_table values (#val);
throw;
end catch
end
So apparently, my procedure was working as expected in SQLServer side. The problem was that I was calling this procedure from Java/Spring native query method and had to be annotated with #Modifying and #Transactional since it's doing insertions. Thus when an exception is caught, it was automatically rolled back.
I didn't find a quick solution to bypass Spring's transaction. Now I think all I have to do is, catch the exception in App layer and log to the log_table in app layer too

SQL Service Broker

I have created a service broker that stores id's in the queue table. But the problem is when i want to get the id back in the stored procedure it's now formatted as xml. Because the service broker message is XML.
How can i just get the last Id each time because the trigger is fired after an update of a row.
Below my code =>
/****** Object: Trigger [dba].[TriggerCall] Script Date: 6/16/2015 2:55:57 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dba].[TriggerCall] ON [dba].[CallID] FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MessageBody XML
DECLARE #ID varchar(50)
-- Insert statements for trigger here
--get relevant information from inserted/deleted and convert to xml message
SET #MessageBody = (SELECT Id FROM inserted
FOR XML AUTO)
If (#MessageBody IS NOT NULL)
BEGIN
DECLARE #Handle UNIQUEIDENTIFIER;
BEGIN DIALOG CONVERSATION #Handle
FROM SERVICE [TestServiceInitiator]
TO SERVICE 'TestServiceTarget'
ON CONTRACT [TestContract]
WITH ENCRYPTION = OFF;
SEND ON CONVERSATION #Handle
MESSAGE TYPE [TestMessage](#MessageBody);
END
END
/****** Object: StoredProcedure [dbo].[usp_GetCall] Script Date: 6/16/2015 2:44:27 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[usp_GetCall]
AS
BEGIN
DECLARE #message_type varchar(100)
DECLARE #dialog uniqueidentifier, #message_body XML;
Declare #Object as Int;
Declare #URL as varchar(255)
Declare #ResponseText as Varchar(8000);
Declare #ID as Varchar(38);
WHILE (1 = 1)
BEGIN -- Receive the next available message from the queue
WAITFOR (
RECEIVE TOP(1) #message_type = message_type_name,
#message_body = CAST(message_body AS XML),
#dialog = conversation_handle
FROM dbo.TestQueue ), TIMEOUT 500 if (##ROWCOUNT = 0 OR #message_body IS NULL)
BEGIN
BREAK
END
ELSE
BEGIN
INSERT INTO [dbo].[testtabel]
([id]
,[callid],
[test])
VALUES
('111', '111', #message_body)
END
END CONVERSATION #dialog
END
END
If I understand your question correctly, you're just looking to rehydrate the ID into an int. If that's the case, the following should do:
use tempdb;
create table inserted (id int);
insert into inserted values (1);
declare #message_body xml;
set #message_body= (select * from inserted for xml auto);
select #message_body.value('(/inserted/#id)[1]', 'int');
The magic is the last line (the rest was just setup for me to test).
I'll take this opportunity to clear up a misconception that you seem to have, though. Triggers in SQL server aren't fired per row, but per batch. So, if you run an update against your table and it updates 50 rows, the trigger gets fired once and the inserted (and deleted) table will have 50 rows in it. Just something to take into account.

Sp to sync two tables using linked servers sql server

I'm working in a SP and I got two tables in different servers, one is the main and the other is a copy but whit less columns, all I want is to run the SP every 5 min over the main table in order to validate if new records has been created, if yes take those new records and insert them into the copy table, that resides in the other server.
Searching a solution I've found this script wich works ok in the same server, but when I try to run it with the linked server it fails....please somebody help me with this.
This is the error that a get....
OLE DB provider "SQLNCLI" for linked server "ESDBCGW001T" returned
message "The partner transaction manager has disabled its support for
remote/network transactions.". Msg 7391, Level 16, State 2, Procedure
sp_newrecords, Line 37 The operation could not be performed because
OLE DB provider "SQLNCLI" for linked server "LINKEDSERVER" was unable
to begin a distributed transaction.
All configurations are perfect, and when I run the insert without the variables runs fine,I mean, instead of #val1, #val2 and #val3 I use real values 1, 'A', 'B'.
USE [XXX]
GO
/****** Object: StoredProcedure [dbo].[sp_newrecords] Script Date: 03/02/2015 11:11:57 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: <>
-- Create date: <>
-- Description: <Description,,>
-- =============================================
ALTER PROCEDURE [dbo].[sp_newrecords]
-- Add the parameters for the stored procedure here
AS
BEGIN
DECLARE #val1 int
DECLARE #val2 nvarchar(50)
DECLARE #val3 nvarchar(50)
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE mycur1 CURSOR for
select empid,fname,lname from employee
where empid = 508 --this is for test only
OPEN mycur1
FETCH NEXT FROM mycur1 INTO #val1, #val2 , #val3
WHILE ##Fetch_Status = 0
BEGIN
begin tran /* default read committed isolation level is fine */
if not exists (select * from openquery (linkedserver,'select * from DBNAME.dbo.Employee_backup'))
--insert employee_backup values (#val1, #val2 , #val3)
begin
INSERT into [linkedserver].[DBNAME].[dbo].[Employee_backup] values (#val1, #val2 , #val3)
end
/*if not exists (select * from employee_backup with (updlock, rowlock, holdlock)
where employee_backup.empid = #val1
and employee_backup.fname = #val2 )
--insert employee_backup values (#val1, #val2 , #val3)
INSERT [LINKEDSERVER].[DBNAME].[dbo].[Employee_backup] values (#val1, #val2 , #val3)*/
--else
-- /* update */
commit /* locks are released here */
FETCH NEXT FROM mycur1 INTO #val1, #val2 , #val3`enter code here`
END
CLOSE mycur1
DEALLOCATE mycur1
END
Your database admin would need to allow that permission on the target server. Personally, I would simply replicate the table from one server to the other and then the stored procedure would run against the replicated data.
If you absolutely need to go cross server, please refer to this SO question.
why not just use replication for something like this?
Might need to enable Distributed Transaction Coordinator (DTC) for your specific problem https://technet.microsoft.com/en-us/library/cc759136%28v=ws.10%29.aspx
In addition to ensuring DTC is enabled, you should be using begin distributed tran rather than begin tran
See:
https://msdn.microsoft.com/en-us/library/ms188386.aspx

Must Declare Scalar variable on creating stored procedure

I am creating first time stored procedure but I don't know why this showing error. I also have many other post but didn't understand if someone can tell what I am doing wrong here.
Stored procedure:
SET ANSI_NULLS ON
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[DeleteOrganization]
#ORG_ID bigint
AS
BEGIN
SET NOCOUNT ON;
delete from Organizations where ORG_ID=#ORGID
delete from Institutes where INS_FK_ORGID=#ORGID
delete from Branches where BRN_ID=#ORGID
END
The variable is declared as #ORG_ID bigint with an underscore but you refer to it without the underscore: #ORGID. Pick one and use it consistently.
This should work:
ALTER PROCEDURE [dbo].[DeleteOrganization]
#ORGID bigint
AS
BEGIN
SET NOCOUNT ON;
DELETE FROM Organizations WHERE ORG_ID = #ORGID
DELETE FROM Institutes WHERE INS_FK_ORGID = #ORGID
DELETE FROM Branches WHERE BRN_ID = #ORGID
END

Cursor inside Trigger not working

USE [ddb]
GO
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[requeststrigger]
ON [dbo].[requests]
AFTER INSERT,UPDATE
AS
BEGIN
DECLARE #email VARCHAR(400);
DECLARE #firstname VARCHAR(400);
DECLARE #requestno VARCHAR(400);
DECLARE #lastname VARCHAR(400);
DECLARE #statusid INT;
DECLARE thecursor CURSOR FOR SELECT inserted.requestno,contacts.firstname,contacts.lastname,contacts.email FROM request_contacts,contacts,inserted WHERE request_contacts.requestid=inserted.requestid AND contacts.contactid=request_contacts.contactid AND request_contacts.notification=1 AND contacts.notification=1;
SET #statusid = (SELECT statusid FROM inserted);
IF #statusid = 4 AND #statusid <> (SELECT statusid FROM deleted)
BEGIN
SET NOCOUNT ON
SET ARITHABORT ON
OPEN thecursor;
FETCH NEXT FROM thecursor
INTO #requestno,#firstname,#lastname,#email
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC MAIL_SEND #email,#firstname,#requestno,#lastname;
FETCH NEXT FROM thecursor
INTO #requestno,#firstname,#lastname,#email
END
CLOSE thecursor;
DEALLOCATE thecursor
SET NOCOUNT OFF
END
END
This simply makes the whole UPDATE/INSERT not work. When I remove the cursor declaration, it works. The cursor is just selecting a field from a table that is existing in the same database called "contacts". What is wrong?
Are you prepared to consider amending your design? There appear to be a couple of issues with what you're attempting here.
A trigger isn't necessarily the best place to be doing this kind of row-by-row operation since it executes in-line with the changes to the source table, and will affect the performance of the system negatively.
Also, your existing code evaluates statusid only for a single row in the batch, although logically it could be set to more than one value in a single batch of updates.
A more robust approach might be to insert the rows which should generate a MAIL_SEND operation to a queuing table, from which a scheduled job can pick rows up and execute MAIL_SEND, setting a flag so that each operation is carried out only once.
This would simplify your trigger to an insert - no cursor would be required there (although you will still need a loop of some kind in the sechduled job).

Resources