SQL Server trigger with loop for multiple row insertion - sql

I've created trigger for my database which handles some insertion but when I add multiple values in 1 SQL query it doesn't work:
ALTER TRIGGER [dbo].[ConferenceDayTrigger]
ON [dbo].[Conferences]
AFTER INSERT
AS
BEGIN
DECLARE #ID INT
DECLARE #dayC INT
DECLARE #counter INT
SET #counter = 1
SET #ID = (SELECT IDConference FROM Inserted)
SET #dayC = (SELECT DATEDIFF(DAY, start,finish) FROM Inserted)
WHILE #counter <= #dayC + 1
BEGIN
EXEC AddConferenceDay #Id, #counter
SET #counter = #counter +1
END
END
For single insertion it works ok. But what should I change/add to make it execute for each row of inserted values?

If you cannot change the stored procedure, then this might be one of the (very few) cases when a cursor comes to the rescue. Double loops, in fact:
ALTER TRIGGER [dbo].[ConferenceDayTrigger]
ON [dbo].[Conferences]
AFTER INSERT
AS
BEGIN
DECLARE #ID INT;
DECLARE #dayC INT;
DECLARE #counter INT
SET #counter = 1;
DECLARE yucky_Cursor CURSOR FOR
SELECT IDConference, DATEDIFF(DAY, start,finish) FROM Inserted;
OPEN yucky_Cursor; /*Open cursor for reading*/
FETCH NEXT FROM yucky_Cursor INTO #ID, #dayC;
WHILE ##FETCH_STATUS = 0
BEGIN
WHILE #counter <= #dayC + 1
BEGIN
EXEC AddConferenceDay #Id, #counter;
SET #counter = #counter + 1;
END;
FETCH NEXT FROM yucky_Cursor INTO #ID, #dayC;
END;
CLOSE yucky_Cursor;
DEALLOCATE yucky_Cursor;
END;
I suspect there is a way to refactor and get rid of the cursor and use set-based operations.

When you insert more than one record, you need to cursor/while to call the AddConferenceDay procedure for each record.
But I will suggest you to alter your procedure to accept table type as input parameter. So that more than one ID and dayC as input to AddConferenceDay procedure. It is more efficient than your current approach.
something like this
create type udt_Conferences as table (ID int,dayC int)
Alter the procedure to use udt_Conferences as input parameter
Alter procedure AddConferenceDay (#input udt_Conferences readonly)
as
begin
/* use #input table type instead of #Id and #counter variables */
end
To call the procedure update the trigger with created udt
ALTER TRIGGER [dbo].[ConferenceDayTrigger]
ON [dbo].[Conferences]
AFTER INSERT
AS
BEGIN
Declare #input udt_Conferences
insert into #input (ID,dayC)
select IDConference,DATEDIFF(DAY, start,finish) from Inserted
END

add these lines to your trigger
AFTER INSERT
AS
BEGIN
AFTER INSERT
AS
BEGIN
Declare #Count int;
Set #Count=##ROWCOUNT;
IF #Count=0
Return;
SET NOCOUNT ON;
-- Insert statements for trigger here

Related

Update identity column in dynamic T-SQL

I should write a SQL script in which I use a dynamic cursor. I want to reidentity table rows and has written stored procedure:
CREATE PROCEDURE [dbo].[ReidentityComments]
AS
BEGIN
SET NOCOUNT ON;
DECLARE Reidentitier CURSOR
LOCAL
SCROLL
DYNAMIC
FOR
SELECT * FROM Comment
FOR UPDATE;
OPEN Reidentitier;
DECLARE #CommentId INT;
DECLARE #FilmId INT;
DECLARE #Text NVARCHAR(2000);
DECLARE #PlacingDate DATETIME;
DECLARE #UserId INT;
DECLARE #current INT;
SET #current = 1;
DECLARE #updateSql NVARCHAR(100);
SET #updateSql = N'
SET IDENTITY_INSERT VideoLibrary.dbo.Comment ON;
UPDATE Comment SET CommentId = #cur WHERE CommentId = #id;
SET IDENTITY_INSERT VideoLibrary.dbo.Comment OFF;
';
DECLARE #params NVARCHAR(100);
SET #params = N'#cur INT, #id INT';
FETCH NEXT
FROM Reidentitier
INTO #CommentId, #FilmId, #Text, #PlacingDate, #UserId;
WHILE ##FETCH_STATUS = 0
BEGIN
IF #CommentId != #current
EXECUTE sp_executesql #updateSql, #params, #cur = #current, #id = #CommentId;
FETCH NEXT
FROM Reidentitier
INTO #CommentId, #FilmId, #Text, #PlacingDate, #UserId;
SET #current = #current + 1;
END
END
But when I try to execute the procedure I get the error:
An expression of non-boolean type specified in a context where a condition is expected, near 'Co'.
Can anyone help me, please?
The problem in your code is in the declaration of #updateSql
nvarchar(100) is too small to store the whole string. It's getting truncated, and you're ending up with something like...
SET IDENTITY_INSERT VideoLibrary.dbo.Comment ON;
UPDATE Comment SET CommentId = #cur WHERE CommentId
And that's when the error is returned...
That said, i.m.h.o. a better approach to do what you're trying to do would probably be:
SELECT IDENTITY (int, 1, 1) AS CommentId, FilmId, Text, PlacingDate, UserId
INTO TMP_COMMENT
FROM Comment;
exec SP_RENAME Comment, OLD_COMMENT;
exec SP_RENAME TMP_COMMENT, Comment;
The only downside to this approach is that none of the indexes, or primary keys will be created on the new comment table.
You wont be able to update the identity column anyway (regardless of Identity_insert value).
So to solve your problem - Does it really need to be the identity column? (if the Identity column is also the primary key you could get into issues if you have data referring to the comments table.)
Could you just create a column called RowNum (for instance) - and re populate that using Row_Number()?
eg
UPDATE Comment SET RowNum = c.NewSequence
FROM (
SELECT CommentId, NewSequence = ROW_NUMBER() OVER (ORDER BY CommentId)
FROM Comments) c
WHERE c.Id = Comment.id

How to update SQL field using function and stored procedure?

What I want to do is to update column (NewID) in my table (SampleTable) with the following code, but it's not working.. can somebody help me please? Whats wrong with it?
I have the table 'SampleTable' wich has the fields 'NewID' and 'OldID'.
UPDATE SampleTable SET NewID = dbo.fn_DoStuff(OldID) <-- Not working
My function:
ALTER FUNCTION [dbo].[fn_DoStuff]
(
#int oldid
)
RETURNS int
AS
BEGIN
DECLARE #returnValue int
EXEC #returnValue = dbo.spc_DoStuff #oldid
RETURN #returnValue
END
My stored procedure:
SampleTable1 has the columns ID, SomeColName.
ALTER PROCEDURE [dbo].[spc_GeraAtriðisorðalistaÚrAtriðisorði]
(
#oldid int
)
AS
BEGIN
DECLARE #returnValue int
INSERT INTO SampleTable1 (SomeColName) VALUES (null)
SET #returnValue = ##IDENTITY
INSERT INTO SampleTable2 (SomeColName1, SomeColName2) VALUES (#returnValue, #oldid)
SELECT #returnValue AS RetVal
END
You have 2 problems, the first is you cannot call a stored procedure inside a function, nor can you perform your insert within a function.
The second problem is that even if you could call a stored procedure inside a function, you are not returning the value from the procedure correctly. You would need something like:
CREATE TABLE dbo.T (ID INT IDENTITY, Filler CHAR(10));
GO
CREATE PROCEDURE dbo.Test
AS
DECLARE #i INT;
INSERT dbo.T (Filler) VALUES (NULL);
RETURN SCOPE_IDENTITY();
GO
Note the use of the RETURN statement, if you don't use this the default return value is 0
Then you can use:
DECLARE #i INT;
EXECUTE #i = dbo.Test;
SELECT ReturnVal = #i;
*Note, I have replaced ##IDENTITY with SCOPE_IDENTITY(), ##IDENTITY is rarely the correct function to use*
Example on SQL Fiddle
With your solution GarethD I could still not call the function like I wanted to
UPDATE SampleTable SET NewID = dbo.fn_DoStuff(OldID).
Your code helped me though to start thinking another way. Now I'm using a cursor and a while loop and it works perfectly. See my solution below:
DECLARE #OldID AS INT
DECLARE Upd_CURSOR FOR
SELECT OldID
FROM dbo.SampleTable
WHERE OldID is not null
FOR UPDATE OF NewID
OPEN Upd_CURSOR;
FETCH NEXT FROM Upd_CURSOR INTO #OldID
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #returnValue int;
INSERT INTO SampleTable1 (SomeColName) VALUES (null);
SET #returnValue = SCOPE_IDENTITY();
INSERT INTO SampleTable2 (SomeColName1, SomeColName2) VALUES (#returnValue, #OldID)
UPDATE dbo.SampleTable SET NewID = #returnValue WHERE CURRENT OF Upd_CURSOR;
FETCH NEXT FROM Upd_CURSOR INTO #OldID;
END;
CLOSE Upd_CURSOR;
DEALLOCATE Upd_CURSOR;
GO

How to return the output of stored procedure into a variable in sql server

I want to execute a stored procedure in SQL Server and assign the output to a variable (it returns a single value) ?
That depends on the nature of the information you want to return.
If it is a single integer value, you can use the return statement
create proc myproc
as
begin
return 1
end
go
declare #i int
exec #i = myproc
If you have a non integer value, or a number of scalar values, you can use output parameters
create proc myproc
#a int output,
#b varchar(50) output
as
begin
select #a = 1, #b='hello'
end
go
declare #i int, #j varchar(50)
exec myproc #i output, #j output
If you want to return a dataset, you can use insert exec
create proc myproc
as
begin
select name from sysobjects
end
go
declare #t table (name varchar(100))
insert #t (name)
exec myproc
You can even return a cursor but that's just horrid so I shan't give an example :)
You can use the return statement inside a stored procedure to return an integer status code (and only of integer type). By convention a return value of zero is used for success.
If no return is explicitly set, then the stored procedure returns zero.
CREATE PROCEDURE GetImmediateManager
#employeeID INT,
#managerID INT OUTPUT
AS
BEGIN
SELECT #managerID = ManagerID
FROM HumanResources.Employee
WHERE EmployeeID = #employeeID
if ##rowcount = 0 -- manager not found?
return 1;
END
And you call it this way:
DECLARE #return_status int;
DECLARE #managerID int;
EXEC #return_status = GetImmediateManager 2, #managerID output;
if #return_status = 1
print N'Immediate manager not found!';
else
print N'ManagerID is ' + #managerID;
go
You should use the return value for status codes only. To return data, you should use output parameters.
If you want to return a dataset, then use an output parameter of type cursor.
more on RETURN statement
Use this code, Working properly
CREATE PROCEDURE [dbo].[sp_delete_item]
#ItemId int = 0
#status bit OUT
AS
Begin
DECLARE #cnt int;
DECLARE #status int =0;
SET NOCOUNT OFF
SELECT #cnt =COUNT(Id) from ItemTransaction where ItemId = #ItemId
if(#cnt = 1)
Begin
return #status;
End
else
Begin
SET #status =1;
return #status;
End
END
Execute SP
DECLARE #statuss bit;
EXECUTE [dbo].[sp_delete_item] 6, #statuss output;
PRINT #statuss;
With the Return statement from the proc, I needed to assign the temp variable and pass it to another stored procedure. The value was getting assigned fine but when passing it as a parameter, it lost the value. I had to create a temp table and set the variable from the table (SQL 2008)
From this:
declare #anID int
exec #anID = dbo.StoredProc_Fetch #ID, #anotherID, #finalID
exec dbo.ADifferentStoredProc #anID (no value here)
To this:
declare #t table(id int)
declare #anID int
insert into #t exec dbo.StoredProc_Fetch #ID, #anotherID, #finalID
set #anID= (select Top 1 * from #t)

Can I get the results of a stored procedure into a cursor within another stored procedure in SQL

I'm trying to put the results of a stored procedure into a cursor to use within the current procedure. I've added my code below but I'm not sure if this is possible or if my syntax is correct?
DECLARE cursorIDList CURSOR FOR
EXEC spGetUserIDs
OPEN cursorIDList
FETCH NEXT FROM cursorIDList INTO #ID
I receive the following error: Incorrect syntax near 'EXEC'. Expecting SELECT, '(' or WITH.
Thanks in advance.
You can do it like this:
DECLARE #t TABLE (ID INT)
INSERT INTO #t
EXEC spGetUserIDs
DECLARE cursorIDList CURSOR FOR
SELECT * FROM #t
OPEN cursorIDList
FETCH NEXT FROM cursorIDList INTO #ID
in my opinion very interesting approach would be to use cursor as parameter (although if you not going to update table i don't think its better choice):
create Table dbo.MyTable
(
i int
);
Insert Into dbo.MyTable (i) values (1)
Insert Into dbo.MyTable (i) values (2)
Insert Into dbo.MyTable (i) values (3)
Insert Into dbo.MyTable (i) values (4)
Go
Set NoCount ON;
Go
Create Proc dbo.myProc
(
#someValue int,
#cur Cursor Varying Output
)
As
Begin
declare #x int;
Set #cur = Cursor for
Select i From dbo.MyTable
Where i < #someValue;
open #cur
End
Go
-- Use of proc
declare #cur cursor;
declare #x int;
Exec dbo.myProc 3, #cur output
fetch next from #cur into #x
while ##fetch_status = 0
begin
print 'value: ' + cast(#x as varchar)
fetch next from #cur into #x
end
close #cur;
Deallocate #cur;
Go
--Cleanup
Drop Proc dbo.myProc
Drop Table dbo.MyTable
The syntax of cursor in SQL-Server is:
DECLARE cursor_name [ INSENSITIVE ] [ SCROLL ] CURSOR FOR select_statement
After FOR you must write a SELECT.
For more info see:
https://msdn.microsoft.com/it-it/library/ms180169.aspx

Is there a way to force a trigger to run on an update statement with multiple rows?

I have had to make changes to a trigger and assumed that running an update query like the following would make the trigger execute for all the matched rows. But instead, it only updates the record that it finds.
UPDATE someTable SET someField = someField WHERE someField = 'something';
As a quick solution, I created the following query using a cursor to loop through the records and update each row. It works, and luckily I don't have a really large dataset so it doens't take too long, but it just doesn't seem like the best solution.
DECLARE #id INT;
DECLARE queryCursor CURSOR FOR
SELECT id FROM someTable WHERE someField='something'
OPEN queryCursor
FETCH NEXT FROM queryCursor INTO #id
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE someTable SET someField = someField WHERE id = #id
FETCH NEXT FROM queryCursor INTO #id
END
CLOSE queryCursor
DEALLOCATE queryCursor
Is there a better way to get a trigger to execute on multiple rows in SQL Server?
Edit: The code from trigger
FOR INSERT, UPDATE
AS
IF UPDATE (LineNumber)
OR UPDATE(LineService)
Begin
DECLARE #CDL VARCHAR(50)
DECLARE #LN VARCHAR(100)
DECLARE #A VARCHAR(25)
SELECT #CDL = CommonDataLink FROM INSERTED
SELECT #A = LineService FROM INSERTED
SET #LN = #CDL + #A
UPDATE CommonData SET ReportedLineNo = #LN WHERE CommonDataLink = #CDL
End
You have to make use of the special table INSERTED for what you want:
UPDATED CODE
FOR INSERT, UPDATE
AS
IF UPDATE (LineNumber)
OR UPDATE(LineService)
Begin
DECLARE #CDL VARCHAR(50)
DECLARE #LN VARCHAR(100)
DECLARE #A VARCHAR(25)
SELECT #CDL = CommonDataLink FROM INSERTED
SELECT #A = LineService FROM INSERTED
SET #LN = #CDL + #A
UPDATE A
SET ReportedLineNo = B.LineService + B.CommonDataLink
FROM CommonData A
INNER JOIN INSERTED B
ON A.CommonDataLink = B.CommonDataLink
End