loop in sql stored procedure and removing cursors - sql

I have to modify a STORED procedure that is written by someone else.Basically the stored prcoedure uses a cusrsor to fetch the data from the table and then insert that data in another table. While fetching the code form another table, it also gets some distinct columns from another table Below is my code:
Declare data_cursor cursor for
Select emp_no, emp_name, event_date, Test_no, Code, Test_result
From test_table1
ORDER by emp_no
declare
#empNo varchar(100),
#emp_name varchar(2000),
#eventDate varchar(20),
#TestNo varchar(100),
#Code varchar(100),
#TestReuslt varchar(100),
#ProcessName varchar(100),
#FileProcess varchar(200),
#TestProcess varchar(100),
#countA int,
#error_count int
SELECT #ProcessName = (select distinct userID from test_table1)
SELECT #FileProcess = 'EW' + #ProcessName
Select #TestProcess = (Select distinct userID from test_Table1) + 'TXT'
select #countA = 0
BEGIN tran
OPEN data_cursor
fetch data_cursor into
#empNo ,
#emp_name ,
#eventDate ,
#TestNo ,
#Code ,
#TestReuslt
while (##FETCH_STATUS=0)
begin
insert into TESTTable2
(
empNum, empName, eventDate,TestNum, Code, TestResult, Testprocess, ProcessName)
values (#empNo, #emp_name, #eventDate , #TestNo , #Code, #TestReuslt, #TestProcess, #ProcessName)
if # ERROR > 0
begin
select #error_count = #error_count + 1
end
else
set #record_id = ##Identity
if #code like 'D%'
Insert into TESTTable3
(testProcess, FileProcess, empNum)
values (#TestProcess, #FileProcess, #empNo )
if ##error > 0
begin
select #error_count = #error_count + 1
end
set #countA = #countA + 1
fetch data_cursor into
fetch data_cursor into
#empNo ,
#emp_name ,
#eventDate ,
#TestNo ,
#Code ,
#TestReuslt
if # ERROR > 0
BEGIN
select #error_count = #error_count + 1
end
end
if #error_count > 0
begin
rollback tran
end
else
begin /* ##error = 0 */
commit tran
close data_cursor
deallocate data_cursor
Insert into LOG_File
(Name, Count, Processname)
values ('Test1', #CountA,#ProcessName)
Select 'TotalCount' = #CountA
The reason, I have to modify the above STORED proc now is because of some APPLICATION changes, I am getting around 50 distinct userID's from test_table1 so the above subquery(SELECT #ProcessName = (select distinct userID from test_table1) doesn't work. How can I loop through the above stored proc so that each #ProcessName can get inserted in table TESTTable2 so in other words
I want to pass each userId one at a time and insert it in table test_table1 and other subsequent tables. I can declare another cursor to accomplish this, but I was wondering if there is any better way to rewrite this stored proc and not use the cursor at all.
because of my application changes all these three statements above are throwing the error:
SELECT #ProcessName = (select distinct userID from test_table1)
SELECT #FileProcess = 'EW' + #ProcessName
Select #TestProcess = (Select distinct userID from testTable1) + 'TXT'
I am using sql server 2005.
any help will be greatly appreciated.

declare #countA int=0
begin tran
begin try
insert into TESTTable2(empNum, empName, eventDate,TestNum, Code, TestResult, Testprocess, ProcessName)
Select emp_no, emp_name, event_date, Test_no, Code, Test_result,userID+ 'TXT',userID
From test_table1
ORDER by emp_no
SET #CountA=##ROWCOUNT
Insert into TESTTable3(testProcess, FileProcess, empNum)
Select userID+ 'TXT','EW' + userID,emp_no
From test_table1
Where code like 'D%'
ORDER by emp_no
commit tran
Insert into LOG_File(Name, Count, Processname) values ('Test1', #CountA,'#ProcessName')
end try
begin catch
rollback tran
SET #CountA =0
Insert into LOG_File(Name, Count, Processname) values ('Test1', #CountA,'#ProcessName')
SELECT ERROR_NUMBER() AS ErrorNumber,ERROR_MESSAGE() AS ErrorMessage
end catch
Select #CountA [TotalCount]

Related

I'm trying this scenario but it is not giving correct output

//*
WAP which takes deptno,dname as input and
insert that records into dept table.
if deptno is already existing,then increment
its value by 10 and again try inserting.The
insert is suposed to be tried 5 times and
falling all 5 times, print amessage saying
'Try later'
*//
Alter procedure sp_update (
#dno int,
#dname varchar(30)
)
as begin
declare #rowcount int
set #rowcount=##ROWCOUNT
if #rowcount<5
begin
if not exists (select dno from dept where #dno=dno)
begin
insert into dept (dno,dname) values (#dno,#dname)
end
else
begin
print 'Try later'
end
end
else
begin
set #dno=(select max(dno) from dept)
set #dno=#dno+10
insert into dept (dno,dname) values (#dno,#dname)
end
end
exec sp_update 10,'HR'
Please give the required output.
The #rowcount variable at the begining of the procedure is always <5, it's never going to enter the segment where "increment its value by 10 and again try inserting".
EDIT: In order to know how many times a user has executed the SP, you would have to insert into a log table, something like:
CREATE TABLE logSp (Username VARCHAR(200), ProcedureName VARCHAR(200), DateExecuted DATETIME)
And change the logic to this:
ALTER PROCEDURE sp_update (
#dno int,
#dname varchar(30)
)
AS
BEGIN
DECLARE #sp_name VARCHAR(200) = 'sp_update'
DECLARE #maxtimes VARCHAR(200) = 5
DECLARE #user VARCHAR(200) = SUSER_NAME()
DECLARE #times INT
SELECT #times = count(1)
FROM logSp
WHERE Username = #user
AND ProcedureName = #sp_name
IF #times = #maxtimes
BEGIN
PRINT 'Try Again'
RETURN
END
ELSE
BEGIN
INSERT INTO logSp values (#user, #sp_name, GETDATE())
END
IF NOT EXISTS (SELECT dno FROM dept WHERE #dno=dno)
BEGIN
INSERT INTO dept (dno,dname) VALUES (#dno,#dname)
END
ELSE
BEGIN
SET #dno=(SELECT MAX(dno) FROM dept)
SET #dno=#dno+10
INSERT INTO dept (dno,dname) VALUES (#dno,#dname)
END
END
Also, by setting the #dno variable to max(id) + 10 you guarantee that
the new deptno doesn't exist, yout don't have to try 5 times. If you want to try 5 times the INSERT you would have to use a while.

catching error in loop sql query

I have the below insert query which selects records from the OriginalData table where everything is nvarchar(max) and inserts it into the Temp table which has specific field definitions i.e MainAccount is INT.
I am doing a row by row insert because if there is a record in OriginalData table where the MainAccount value is 'Test' the it will obviously cause a conversion error and the insert will fail.
I want to be able to capture this error. There is a field on the originalData table called "error" which I want to populate. However I want this to run thru the entire table as oppose to fail on the first error and stop.
DECLARE #RowId INT
, #MaxRowId INT
Set #RowId = 1
Select #MaxRowId = 60
WHILE(#RowId <= #MaxRowId)
BEGIN
INSERT INTO [Temp] (ExtractSource, MainAccount,RecordLevel1Code, RecordLevel2Code, RecordTypeNo, TransDate, Amount, PeriodCode, CompanyCode)
SELECT ExtractSource, MainAccount,RecordLevel1Code, RecordLevel2Code,RecordTypeNo,TransDate, Amount, PeriodCode, DataAreaId
FROM [OriginalData]
WHERE RowId = #RowId
PRINT #RowId
SET #RowId = #RowId + 1
END
select * from [Temp]
You should use TRY..CATCH block:
WHILE(#RowId <= #MaxRowId)
BEGIN
BEGIN TRY
INSERT INTO [Temp] (ExtractSource, MainAccount,RecordLevel1Code,
RecordLevel2Code, RecordTypeNo, TransDate, Amount, PeriodCode, CompanyCode)
SELECT ExtractSource, MainAccount,RecordLevel1Code, RecordLevel2Code,
RecordTypeNo,TransDate, Amount, PeriodCode, DataAreaId
FROM [OriginalData]
WHERE RowId = #RowId;
PRINT #RowId;
END TRY
BEGIN CATCH
-- error handlingg
END CATCH
SET #RowId += 1;
END

Insert Query to insert multiple rows in a table via select and output clause. SQL Server 2008

I have a created a stored procedure (please ignore syntax errors)
alter proc usp_newServerDetails
(#appid int, #envid int, #serType varchar(20), #servName varchar(20))
as
declare #oTbl_sd table (ID int)
declare #outID1
declare #oTbl_cd table (ID int)
declare #outID2
begin Transaction
insert into server_details(envid, servertype, servername)
output inserted.serverid into #oTbl_sd(ID)
values(#envid, #serType, #servName)
select #outID1 = ID from #oTbl_sd
insert into configdetails(serverid, servertype, configpath, configtype)
output inserted.configid into #oTbl_cd(ID)
(select #outID1, cm.servertype, cm.configpath, cm.configtype
from configpthmaster cm
where cm.appid = #appid )
select #outID2 = ID from #oTbl_cd
insert into configkeydetails(confiid, keyname)
output inserted.Keyid into #oTbl_ckd(ID)
(select #outID2, cm.key
from configpthmaster cm
where cm.appid = #appid)
begin
commit
end
server_details table has an identity column ID with is auto-generated ie. #outID1 and first insert query inserts only 1 row.
configpthmaster table is not related to any other table directly and has 2 unique data rows, which I want to fetch to insert data into other tables, one by one during insertion.
The second insert query fetch data from configpthmaster table
and insert 2 rows in configdetails while generating (auto-generated) ID ie. #outID2.
It also has a FK mapped to server_details.
The problem is "#outID2" giving last inserted ID only (ie. if two id generated 100,101 i am getting 101) which eventually on 3rd insertion, inserting 2 rows with same id 101 only but i want the insertion should be linear. i.e one for 100 and other for 101.
If zero rows affected while insertion how to rollback the transaction?
How can I achieve these requirements? Please help.
Change your procedure like below,and try again.
ALTER PROCEDURE usp_newServerDetails(#appid int, #envid int,#serType varchar(20),#servName varchar(20))
AS
BEGIN
BEGIN TRY
DECLARE #Output TABLE (ID int,TableName VARCHAR(50),cmKey VARCHAR(50)) --table variable for keeping Inserted ID's
BEGIN TRAN
IF EXISTS ( SELECT 1 FROM configpthmaster cm WHERE cm.appid = #appid )
AND ( SELECT 1 FROM configkeydetails ck WHERE ck.appid = #appid ) --add a conditon to satisfy the valid insertions
BEGIN
INSERT INTO server_detials(envid,servertype,servername)
OUTPUT inserted.serverid,'server_detials',NULL INTO #Output(ID,TableName,cmKey )
VALUES(#envid ,#serType ,#servName)
INSERT INTO configdetails(serverid,servertype,configpath,configtype)
OUTPUT inserted.configid,'configdetails',cm.Key INTO #Output(ID,TableName,cmKey )
SELECT t.ID,cm.servertype,cm.configpath,cm.configtype
FROM configpthmaster cm
CROSS APPLY (SELECT ID FROM #Output WHERE TableName='server_detials')t
WHERE cm.appid = #appid
INSERT INTO configkeydetails(configId,keyname)
SELECT ID,cmKey FROM #Output
WHERE TableName='configdetails'
END
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK
END CATCH
END
Could you try this solution?
alter proc usp_newServerDetails(#appid int, #envid int,#serType varchar(20),#servName varchar(20))
as
declare #oTbl_sd table (ID int)
declare #outID1
declare #oTbl_cd table (ID int)
declare #outID2
begin Transaction
insert into server_detials(envid,servertype,servername)
output inserted.serverid into #oTbl_sd(ID)
values(#envid ,#serType ,#servName)
select #outID1 = ID from #oTbl_sd
insert into configdetails(serverid,servertype,configpath,configtype)
output inserted.configid into #oTbl_cd(ID)
(select #outID1 ,cm.servertype,cm.configpath,cm.configtype from configpthmaster cm where cm.appid = #appid )
select #outID2 = ID from #oTbl_cd
insert into configkeydetails(confiid,keyname)
output inserted.Keyid into #oTbl_ckd(ID)
(select isnull(replace(stuff((SELECT inserted.configid FOR xml path('')), 1, 1, ''), '&', '&'), '') ,cm.key, from configpthmaster cm where cm.appid = #appid )
begin
commit
end
I just added STUFF in your code.
The STUFF function inserts a string into another string.
Do take note that using STUFF drastically slows the processing time of the code.
for more information about STUFF

get error in sql stored procedure table type

i have this procedure and i want insert list of value into table,and i want check duplicate value and return this but i get this error
ERROR:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
ALTER PROCEDURE [dbo].[insertData]
#Value insertTbl READONLY,
#Result INT OUTPUT
AS
BEGIN
BEGIN TRY
IF (SELECT COUNT(Id)
FROM #Values
WHERE Id IN (SELECT Id
FROM [dbo].[username])) = 0
BEGIN
INSERT INTO dbo.username ( Id, NAME )
SELECT Id,
NAME
FROM #Values
WHERE Id NOT IN (SELECT Id
FROM [dbo].[username])
SELECT #Result = 101
END
ELSE
SELECT #Result = (
SELECT Id
FROM #Values
WHERE Id IN (SELECT Id
FROM [dbo].[username])
)
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
END CATCH
END
Please try this.
I have changed the return type to VARCHAR. That would return a CSV (e.g. 1,3,9...)
The other option is to return a result set. For this replace SELECT #Result = to INSERT INTO #Result...
Hope this helps.
ALTER PROCEDURE [dbo].[insertData]
#Value insertTbl READONLY,
#Result VARCHAR(500) OUTPUT
AS
BEGIN
BEGIN TRY
IF (SELECT COUNT(Id)
FROM #Values
WHERE Id IN (SELECT Id
FROM [dbo].[username])
) = 0
BEGIN
INSERT INTO dbo.username (Id,NAME)
SELECT Id,
NAME
FROM #Values
WHERE Id NOT IN (SELECT Id
FROM [dbo].[username])
SELECT #Result = 101
END
ELSE
BEGIN
SELECT #Result = (SELECT STUFF((SELECT ', ' + CAST(ID AS VARCHAR(5))
FROM #Values
WHERE Id IN (SELECT Id
FROM [dbo].[username])
ORDER BY Id FOR XML PATH('')),1,2,''))
END
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
END CATCH
END
Probably this line
SELECT #Result=(SELECT Id FROM #Values WHERE Id IN (SELECT Id FROM [dbo].[username]))
return more than one row. You should change the query to return only one value as below
SELECT #Result=(SELECT top 1 Id FROM #Values WHERE Id IN (SELECT Id FROM [dbo].[username]))
or you should change your logic. Alternative way is to use temprary table to return listo of ID. Try below solution
-- create temporary table before (!) creating procedure
create table #Resulttab
(
Result int
)
go
ALTER PROCEDURE [dbo].[insertData]
#Value insertTbl READONLY,
#Result INT OUTPUT -- in this solution I think you don't need this parameter
AS
BEGIN
BEGIN TRY
IF (SELECT COUNT(Id)
FROM #Values
WHERE Id IN (SELECT Id
FROM [dbo].[username])) = 0
BEGIN
INSERT INTO dbo.username ( Id, NAME )
SELECT Id,
NAME
FROM #Values
WHERE Id NOT IN (SELECT Id
FROM [dbo].[username])
SELECT #Result = 101
END
ELSE
insert into #Resulttab
SELECT Id
FROM #Values
WHERE Id IN (SELECT Id
FROM [dbo].[username])
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
END CATCH
END
go
You can use it that way:
-- create temporary table before run procedure
create table #Resulttab
(
Result int
)
-- run procedure with parameters
exec insertData ...
--after run procedure you can check list of your IDs
select Result from #Resulttab

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

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.