Loop through all the rows of a temp table and call a stored procedure for each row - sql

I have declared a temp table to hold all the required values as follows:
DECLARE #temp TABLE
(
Password INT,
IdTran INT,
Kind VARCHAR(16)
)
INSERT INTO #temp
SELECT s.Password, s.IdTran, 'test'
from signal s inner join vefify v
on s.Password = v.Password
and s.IdTran = v.IdTran
and v.type = 'DEV'
where s.[Type] = 'start'
AND NOT EXISTS (SELECT * FROM signal s2
WHERE s.Password = s2.Password
and s.IdTran = s2.IdTran
and s2.[Type] = 'progress' )
INSERT INTO #temp
SELECT s.Password, s.IdTran, 'test'
FROM signal s inner join vefify v
on s.Password = v.Password
and s.IdTran = v.IdTran
and v.type = 'PROD'
where s.[Type] = 'progress'
AND NOT EXISTS (SELECT * FROM signal s2
WHERE s.Password = s2.Password
and s.IdTran = s2.IdTran
and s2.[Type] = 'finish' )
Now i need to loop through the rows in the #temp table and and for each row call a sp that takes all the parameters of #temp table as input.
How can I achieve this?

you could use a cursor:
DECLARE #id int
DECLARE #pass varchar(100)
DECLARE cur CURSOR FOR SELECT Id, Password FROM #temp
OPEN cur
FETCH NEXT FROM cur INTO #id, #pass
WHILE ##FETCH_STATUS = 0 BEGIN
EXEC mysp #id, #pass ... -- call your sp here
FETCH NEXT FROM cur INTO #id, #pass
END
CLOSE cur
DEALLOCATE cur

Try returning the dataset from your stored procedure to your datatable in C# or VB.Net. Then the large amount of data in your datatable can be copied to your destination table using a Bulk Copy. I have used BulkCopy for loading large datatables with thousands of rows, into Sql tables with great success in terms of performance.
You may want to experiment with BulkCopy in your C# or VB.Net code.

something like this?
DECLARE maxval, val, #ind INT;
SELECT MAX(ID) as maxval FROM table;
while (ind <= maxval ) DO
select `value` as val from `table` where `ID`=ind;
CALL fn(val);
SET ind = ind+1;
end while;

You can do something like this
Declare #min int=0, #max int =0 --Initialize variable here which will be use in loop
Declare #Recordid int,#TO nvarchar(30),#Subject nvarchar(250),#Body nvarchar(max) --Initialize variable here which are useful for your
select ROW_NUMBER() OVER(ORDER BY [Recordid] ) AS Rownumber, Recordid, [To], [Subject], [Body], [Flag]
into #temp_Mail_Mstr FROM Mail_Mstr where Flag='1' --select your condition with row number & get into a temp table
set #min = (select MIN(Rownumber) from #temp_Mail_Mstr); --Get minimum row number from temp table
set #max = (select Max(Rownumber) from #temp_Mail_Mstr); --Get maximum row number from temp table
while(#min <= #max)
BEGIN
select #Recordid=Recordid, #To=[To], #Subject=[Subject], #Body=Body from #temp_Mail_Mstr where Rownumber=#min
-- You can use your variables (like #Recordid,#To,#Subject,#Body) here
-- Do your work here
set #min=#min+1 --Increment of current row number
END

You always don't need a cursor for this. You can do it with a while loop. You should avoid cursors whenever possible. While loop is faster than cursors.

Related

Generating dummy data from existing data set is slow using cursor

I'm trying to generate dummy data from the existing data I have in the tables. All I want is to increase the number of records in Table1 to N specified amount. The other tables should increase based on the foreign key references.
The tables has one to many relationship. For one record in table 1, I can have multiple entries in table 2, and in table 3 I can have many records based on IDs of the second table.
Since IDs are primary keys, I either capture it by
SET #NEWLY_INSERTED_ID = SCOPE_IDENTITY()
after inserting to table 1 and using in insert for table2, or inserting them to temp table and joining them to achieve the same results for table 3.
Here's the approach I'm taking with the CURSOR.
DECLARE #MyId as INT;
DECLARE #myCursor as CURSOR;
DECLARE #DESIRED_ROW_COUNT INT = 70000
DECLARE #ROWS_INSERTED INT = 0
DECLARE #CURRENT_ROW_COUNT INT = 0
DECLARE #NEWLY_INSERTED_ID INT
DECLARE #LANGUAGE_PAIR_IDS TABLE ( LangugePairId INT, NewId INT, SourceLanguage varchar(100), TargetLangauge varchar(100) )
WHILE (#ROWS_INSERTED < #DESIRED_ROW_COUNT)
BEGIN
SET #myCursor = CURSOR FOR
SELECT Id FROM MyTable
SET #CURRENT_ROW_COUNT = (SELECT COUNT(ID) FROM MyTable)
OPEN #myCursor;
FETCH NEXT FROM #myCursor INTO #MyId;
WHILE ##FETCH_STATUS = 0
BEGIN
IF ((#CURRENT_SUBMISSION_COUNT < #DESIRED_ROW_COUNT) AND (#ROWS_INSERTED < #DESIRED_ROW_COUNT))
BEGIN
INSERT INTO [dbo].[MyTable]
([Column1]
([Column2]
([Column3]
)
SELECT
,convert(numeric(9,0),rand() * 899999999) + 100000000
,COlumn2
,Colum3
FROM MyTable
WHERE Id = #MyId
SET #NEWLY_INSERTED_ID = SCOPE_IDENTITY()
INSERT INTO [dbo].[Language]
([MyTable1Id]
,[Target]
,[Source]
OUTPUT inserted.Id, inserted.MyTable1Id, inserted.Source, inserted.[Target] INTO #LANGUAGE_PAIR_IDS (LangugePairId, NewId, SourceLanguage, TargetLangauge)
SELECT
#NEWLY_INSERTED_ID
,[Target]
,[Source]
FROM [dbo].[Language]
WHERE MyTableId = #MyId
ORDER BY Id
DECLARE #tbl AS TABLE (newLanguageId INT, oldLanguageId INT, sourceLanguage VARCHAR(100), targetLanguage VARCHAR(100))
INSERT INTO #tbl (newLanguageId, oldLanguageId, sourceLanguage, targetLanguage)
SELECT 0, id, [Source], [Target] MyTable1Id FROM Language WHERE MyTable1Id = #MyId ORDER BY Id
UPDATE t
SET t.newlanguageid = lp.LangugePairId
FROM #tbl t
JOIN #LANGUAGE_PAIR_IDS lp
ON t.sourceLanguage = lp.SourceLanguage
AND t.targetLanguage = lp.TargetLangauge
INSERT INTO [dbo].[Manager]
([LanguagePairId]
,[UserId]
,[MyDate])
SELECT
tbl.newLanguageId
,p.[UserId]
,p.[MyDate]
FROM Manager m
INNER JOIN #tbl tbl
ON m.LanguagePairId = tbl.oldLanguageId
WHERE m.LanguagePairId in (SELECT Id FROM Language WHERE MyTable1Id = #MyId) -- returns the old language pair id
SET #ROWS_INSERTED += 1
SET #CURRENT_ROW_COUNT +=1
END
ELSE
BEGIN
PRINT 'REACHED EXIT'
SET #ROWS_INSERTED = #DESIRED_ROW_COUNT
BREAK
END
FETCH NEXT FROM #myCursor INTO #MyId;
END
CLOSE #myCursor
DEALLOCATE #myCursor
END
The above code works! It generates the data I need. However, it's very very slow. Just to give some comparison. Initial load of data for table 1 was ~60,000 records, Table2: ~74,000 and Tabl3 ~3,400
I tried to insert 9,000 rows in Table1. With the above code, it took 17:05:01 seconds to complete.
Any suggestion on how I can optimize the query to run little faster? My goal is to insert 1-2 mln records in Table1 without having to wait for days. I'm not tied to CURSOR. I'm ok to achieve the same result in any other way possible.

Cursor in SQL Server: Use loop / condition to find and replace a value

I have a issue with my table in SQL Server. Sometime during a insert a normal value (20-50-80) changed by 1000000. It's really rare but to secure the average i need to make a fix before finding a new solution.
I want to take the value that exceeds 1000000 and replace them by the average of the value between it.
This picture show the problem.
I'm looking at the Cursor in SQL.
Here a exemple of my code. Some issue about the result.
CREATE PROCEDURE [dbo].[Avg_Kwh_TagValuesArchive]
AS
BEGIN
DECLARE #tagId INT
DECLARE #localTime DATE
DECLARE #tagValue FLOAT
DECLARE #limit FLOAT
DECLARE #temp FLOAT
DECLARE #tagValueBefore FLOAT
DECLARE #tagValueAfter FLOAT
SET #limit = 999999.9
DECLARE Cursor_FalseValues CURSOR
FOR
SELECT TagID, LocalTime, TagValue
FROM TagValuesArchive
ORDER BY LocalTime DESC
OPEN Cursor_FalseValues
FETCH Cursor_FalseValues
INTO #tagId, #localTime, #tagValue
WHILE(##FETCH_STATUS = 0)
BEGIN
IF (#tagValue>=#limit)
BEGIN
SET #tagValueBefore =
(
SELECT TOP 1 TagValue
FROM TagValuesArchive
WHERE LocalTime < #localTime
AND TagID = #tagID
AND TagValue IS NOT NULL
ORDER BY LocalTime DESC
)
SET #tagValueAfter =
(
SELECT TOP 1 TagValue
FROM TagValuesArchive
WHERE LocalTime > #localTime
AND TagID = #tagID
AND TagValue IS NOT NULL
ORDER BY LocalTime DESC
)
UPDATE dbo.TagValuesArchive
SET TagValue= ((SUM( #tagValueBefore + #tagValueAfter ))/2)
FROM dbo.TagValuesArchive
WHERE LocalTime = #localTime
AND TagID = #tagID
FETCH NEXT FROM Cursor_FalseValues
INTO #tagId, #localTime, #tagValue
END
ELSE
BEGIN
-- Fetch of the Cursos increment the line
FETCH NEXT FROM Cursor_FalseValues
INTO #tagId, #localTime, #tagValue
END
-- Fetch of the Cursos increment the line
--FETCH NEXT FROM Cursor_FalseValues
--INTO #tagId, #localTime, #tagValue
END
CLOSE Cursor_FalseValues
DEALLOCATE Cursor_FalseValues
END
I think my problem is a good example to use Cursor, but it's not very clear in my head.
I can take the wrong value and the values between it. But the Update in the database doesn't work.
I don't know if it's a cursor problem or a update. Maybe just a code syntax problem.
Thanks for any informations.
You can try something like this:
DECLARE #t TABLE (
id int,
val float
)
INSERT INTO #t (id, val)
VALUES
(1,.5),
(2,.7),
(3,.3),
(4,.74),
(5,.2341234),
(6,10000000),
(7,.9),
(8,.8),
(9,.87123),
(10,100000000),
(11,.99)
SELECT * FROM #t
DECLARE #limit FLOAT = 1000000;
;WITH OutOfOBoundsValues AS (
SELECT id FROM #t WHERE val >= #limit
), Neighbourvalues AS (
SELECT O.id, (t1.val+t2.val)/2 newval FROM OutOfOBoundsValues O
JOIN #t t1 ON t1.id = O.id-1
JOIN #t t2 ON t2.id = O.id+1
)
UPDATE #t
SET val = N.newval
FROM #t t
JOIN Neighbourvalues N ON t.id = N.Id
SELECT * FROM #t
What happens here is that we select the data same as and above the limit.
Then we get the neighbouring values and calculates the mean value from them.
Lastly we update the out of bounds values with the mean value.
This should be much faster than your cursor.

Insert Data into Table if a condition is true

SQL statement Question
I have an instance where I need to insert one record multiple times in a DB table that contains a unique reference column. I'm declaring a variable and using a select statement to populate variable and then running an insert statement. The issue is that when grabbing unique value into variable it only grabs the last record in the reference table. I need for insert statement to insert at each point where a record is found.
DECLARE #ID INT;
DECLARE #RuleID INT;
SELECT #RuleID = RuleID from Rules where Rule_Name = 'VERSION_ID' and Field = 'TSH'
SELECT #ID = ID FROM CHANNELS WHERE SUBSTRING(CHANNEL_NAME,0,4) != 'HEL'
BEGIN
INSERT INTO Rule_Items
VALUES(#ID,#RuleID,0,'2.5.1','E','A',0,getdate())
END
If you want all combinations, you can use insert . . . select with a cross join:
INSERT INTO Rule_Items
select c.id, r.ruleid, 0, '2.5.1', 'E', 'A', 0, getdate()
from rules r cross join
channels c
where r.Rule_Name = 'VERSION_ID' and r.Field = 'TSH' and
SUBSTRING(c.CHANNEL_NAME, 0, 4) <> 'HEL';
I decided to use a cursor and create a temp table. Here is query that worked.
**--BEGIN TRAN
DECLARE #Channelid INT
DECLARE #RuleID INT
SELECT #RuleID = RuleID From Rules Where Rule_Name = 'VERSION_ID'
DECLARE GetChannelId CURSOR FOR SELECT Channelid FROM HL7_Channels WHERE Channel_Name not like 'MU2%' AND Channel_Description LIKE '%outbound%'
OPEN GetChannelId
FETCH GetChannelId INTO #Channelid
WHILE (##FETCH_STATUS = 0)
BEGIN
IF NOT EXISTS (Select 1 From Rule_Items Where ChannelId = #Channelid AND RuleID = #RuleID)
BEGIN
INSERT INTO Rule_Items VALUES (#Channelid,#RuleID,0,'2.5.1','E','A',0,getdate())
END
FETCH GetChannelId INTO #Channelid
CONTINUE
END
CLOSE GetChannelId
DEALLOCATE GetChannelId
--ROLLBACK TRAN
--COMMIT TRAN**

Combine results of multiple queries as one result in a SQL Server stored procedure

I need some help with a stored procedure. It contains a loop which runs a Select query. What I get is 3 tables with result if it loops thrice. How can I combine the results as one table?
The procedure is as below:
CREATE PROCEDURE [dbo].[spGetRndQuestions]
#ExamCode Nvarchar(60)
AS
BEGIN
Declare #NosQues Int, #Catgry nvarchar(50)
DECLARE CategCursor CURSOR FOR
(Select Category From tblExamDetail Where ExamCode = #ExamCode)
OPEN CategCursor
FETCH NEXT FROM CategCursor INTO #Catgry
WHILE ##FETCH_STATUS = 0
BEGIN
SET #NosQues = (Select NoOfQues from tblExamDetail Where ExamCode=#ExamCode AND Category=#Catgry)
SELECT TOP(#NosQues) QM.QuestionID, QM.QuestionDesc, QM.QuestionMarks, QM.Answer1, QM.Answer2, QM.Answer3, QM.Answer4 FROM tblQuestionMaster QM
INNER JOIN tblExamMaster EM ON QM.Dept = EM.Dept AND QM.Location = EM.Location AND QM.QuesModule = EM.ExamModule
Where EM.ExamCode=#ExamCode AND QM.Category =#Catgry
Order by NEWID()
/*SELECT TOP (#NosQues) QuestionID,QuestionDesc,Answer1,Answer2,Answer3,Answer4,QuestionMarks from [dbo].[tblQuestionMaster] Where Category=#Catgry AND
Order by NEWID() */
FETCH NEXT FROM CategCursor INTO #Catgry
END
CLOSE CategCursor
DEALLOCATE CategCursor
END
Thanks, your help is truly appreciated.
Create the table variable in start of your SP of appropriate structure of your return table. In each iteration insert selected data to that table. After deallocating cursor select from that table.
CREATE PROCEDURE [dbo].[spGetRndQuestions]
#ExamCode Nvarchar(60) AS
BEGIN
Declare #NosQues Int, #Catgry nvarchar(50)
DECLARE #tbl TABLE(QuestionID int, QuestionDesc ....)
DECLARE CategCursor CURSOR FOR (Select Category From tblExamDetail Where ExamCode=#ExamCode)
OPEN CategCursor
FETCH NEXT FROM CategCursor INTO #Catgry
WHILE ##FETCH_STATUS = 0
BEGIN
SET #NosQues = (Select NoOfQues from tblExamDetail Where ExamCode=#ExamCode AND Category=#Catgry)
INSERT INTO #tbl
SELECT TOP(#NosQues) QM.QuestionID, QM.QuestionDesc, QM.QuestionMarks, QM.Answer1, QM.Answer2, QM.Answer3, QM.Answer4 FROM tblQuestionMaster QM
INNER JOIN tblExamMaster EM ON QM.Dept = EM.Dept AND QM.Location = EM.Location AND QM.QuesModule = EM.ExamModule
Where EM.ExamCode=#ExamCode AND QM.Category =#Catgry
Order by NEWID()
/*SELECT TOP (#NosQues) QuestionID,QuestionDesc,Answer1,Answer2,Answer3,Answer4,QuestionMarks from [dbo].[tblQuestionMaster] Where Category=#Catgry AND
Order by NEWID() */
FETCH NEXT FROM CategCursor INTO #Catgry
END
CLOSE CategCursor
DEALLOCATE CategCursor
SELECT * FROM #tbl
END
Hamlet does answer the question. However, the query you posted could be optimized to eliminate the need for the cursor or table variable. The following code should do that nicely:
CREATE PROCEDURE [dbo].[spGetRndQuestions]
#ExamCode Nvarchar(60)
AS
SELECT
--A.[category],
B.*
FROM tblExamDetail A
CROSS APPLY (
SELECT TOP (A.[NoOfQues])
QM.QuestionID,QM.QuestionDesc,QM.QuestionMarks,
QM.Answer1,QM.Answer2,QM.Answer3,QM.Answer4
FROM tblQuestionMaster QM
INNER JOIN tblExamMaster EM
ON QM.Dept = EM.Dept
AND QM.Location = EM.Location
AND QM.QuesModule = EM.ExamModule
WHERE EM.ExamCode = A.[ExamCode]
AND QM.Category = A.[Category]
ORDER BY NEWID()
) B
WHERE A.[ExamCode] = #ExamCode

SQL while loop with Temp Table

I need to create a temporary table and then update the original table. Creating the temporary table is not a problem.
create table #mod_contact
(
id INT IDENTITY NOT NULL PRIMARY KEY,
SiteID INT,
Contact1 varchar(25)
)
INSERT INTO #mod_contact (SiteID, Contact1)
select r.id, r.Contact from dbo.table1 r where CID = 142
GO
Now I need to loop through the table and update r.contact = SiteID + r.contact
I have never used a while loop before and can't seem to make any examples I have seen work.
You can do this in multiple ways, but I think you're looking for a way using a cursor.
A cursor is sort of a pointer in a table, which when incremented points to the next record. ( it's more or less analogeous to a for-next loop )
to use a cursor you can do the following:
-- DECLARE the cursor
DECLARE CUR CURSOR FAST_FORWARD READ_ONLY FOR SELECT id, siteId, contract FROM #mod_contract
-- DECLARE some variables to store the values in
DECLARE #varId int
DECLARE #varSiteId int
DECLARE #varContract varchar(25)
-- Use the cursor
OPEN CUR
FETCH NEXT FROM CUR INTO #varId, #varSiteId, #varContract
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE dbo.table1
SET contract = #varSiteId + #varContract -- It might not work due to the different types
WHERE id = #varId
FETCH NEXT FROM CUR INTO #varId, #varSiteId, #varContract
END
CLOSE CUR
DEALLOCATE CUR
It's not the most efficient way to get this done, but I think this is what you where looking for.
Hope it helps.
Use a set based approach - no need to loop (from the little details):
UPDATE
r
SET
r.Contact = m.SiteID + r.Contact
FROM
table1 r
INNER JOIN
#mod_contact m
ON m.id=r.id
Your brain wants to do this:
while records
update(i); //update record i
records = records + 1
end while
SQL is set based and allows you to take a whole bunch of records and update them in a single command. The beauty of this is you can use the WHERE clause to filter certain rows that are not needed.
As others have mentioned, learning how to do loops in SQL is generally a bad idea; however, since you're trying to understand how to do something, here's an example:
DECLARE #id int
SELECT #ID =1
WHILE #ID <= (SELECT MAX(ID) FROM table_1)
-- while some condition is true, then do the following
--actions between the BEGIN and END
BEGIN
UPDATE table_1
SET contact = CAST(siteID as varchar(100)) + contact
WHERE table_1.CID = #ID
--increment the step variable so that the condition will eventually be false
SET #ID = #ID + 1
END
--do something else once the condition is satisfied
PRINT 'DONE!! Don't try this in production code...'
Try this one:
-- DECLARE the cursor
DECLARE CUR CURSOR FAST_FORWARD READ_ONLY FOR SELECT column1,column2 FROM table
-- DECLARE some variables to store the values in
DECLARE #varId int
DECLARE #varSiteId int
--DECLARE #varContract varchar(25)
-- Use the cursor
OPEN CUR
FETCH NEXT FROM CUR INTO #varId, #varSiteId
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT *
FROM Table2
WHERE column1 = #varId
AND column2 = #varSiteId
FETCH NEXT FROM CUR INTO #varId, #varSiteId
END
CLOSE CUR
DEALLOCATE CUR
need to create a temporary table and then up date the original table.
Why use a temporary table at all? Your CID column doesn't appear in the temporary table, so I don't see how you can successfully update the original table using SiteID, unless there is only one row where CID = 142 in which using a temp table is definitely overkill.
You can just do this:
UPDATE dbo.table1
SET contact = SiteID + contact
WHERE CID = 142;
Here's a related example which may help getting you to 'think in SQL':
UPDATE T
SET A = B, B = A;
Assuming A and B are of the same type, this would successfully swap their values.