Loop in stored procedure in SQL server - sql

I need help with writing stored procedure that calls another stored procedure and passes values to it. So far this was done in C#, now I want to move it to stored procedure and make an SQL agent job that calls it at specific time. Any ideas? This is the case.
Table A:
PK_TableA_ID
Table B:
PK_TableB_ID
Stored procedure SP1:
#TableA_ID
#TableB_ID
I need this but in T-SQL
foreach(var TableAId in TableA)
{
foreach(var TableBId in TableB)
{
//call stored procedure
SP1(TableAId, TableBId);
}
}

Here's an example of how you can use cursors to do loops:
-- set up some test data
declare #table_a table (PK_TableA_ID int)
declare #table_b table (PK_TableB_ID int)
insert #table_a values (1),(2),(3)
insert #table_b values (4),(5),(6)
-- do the actual processing
SET NOCOUNT ON
DECLARE #TableA_ID int, #TableB_ID int
DECLARE TableA_cursor CURSOR FOR SELECT PK_TableA_ID FROM #table_a
OPEN TableA_cursor
FETCH NEXT FROM TableA_cursor INTO #TableA_ID
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE TableB_cursor CURSOR FOR SELECT PK_TableB_ID FROM #table_b
OPEN TableB_cursor
FETCH NEXT FROM TableB_cursor INTO #TableB_ID
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT CAST(#TableA_ID AS CHAR(1)) + ':' + CAST(#TableB_ID AS CHAR(1))
-- execute your stored procedure here:
-- EXEC Your_stored_procedure (#TableA_ID, #TableB_ID)
FETCH NEXT FROM TableB_cursor INTO #TableB_ID
END
CLOSE TableB_cursor
DEALLOCATE TableB_cursor
FETCH NEXT FROM TableA_cursor INTO #TableA_ID
END
CLOSE TableA_cursor
DEALLOCATE TableA_cursor
The cursor above (with the test data in the temporary tables) will generate this output:
1:4
1:5
1:6
2:4
2:5
2:6
3:4
3:5
3:6
Using cursors might not be the best way to solve your problem though.

I have a clean and clear option without cursors for this case using the table ids.
DECLARE
#Counter1 INT,#MaxId1 INT,
#Counter2 INT, #MaxId2 INT
SELECT #Counter1 = min(PK_TableA_ID) , #MaxId1 = max(PK_TableA_ID)
FROM TableA
SELECT #Counter2 = min(PK_TableB_ID) , #MaxId2 = max(PK_TableB_ID)
FROM TableB
WHILE(#Counter1 IS NOT NULL AND #Counter1 <= #MaxId1)
BEGIN
WHILE(#Counter2 IS NOT NULL AND #Counter2 <= #MaxId2)
BEGIN
//call stored procedure
SP1(#Counter1, #Counter2);
SET #Counter2 = #Counter2 + 1
END;
SELECT #Counter2 = min(PK_TableB_ID) , #MaxId2 = max(PK_TableB_ID)
FROM TableB
SET #Counter1 = #Counter1 + 1
END;

Related

SQL Server trigger with loop for multiple row insertion

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

Trying to create a stored procedure that will build multiple entries

We use a product that essentially sets permissions for objects via a SQL table. I am trying to create a stored procedure that will essentially set the permissions for one user to the same as another. Here's what I have so far:
CREATE PROCEDURE SETPRODUCTS
#sourceCC BIGINT,
#targetCC BIGINT
BEGIN
DECLARE #SQL varchar(5000)
DECLARE #Sequence_Id varchar(50)
SET #sequence_id = 0
BEGIN TRANSACTION
DELETE FROM TCC WHERE CC_Id = #targetCC
DECLARE cursorCurrent CURSOR FOR
SELECT MAX(TCC_Id) + 1
FROM TCC WITH (tablockx holdlock)
OPEN cursorCurrent
FETCH NEXT FROM cursorCurrent INTO #sequence_id
IF (#sequence_id IS NULL)
SET #sequence_id = 1
INSERT INTO TCC (TCC_Id, T_Id, CCC_Id)
VALUES (#sequence_id,
(SELECT T_Id FROM TCC WHERE CC_Id = #sourceCC), #targetCC)
CLOSE cursorCurrent
DEALLOCATE cursorCurrent
COMMIT TRANSACTION
END
The error I get is that my subquery has more than one value, which is true. I want to take every entry from that subquery and use it to insert new rows into my database.
Any help would be greatly appreciated!
Try this version of Insert:
INSERT INTO TCC (TCC_Id, T_Id, CCC_Id)
SELECT #sequence_id, #targetCC, T_Id FROM TCC WHERE CC_Id = #sourceCC
Alter your procedure as below
Alter PROCEDURE SETPRODUCTS
#sourceCC BIGINT,
#targetCC BIGINT
BEGIN
DECLARE #SQL varchar(5000)
DECLARE #Sequence_Id varchar(50)
SET #sequence_id = 0
BEGIN TRANSACTION
DELETE FROM TCC WHERE CC_Id = #targetCC
DECLARE cursorCurrent CURSOR FOR
SELECT MAX(TCC_Id) + 1
FROM TCC WITH (tablockx holdlock)
OPEN cursorCurrent
FETCH NEXT FROM cursorCurrent INTO #sequence_id
Declare #total int
IF (#sequence_id IS NULL)
SET #sequence_id = 1
SELECT T_Id,ROW_NUMBER() over (order by TCC_id) as rowno into #temp
FROM TCC WHERE CC_Id = #sourceCC
select #total=COUNT(*) from #temp
while(#rowno <> #total)
begin
Declare #t_id int
set #t_id=(select top 1 T_id from #temp where rowno=#rowno)
INSERT INTO TCC (TCC_Id, T_Id, CCC_Id)
VALUES (#sequence_id,#t_id,#targetCC)
set #rowno=#rowno+1
End
DROP TABLE #temp
CLOSE cursorCurrent
DEALLOCATE cursorCurrent
COMMIT TRANSACTION
END

Execute Stored Procedure having a query as a parameter

How can I execute the sp_1 for every ProductId and get a result set?
EXEC sp_1 (SELECT ID FROM Products)
Try this way. No direct query it seems.
execute sp for each row
or try this , make small changes if needed.Use temp table to get values out of sp. Use the below inside a sp if needed.
begin
declare #ID int
declare #temp table (col1 int)
declare cur cursor for select distinct ID from products
open cur
fetch next from cur into #ID
truncate table #temp
while(##FETCH_STATUS=0)
begin
insert into #temp (<'cols/output from procedure'>) exec (#ID)
end
select * from #temp
end
I would store the id's in a temp table and use a WHILE loop (AVOID CURSORS!)
DECLARE #prodid INT
SELECT prodid, 0 as Processed INTO #prod_ids FROM Products
WHILE EXISTS (SELECT prodid FROM #prod_ids WHERE Processed = 0)
BEGIN
SELECT TOP 1 #prodid = prodid FROM #prod_ids WHERE Processed = 0
EXEC sp_1(#prodid)
UPDATE #prod_ids SET Processed = 1 WHERE prodid = #prodid
END
With dynamic query in SQL Server:
declare #cadena varchar(max) = ''
select #cadena = #cadena + 'exec sp_1 ' + ltrim(ID) + ';'
from Products;
exec(#cadena);

Append result of stored procedure to earlier executed stored procedure

I have a stored procedure which takes a single parameter containing single value.
I want to pass a collection of values to the procedure one by one and get the combined result into one table . Is this possible ?
Currently I am using cursor to execute procedure in loop but only result from passing first value in the collection is obtained.
declare #clientid varchar(10)
create table #tmpp(secid varchar(10))
insert into #tmpp values(2319)
insert into #tmpp values(2855)
insert into #tmpp values(1303)
declare cur CURSOR LOCAL for
select secid from #tmpp
open cur
fetch next from cur into #seclientid
while ##FETCH_STATUS = 0 BEGIN
exec getReportforclient #clientid
fetch next from cur into #clientid
END
close cur
deallocate cur
drop table #tmpp
If this is too obfuscated / unclear/ silly, can somebody please provide me an alternative ?
Any help is most appreciated . Thanks.
There probably is a way to this without using cursors, but without taking a look at your stored procedure we can't help too much on that. You can create a table (temporary or not) that has the same structure that the results of your sp and insert the results into it. Something like this:
DECLARE #clientid VARCHAR(10)
CREATE TABLE #tmpp(secid VARCHAR(10))
INSERT INTO #tmpp VALUES(2319)
INSERT INTO #tmpp VALUES(2855)
INSERT INTO #tmpp VALUES(1303)
CREATE TABLE #Results(col1 INT, col2.... -- create the table that will hold your
-- results
DECLARE cur CURSOR LOCAL for
SELECT secid
FROM #tmpp
OPEN cur
FETCH NEXT FROM cur INTO #seclientid
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #Results
exec getReportforclient #clientid
FETCH NEXT FROM cur INTO #clientid
END
CLOSE cur
DEALLOCATE cur
DROP TABLE #tmpp
SELECT *
FROM #Results
Loop without CURSOR
IF OBJECT_ID('tempdb.dbo.#tmpp') IS NOT NULL DROP TABLE #tmpp
CREATE TABLE #tmpp(Clientid varchar(10))
INSERT #tmpp values(2319), (2855), (1303)
IF OBJECT_ID('tempdb.dbo.#Results') IS NOT NULL DROP TABLE #Results
CREATE TABLE #Results(--output columns of procedure)
DECLARE #Clientid int = (SELECT MIN(Clientid) FROM #tmpp)
WHILE (#Clientid IS NOT NULL)
BEGIN
INSERT INTO #Results
EXEC getReportforclient #Clientid
SELECT #Clientid = MIN(Clientid) FROM #tmpp WHERE Clientid > #Clientid
END
Simple example on SQLFiddle

Passing data into nested cursors in TSQL

I have a cursor that gets data and fetches it into #data_table. Then in the while loop I want to pass that #data_table into another cursor (as the table name) to run some more processing. I keep getting a declare #data_table error. How do I fix this?
DECLARE #var_name varchar(50)
DECLARE #data_table varchar(50)
DECLARE #data_value varchar(50)
DECLARE curDiscreteVars CURSOR LOCAL
FOR SELECT DISTINCT v.var_name, v.data_table
FROM dbo.vars v
GROUP BY v.var_name, v.data_table
-- Loop through cursor, translating variable values as needed, and generate counts for each val_code for a variable
OPEN curDiscreteVars
FETCH NEXT FROM curDiscreteVars
INTO #var_name, #data_table
WHILE ##FETCH_STATUS = 0
BEGIN
--loop through all possible data values
DECLARE curValues CURSOR LOCAL
FOR SELECT DISTINCT #var_name
FROM #data_table
OPEN curValues
FETCH NEXT FROM curValues
INTO #data_value
WHILE ##FETCH_STATUS = 0
BEGIN
print #var_name
FETCH NEXT FROM curValues
INTO #data_value
END
CLOSE curValues
DEALLOCATE curValues
FETCH NEXT FROM curDiscreteVars
INTO #var_name, #data_table
END
CLOSE curDiscreteVars
DEALLOCATE curDiscreteVars
For my part, i don't like cursors! For me cursors are evil. The give you locks and such that you don't want.
What i always do is create a temp table with the values (like you normally insert into the cursor) and loop through it with a while loop
like this :
declare #currow int
, #totrow int
create table #tmp_values (id int identity(1, 1), val int)
insert
into #tmp_values
select val
from tableX
set #totrow = ##rowcount
set #currow = 1
while #totrow > 0 and #currow <= #totrow
begin
select #val = val
from #tmp_values
where id = #currow
set #currow = #currow + 1
end
That way you have more control of things and you can re-use the tmp table
I'm not sure I understand what you're talking about doing, but variables cannot be used as table names. Or, really anything that's not a field name. You'll need to use dynamic SQL. That is, assign your SQL string to a variable, an then run EXEC() command.
For example:
DECLARE #sqlcmd varchar(max)
DECLARE #table_name sysname
DECLARE cur_tables FOR
SELECT name FROM sys.tables
OPEN cur_tables
FETCH NEXT FROM cur_tables INTO #table_name
WHILE ##FETCH_STATUS = 0 BEGIN
SET #sqlcmd = 'SELECT TOP 10 * FROM ' + QUOTENAME(#table_name)
EXEC ( #sqlcmd )
FETCH NEXT from cur_tables INTO #table_name
END
CLOSE cur_tables
DEALLOCATE cur_tables
Alternately, if what you mean is that you need a location to store the data that is like a table, then create a temporary tabled for it.