SQL Server Trigger containing one or two cursors - sql

I am trying to create a cursor, or cursors, inside of a trigger. What I need to do is when one field in a table is updated, I have to have one cursor or two cursors iterate through and record all of the fields in a table and insert the old and new values into a table.
Here is the code I currently have. This code iterates through the new values in the inserted table correctly, but does not iterate through the old values, in the deleted table.
ALTER TRIGGER [dbo].[Audit_Emp_Trigger]
ON [dbo].[EMPLOYEE]
AFTER UPDATE, DELETE
AS
BEGIN
--Set the fields that we will need in this trigger
DECLARE #OldLName NVARCHAR(50);
DECLARE #NewLName NVARCHAR(50);
DECLARE #OldSSN INT;
DECLARE #NewSSN INT;
DECLARE #OldDno INT;
DECLARE #NewDno INT;
DECLARE #Fname NVARCHAR(50);
DECLARE #Mname NVARCHAR(50);
DECLARE #BDate DATE;
DECLARE #Address NVARCHAR(100);
DECLARE #Sex CHAR(1);
DECLARE #Salary INT;
DECLARE #SuperSSN INT;
--Only execute the trigger if the Dno field was updated or deleted
IF UPDATE(Dno)
BEGIN
--If this is an insert operation, we will be inserting a new Dno value
SELECT #OldLName = D.LName FROM deleted D
SELECT #OldSSN = D.Ssn FROM deleted D
SELECT #OldDno = D.Dno FROM deleted D
DECLARE InsertCursor CURSOR FOR SELECT Fname, Minit, Lname, Ssn, Bdate, Address, Sex, Salary, Super_ssn, Dno FROM inserted
OPEN InsertCursor
FETCH NEXT FROM InsertCursor INTO #Fname, #Mname, #NewLName, #NewSSN, #BDate, #Address, #Sex, #Salary, #SuperSSN, #NewDno
WHILE ##FETCH_STATUS = 0
BEGIN
--If the Audit_Emp_Record table does not exist already, we need to create it
IF OBJECT_ID('dbo.Audit_Emp_Record') IS NULL
BEGIN
--Table does not exist in database, so create table
CREATE TABLE Audit_Emp_Record
(
date_of_change smalldatetime,
old_Lname varchar (50),
new_Lname varchar (50),
old_ssn int,
new_ssn int,
old_dno int,
new_dno int
);
--Once table is created, insert the values of the update operation into the table
INSERT INTO Audit_Emp_Record(date_of_change, old_Lname, new_Lname, old_ssn, new_ssn, old_dno, new_dno) VALUES(GETDATE(), #OldLName, #NewLName, #OldSSN, #NewSSN, #OldDno, #NewDno)
END
ELSE
BEGIN
--The table already exists, so simply insert the new values of the update operation into the table
INSERT INTO Audit_Emp_Record(date_of_change, old_Lname, new_Lname, old_ssn, new_ssn, old_dno, new_dno) VALUES(GETDATE(), #OldLName, #NewLName, #OldSSN, #NewSSN, #OldDno, #NewDno)
END
FETCH NEXT FROM InsertCursor INTO #Fname, #Mname, #NewLName, #NewSSN, #BDate, #Address, #Sex, #Salary, #SuperSSN, #NewDno
END
END
END

Related

SQL Cursor to Insert into Temporary Table and Fetch From Temparory Table

Sir,
I have build a SQL query using Cursor as #AllRecords to insert values into Temporary Table & then Fetch values from that temporary table. But it showing me an error at last statement when I am fetching values from table (Error: incorrect syntax near #AllRecords). Below is my code:
DECLARE #ColName varchar(20)=null,
#Query varchar(MAX)=null,
#DepartmentName varchar(50)=null,
#deptt_code varchar(4)=null,
#DistrictId varchar(4)='0001',
#Deptt_Id char(4)=null,
#stYear varchar(4)=null,
#cYear varchar(4)=null,
#yr varchar(9)='2017-2018',
#tno int
BEGIN
set #stYear = SUBSTRING(#yr,0,5)
set #cYear = SUBSTRING(#yr,6,4)
--DECLARE & SET COUNTER
DECLARE #counter int
SET #counter = 1
--CREATE DYNAMIC TABLE WITH COLs
DECLARE #AllRecords table
(
department_name varchar(50),
project_name varchar(100),
department_code varchar(4)
)
--*** Declare Cursor
DECLARE cur_FetchDepartmentName CURSOR READ_ONLY
FOR
select deptt_code,deptt_name+'('+ RTRIM(LTRIM(deptt_short))+')' as dept_name from m_Department
where deptt_code in (select distinct department_code from t_Project_Details where district_id=#DistrictId
and financial_year=#yr)
OPEN cur_FetchDepartmetName
fetch next from cur_FetchDepartmetName into
#deptt_code, #DepartmentName
--LOOP UNTIL RECORDS ARE AVAILABLE
while ##FETCH_STATUS=0
BEGIN
if(#tno=0)
BEGIN
set #tno=1
insert into #AllRecords values(#DepartmentName,#deptt_code)
fetch next from cur_FetchDepartmetName into
#deptt_code,#DepartmentName
END
else
BEGIN
set #tno=#tno+1
insert into #AllRecords values(#DepartmentName,#deptt_code)
fetch next from cur_FetchDepartmetName into
#deptt_code,#DepartmentName
END
END
--CLOSE CURSOR
CLOSE cur_FetchDepartmetName
DEALLOCATE cur_FetchDepartmetName
select department_name, department_code from #AllRecords
Instead of answering what is error in this solution, I would like to offer a better solution to the problem. Use of cursor in this example is completely unnecessary, query can be more easily be written without it. It's a simple INSERT..SELECT statement and counting of records to set #tno can easily be done in the end.
BEGIN
set #stYear = SUBSTRING(#yr,0,5);
set #cYear = SUBSTRING(#yr,6,4);
--CREATE DYNAMIC TABLE WITH COLs
DECLARE #AllRecords table
(
department_name varchar(50),
project_name varchar(100), --what's the use of this column?
department_code varchar(4)
);
INSERT INTO #AllRecords (department_code, department_name)
select deptt_code,deptt_name+'('+ RTRIM(LTRIM(deptt_short))+')' as dept_name from m_Department
where deptt_code in (select distinct department_code from t_Project_Details where district_id=#DistrictId
and financial_year=#yr);
SELECT #tNo = COALESCE(#tno,0) + COUNT(*) FROM #AllRecords;
select department_name, department_code from #AllRecords;
END
Please check this article about cursors and how to avoid them:
Cursors and How to Avoid Them
Your SQL Query as below :
BEGIN
DECLARE #ColName VARCHAR(20)= NULL, #Query VARCHAR(MAX)= NULL, #DepartmentName VARCHAR(50)= NULL, #deptt_code VARCHAR(4)= NULL, #DistrictId VARCHAR(4)= '0001', #Deptt_Id CHAR(4)= NULL, #stYear VARCHAR(4)= NULL, #cYear VARCHAR(4)= NULL, #yr VARCHAR(9)= '2017-2018', #tno INT;
SET #stYear = SUBSTRING(#yr, 0, 5);
SET #cYear = SUBSTRING(#yr, 6, 4);
--DECLARE & SET COUNTER
DECLARE #counter INT;
SET #counter = 1;
--CREATE DYNAMIC TABLE WITH COLs
DECLARE #AllRecords TABLE
(department_name VARCHAR(50),
project_name VARCHAR(100),
department_code VARCHAR(4)
);
--*** Declare Cursor
DECLARE cur_FetchDepartmentName CURSOR READ_ONLY
FOR
SELECT deptt_code,
deptt_name+'('+RTRIM(LTRIM(deptt_short))+')' AS dept_name
FROM m_Department
WHERE deptt_code IN
(
SELECT DISTINCT
department_code
FROM t_Project_Details
WHERE district_id = #DistrictId
AND financial_year = #yr
);
OPEN cur_FetchDepartmetName;
FETCH NEXT FROM cur_FetchDepartmetName INTO #deptt_code, #DepartmentName;
--LOOP UNTIL RECORDS ARE AVAILABLE
WHILE ##FETCH_STATUS = 0
BEGIN
IF(#tno = 0)
BEGIN
SET #tno = 1;
INSERT INTO #AllRecords
(department_name,
department_code
)
SELECT #DepartmentName,
#deptt_code;
FETCH NEXT FROM cur_FetchDepartmetName INTO #deptt_code, #DepartmentName;
END;
ELSE
BEGIN
SET #tno = #tno + 1;
INSERT INTO #AllRecords
(department_name,
department_code
)
SELECT #DepartmentName,
#deptt_code;
FETCH NEXT FROM cur_FetchDepartmetName INTO #deptt_code, #DepartmentName;
END;
END;
--CLOSE CURSOR
CLOSE cur_FetchDepartmetName;
DEALLOCATE cur_FetchDepartmetName;
select department_name, department_code from #AllRecords
END;

Separate rows in multiple delete SQL server

I have a trigger. When i delete something in my table i want to create a row in other table named Archive with the row i deleted. All works fine. The problem is when i delete more than one row in the same time. It store just the first row in the table Archive. I think about creating a loop but how i separate them in subqueries to select every row. How do i solve that?
ALTER TRIGGER [dbo].[trigArhiva]
ON [dbo].[student]
AFTER DELETE
AS
SET NOCOUNT ON;
BEGIN
declare #nume varchar(45);
declare #anStudiu int;
declare #prenume varchar(45);
declare #CNP char(13);
declare #grupa int;
declare #idFacult int;
declare #rowss int;
declare #i int;
select #rowss = count(*) from DELETED;
set #i = 1;
while (#i <= #rowss)
begin
select #nume = nume from DELETED;
select #anStudiu = anStudiu from DELETED;
select #prenume = prenume from DELETED;
select #CNP = CNP from DELETED;
select #grupa = idGrupa from DELETED;
select #idFacult = idFacult from DELETED;
insert into arhivaStud(nume, prenume, CNP, grupa, idFacult, anStudiu) values (#nume, #prenume, #CNP, #grupa, #idFacult, #anStudiu);
set #i = #i+1;
end
END
I think you should use something like this (deleted is an "internal" representation of all records deleted during the operation which recall the trigger):
ALTER TRIGGER [dbo].[trigArhiva]
ON [dbo].[student]
AFTER DELETE
AS
SET NOCOUNT ON;
BEGIN
INSERT INTO arhivaStud(nume, prenume, CNP, grupa, idFacult, anStudiu)
SELECT nume, prenume, CNP, idgrupa, idFacult, anStudiu FROM deleted
END

getting the last inserted id from previous insert statement

I am having a stored procedure with two insert statement, where I want to insert the ID of the first insert statement into the second one.
CREATE PROC [dbo].[Log_Action]
#action_description VARCHAR(MAX),
#creator_id INT,
#entity VARCHAR(50),
#entity_identifier UNIQUEIDENTIFIER
AS
BEGIN
DECLARE #return_value BIT;
BEGIN TRY
INSERT INTO Action_Lookup (action_description)
VALUES (#action_description);
INSERT INTO Audit ([user_id], action_id, CREATED, [guid], entity, entity_identifier)
VALUES (#creator_id, SCOPE_IDENTITY(), GETDATE(), NEWID(), #entity, #entity_identifier);
SET #return_value = 1;
RETURN #return_value;
END TRY
BEGIN CATCH
SET #return_value = 0;
RETURN #return_value;
END CATCH
END
the problem that SCOPE_IDENTITY() returns null, I also tried ##IDENTITY and IDENT_CURRENT but non works.
Try output clause:
CREATE PROC [dbo].[Log_Action]
#action_description VARCHAR(MAX),
#creator_id INT,
#entity varchar(50),
#entity_identifier uniqueidentifier
AS
BEGIN
DECLARE #return_value bit;
BEGIN TRY
INSERT INTO Action_Lookup (action_description)
OUTPUT
#creator_id,
inserted.[id], -- in [] there should be actual name of identity column
GETDATE(),
NEWID(),
#entity,
#entity_identifier
INTO Audit ([user_id], action_id, created, [guid], entity, entity_identifier)
VALUES (#action_description);
set #return_value = 1;
return #return_value;
END TRY
BEGIN CATCH
set #return_value = 0;
return #return_value;
END CATCH
END

Trigger to update table according to user rank

I'm a stranger to SQL Server Triggers.
I ended up having a problem like this. Please have a look.
I have two tables 'users' & 'test'
CREATE TABLE users(
email VARCHAR(250),
rank FLOAT
);
CREATE TABLE test(
score INT,
total INT
);
I need to create a trigger to;
2.1 Update users rank by the value of avg ( avg = test.score / test.total)
2.2 Here's What I tried so far:
CREATE TRIGGER auto_rank ON dbo.test FOR INSERT
BEGIN
DECLARE #sc INT
DECLARE #tot INT
DECLARE #avg FLOAT
#tot = SELECT inserted.total FROM dbo.test
#sc = SELECT inserted.score FROM dbo.test
SET #avg=#sc/#tot
UPDATE dbo.users SET rank=#avg WHERE email=inserted.email
END
You missing the email in test from your table design, but it should have such column per your code:
UPDATE dbo.users SET rank=#avg WHERE email=inserted.email
Then you need a view instead of trigger in this case:
Create view user as (select email, score/total as rank from test group by email);
Hope this help.
Try this :
CREATE TRIGGER auto_rank ON dbo.test FOR INSERT
BEGIN
UPDATE a SET a.rank=b.rn
from
users a
inner join
(select email,inserted.score/inserted.total rn from inserted)b
on a.email=b.email
END
I have not tested this, but this should work fine.
You need to modify your tables so that the test table contains the email column:
CREATE TABLE test(score INT,
total INT,
email varchar(250)
);
Then you can create the trgiger like this:
CREATE TRIGGER [dbo].[auto_rank] ON [dbo].[test]
FOR INSERT
AS
BEGIN
DECLARE MyCursor CURSOR FOR
SELECT score, total, email FROM Inserted
DECLARE #sc INT
DECLARE #tot INT
DECLARE #email VARCHAR(30)
DECLARE #avg FLOAT
DECLARE #MSG VARCHAR(50)
OPEN MyCursor;
FETCH NEXT FROM MyCursor INTO #sc, #tot, #email
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #avg=#sc/#tot
UPDATE users SET rank=#avg WHERE users.email=#email
SELECT #MSG = 'email Updated ' + #email + '. New Rank is ' + Str(#avg, 25, 5);
PRINT #MSG
FETCH NEXT FROM MyCursor
END;
CLOSE MyCursor;
DEALLOCATE MyCursor;
END
Sorry for being so late to continue this thread but, I'm happy to say that I found the answer. it's because of you all.
So, here's what i did;
first;
CREATE TABLE users(
email VARCHAR(250),
rank FLOAT,
constraint pk_users PRIMARY KEY(email)
);
CREATE TABLE test(
email VARCHAR(250),
score INT,
total INT,
constraint pk_test PRIMARY KEY(email),
constraint fk_from_users FOREIGN KEY(email) references users(email)
);
create trigger trig_ex02 on dbo.test
after insert
as
begin
declare #score FLOAT
declare #total FLOAT
declare #average FLOAT
declare #msg varchar(100)
declare #email varchar(250)
set #email = (select email from inserted)
set #score = (select score from inserted)
set #total = (select total from inserted)
set #average =(#score/#total)
select #msg = 'SCORE IS'+ str(#score)+'TOTAL IS'+str(#total)+' AVERAGE IS ' +str(#average,25,5)+' END '
print #msg
UPDATE users SET rank=#average WHERE users.email=#email
end;

How can I iterate over a recordset within a stored procedure?

I need to iterate over a recordset from a stored procedure and execute another stored procedure using each fields as arguments. I can't complete this iteration in the code. I have found samples on the internets, but they all seem to deal with a counter. I'm not sure if my problem involved a counter. I need the T-SQL equivalent of a foreach
Currently, my first stored procedure stores its recordset in a temp table, #mytemp. I assume I will call the secondary stored procedure like this:
while (something)
execute nameofstoredprocedure arg1, arg2, arg3
end
You need to create a cursor to loop through the record set.
Example Table:
CREATE TABLE Customers
(
CustomerId INT NOT NULL PRIMARY KEY IDENTITY(1,1)
,FirstName Varchar(50)
,LastName VARCHAR(40)
)
INSERT INTO Customers VALUES('jane', 'doe')
INSERT INTO Customers VALUES('bob', 'smith')
Cursor:
DECLARE #CustomerId INT, #FirstName VARCHAR(30), #LastName VARCHAR(50)
DECLARE #MessageOutput VARCHAR(100)
DECLARE Customer_Cursor CURSOR FOR
SELECT CustomerId, FirstName, LastName FROM Customers
OPEN Customer_Cursor
FETCH NEXT FROM Customer_Cursor INTO
#CustomerId, #FirstName, #LastName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #MessageOutput = #FirstName + ' ' + #LastName
RAISERROR(#MessageOutput,0,1) WITH NOWAIT
FETCH NEXT FROM Customer_Cursor INTO
#CustomerId, #FirstName, #LastName
END
CLOSE Customer_Cursor
DEALLOCATE Customer_Cursor
Here is a link to MSDN on how to create them.
http://msdn.microsoft.com/en-us/library/ms180169.aspx
This is why I used Raise Error instead of PRINT for output.
http://structuredsight.com/2014/11/24/wait-wait-dont-tell-me-on-second-thought/
It's very easy to loop through the rows in SQL procedure. You just need to use a cursor. Here is an example:
Let us consider a table Employee with column NAME and AGE with 50 records into it and you have to execute a stored procedure say TESTPROC which will take name and age parameters of each row.
create procedure CursorProc
as
begin
declare #count bigint;
declare #age varchar(500)
declare #name varchar(500)
select #count = (select count(*) from employee)
declare FirstCursor cursor for select name, age from employee
open FirstCursor
while #count > 0
begin
fetch FirstCursor into #name, #age
Exec TestProc #name, #age
set #count = #count - 1
end
close FirstCursor
deallocate FirstCursor
end
Make sure you deallocate the cursor to avoid errors.
try this (cursor free looping):
CREATE TABLE #Results (RowID int identity(1,1), Col1 varchar(5), Col2 int, ... )
DECLARE #Current int
,#End int
DECLARE #Col1 varchar(5)
,#Col2 int
,...
--you need to capture the result set from the primary stored procedure
INSERT INTO #Results
(Col1, COl2,...)
EXEC nameofstoredprocedure_1 arg1, arg2, arg3
SELECT #End=##ROWCOUNT,#Current=0
--process each row in the result set
WHILE #Current<#End
BEGIN
SET #Current=#Current+1
SELECT
#Col1=COl1, #Col2=Col2
FROM #Results
WHERE RowID=#Current
--call the secondary procedure for each row
EXEC nameofstoredprocedure_2 #Col1, #Col2,...
END
working example:
CREATE PROCEDURE nameofstoredprocedure_1
(#arg1 int, #arg2 int, #arg3 int)
AS
SELECT 'AAA',#arg1 UNION SELECT 'BBB',#arg2 UNION SELECT 'CCC',#arg3
GO
CREATE PROCEDURE nameofstoredprocedure_2
(#P1 varchar(5), #P2 int)
AS
PRINT '>>'+ISNULL(#P1,'')+','+ISNULL(CONVERT(varchar(10),#P2),'')
GO
CREATE TABLE #Results (RowID int identity(1,1), Col1 varchar(5), Col2 int)
DECLARE #Current int
,#End int
DECLARE #Col1 varchar(5)
,#Col2 int
INSERT INTO #Results
(Col1, COl2)
EXEC nameofstoredprocedure_1 111, 222, 333
SELECT #End=##ROWCOUNT,#Current=0
WHILE #Current<#End
BEGIN
SET #Current=#Current+1
SELECT
#Col1=COl1, #Col2=Col2
FROM #Results
WHERE RowID=#Current
EXEC nameofstoredprocedure_2 #Col1, #Col2
END
OUTPUT:
(3 row(s) affected)
>>AAA,111
>>BBB,222
>>CCC,333