SQL Server stored procedure avoid cursor - sql-server

I have the following SQL Server stored procedure :
BEGIN TRAN
CREATE TABLE #TempTable (
SampleOrderID int,
SampleOrderNo varchar(512),
ChallanNoAndChallanDate varchar(MAX)
)
CREATE NONCLUSTERED INDEX #IX_Temp2_1 ON #TempTable(SampleOrderID)
DECLARE
#SQL as varchar(MAX)
SET #SQL=' SELECT SampleOrderID, SampleOrderNo FROM SampleOrder WHERE SampleOrderID IN (37808,37805,37767,37571,37745,37772,37843,37394,37909,37905,37903) '
INSERT INTO #TempTable (SampleOrderID, SampleOrderNo)
EXEC (#SQL)
DECLARE
#SampleOrderID as int,
#ChallanNoAndChallanDate as varchar(max)
DECLARE Cur_AB1 CURSOR GLOBAL FORWARD_ONLY KEYSET FOR
SELECT SampleOrderID FROM #TempTable
OPEN Cur_AB1
FETCH NEXT FROM Cur_AB1 INTO #SampleOrderID
WHILE(##Fetch_Status <> -1)
BEGIN--2
SET #ChallanNoAndChallanDate=''
SELECT #ChallanNoAndChallanDate= COALESCE(#ChallanNoAndChallanDate+ ',', '') + CONVERT(VARCHAR(12),ChallanDate,106)+':'+ChallanNo FROM Challan WHERE OrderID =#SampleOrderID AND OrderType=2
UPDATE #TempTable SET ChallanNoAndChallanDate=#ChallanNoAndChallanDate WHERE SampleOrderID=#SampleOrderID
FETCH NEXT FROM Cur_AB1 INTO #SampleOrderID
END--2
CLOSE Cur_AB1
DEALLOCATE Cur_AB1
SELECT * FROM #TempTable
DROP TABLE #TempTable
COMMIT TRAN
Output :
SamID SamNo ChallanNoAndDaet
37394 37394 ,31 May 2012:151592
37571 37571 ,31 May 2012:151580
37745 37745 ,31 May 2012:151582
37767 37767 ,30 May 2012:151507,31 May 2012:151576
37772 37772 ,31 May 2012:151587
37805 37805 ,31 May 2012:151574
37808 37808 ,31 May 2012:151573
37843 37843 ,31 May 2012:151588
37903 37903 ,31 May 2012:151597
37905 37905 ,31 May 2012:151596
37909 37909 ,31 May 2012:151593
It works successfully for small volume of data but When i try to execute it on a Large volume (i.e. more then 500,000 record) my C# interface throws the time out exception.
Can anyone help me edit my stored procedure to avoid the cursor?
Thanks for response.

I use this to avoid cursor in everywhere I need
DECLARE #num_rows int
DECLARE #cnt int
DECLARE #selected int
DECLARE #table1 TABLE (Id int not null primary key identity(1,1), col1 int )
INSERT into #table1 (col1) SELECT col1 FROM table2
SET #num_rows=##ROWCOUNT
SET #cnt=0
WHILE #cnt<#num_rows
BEGIN
SET #cnt=#cnt+1
SELECT
#selected=col1
FROM #table1
WHERE Id=#cnt
--do your stuff here--
END

I usually use something like the following:
SELECT #SampleOrderID = MIN (SampleOrderID) FROM #TempTable
WHILE #SampleOrderID IS NOT NULL
BEGIN
SET #ChallanNoAndChallanDate=''
SELECT #ChallanNoAndChallanDate= COALESCE(#ChallanNoAndChallanDate+ ',', '') + CONVERT(VARCHAR(12),ChallanDate,106)+':'+ChallanNo FROM Challan WHERE OrderID =#SampleOrderID AND OrderType=2
UPDATE #TempTable SET ChallanNoAndChallanDate=#ChallanNoAndChallanDate WHERE SampleOrderID=#SampleOrderID
SELECT #SampleOrderID = MIN (SampleOrderID) FROM #TempTable WHERE SampleOrderID > #SampleOrderID
END
This code would replace the cursor stuff you have.

Related

SQL server execute SP from sql table and update

The table below stores sql insert statements and I run those from a sp. I need to also add an insert to the last_run_dt column. I put the code together via existing stackoverflow questions. I need help implementing this in my code, any feedback will be helpful.
How can I update my code to update the last_run_dt column?
Table:
audit_sql_id audit_sql last_run_dt
1 select * from <<need to add last run_dt value>>
2 select * from <<need to add last run_dt value>>
Code:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
alter proc [dbo].[sp_sqlAudit]
#packagename as varchar(255)
as
begin
set nocount on;
if #packagename='SQL_DM_AUDIT'
begin
declare #queries table (audit_sql_id int identity(1,1),sqlscript varchar(max))
declare #str_query varchar(max);
declare #startloop int
declare #endloop int
insert into #queries
select audit_sql
from dw.dbo.audit_sql with(nolock)
select #endloop = max(audit_sql_id), #startloop = min(audit_sql_id)
from #queries
while #startloop < = #endloop
begin
select #str_query = sqlscript
from #queries
where audit_sql_id = #startloop
exec (#str_query)
set #startloop = #startloop + 1
end
end
end
I would suggest a slight refactor something like the below. There's no need to bring the entire list of sql statements into TemDB, just iterate over it and get each statement in turn. I would also always add a #debug parameter to print the sql instead if executing.
create or alter procedure dbo.sqlAudit
#packagename as varchar(255)
as
set nocount on;
declare #str_query varchar(max), #Id int
declare #AuditID table (Id int)
if #packagename='SQL_DM_AUDIT'
begin
insert into #AuditID (Id) /* Get list of IDs */
select audit_sql_id
from dw.dbo.audit_sql
while exists(select * from #AuditID) /* Continue while there are IDs in the list */
begin
select top (1) #Id=Id from #AuditID /* Get an ID */
select #str_query=audit_sql /* Get the sql for the ID */
from dw.dbo.audit_sql
where audit_sql_id=#Id
delete from #AuditID where Id=#Id /* Remove this ID from the list */
begin try
exec (#str_query)
if ##Error=0
begin
update dw.dbo.audit_sql set last_run_dt=GetDate() /* Update date for ID if run successful */
where audit_sql_id=#Id
end
end try
begin catch
/*handle error*/
end catch
end
end
go

how to dynamically find and replace the function text

I have 800+ functions in my database. I would need to modify their source databases dynamically and create snapshots.
example of the function:
create function [schema1].[funTest1] (#param1 varchar(50))
returns table as
return
(
select * from [curr_database1].[schema1].[funTest1](#param1)
union
select * from [curr_database2].[schema1].[funTest1](#param1)
)
I want to change the script as:
create or alter function [schema1].[funTest1] (#param1 varchar(50))
returns table as return
(
select * from [new_database2].[schema1].[funTest1](#param1)
union
select * from [new_database3].[schema1].[funTest1](#param1)
)
basically, I got all the functions script using the sys.syscomments. I'm looking for an option to find and replace the database dynamically to create the snapshots.
How can I get it? Thank you!
Here is the sample code that I have developed for sharing. All the database in the functions starts with the same text(for ex. "curr"). Please share your thoughts. Thanks in advance!
create or alter proc test_proc as
begin
set nocount on
-- this piece of code has the new databases
if object_id('tempdb..#dbNames') is not null drop table #dbNames
create table #dbNames (dbName varchar(1000), id int)
insert into #dbNames(dbName, id) values ('new_database2', 1),('new_database3', 2)
insert into #dbNames(dbName, id) values ('new_database8', 3),('new_database9', 4)
-- this one has the sample functions
if object_id('tempdb..#dbFunctions') is not null drop table #dbFunctions
create table #dbFunctions (funText nvarchar(max))
insert into #dbFunctions (funText) values('create function [schema1].[funTest1] (#param1 varchar(50))
returns table as
return
(
select * from [curr_database1].[schema1].[funTest1](#param1)
union
select * from [curr_database2].[schema1].[funTest1](#param1)
)'),
('create function [schema2].[funTest2] (#param1 varchar(50), #param2 varchar(100))
returns table as
return
(
select * from [curr_database4].[schema2].[funTest2](#param1, #param2)
union
select * from [curr_database5].[schema2].[funTest2](#param1, #param2)
)')
-- declare variables and assign value for #frmStr variable (for testing purposes)
declare #str nvarchar(max)
declare #dbName varchar(100)
declare #frmStr varchar(100) = '[curr_database1]'
-- get the total count of the databases and the functions to iterate and replace the string
declare #dbCnt int = (select count(id) from #dbNames)
declare #fnCnt int = (select count(*) from #dbFunctions)
while #dbCnt > 0
begin
set #dbname = (select dbname from #dbnames where id = #dbcnt)
while #fnCnt > 0
begin
-- this is where I would need to replace the code
select #str = replace(funText, #frmStr, #dbName) from #dbFunctions
select #str
set #fnCnt = #fnCnt - 1
end
set #dbCnt = #dbCnt - 1
end
end
Your actual goal isn't clear, but to answer the question you asked, you can use REPLACE functions in the query to syscomments that you used to get the code in the first place:
REPLACE(
REPLACE([FunctionTextColumn],'curr_database1','new_database2')
,'curr_database2','new_database3'
)

How to get and use the value returned by a stored procedure to a INSERT INTO... SELECT... statement

I am just new in SQL language and still studying it. I'm having hard time looking for answer on how can I use the stored procedure and insert value into a table.
I have this stored procedure:
CREATE PROCEDURE TestID
AS
SET NOCOUNT ON;
BEGIN
DECLARE #NewID VARCHAR(30),
#GenID INT,
#BrgyCode VARCHAR(5) = '23548'
SET #GenID = (SELECT TOP (1) NextID
FROM dbo.RandomIDs
WHERE IsUsed = 0
ORDER BY RowNumber)
SET #NewID = #BrgyCode + '-' + CAST(#GenID AS VARCHAR (30))
UPDATE dbo.RandomIDs
SET dbo.RandomIDs.IsUsed = 1
WHERE dbo.RandomIDs.NextID = #GenID
SELECT #NewID
END;
and what I'm trying to do is this:
INSERT INTO dbo.Residents([ResidentID], NewResidentID, [ResLogdate],
...
SELECT
[ResidentID],
EXEC TestID ,
[ResLogdate],
....
FROM
source.dbo.Resident;
There is a table dbo.RandomIDs containing random 6 digit non repeating numbers where I'm pulling out the value via the stored procedure and updating the IsUsed column of the table to 1. I'm transferring data from one database to another database and doing some processing on the data while transferring. Part of the processing is generating a new ID with the required format.
But I can't get it to work Sad I've been searching the net for hours now but I'm not getting the information that I need and that the reason for my writing. I hope someone could help me with this.
Thanks,
Darren
your question is little bit confusing, because you have not explained what you want to do. As i got your question, you want to fetch random id from randomids table and after performed some processing on nextid you want to insert it into resident table [newresidentid] and end of the procedure you fetch data from resident table. if i get anything wrong feel free to ask me.
your procedure solution is following.
CREATE PROCEDURE [TestId]
AS
SET NOCOUNT ON;
BEGIN
DECLARE #NEWID NVARCHAR(30)
DECLARE #GENID BIGINT
DECLARE #BRGYCODE VARCHAR(5) = '23548'
DECLARE #COUNT INTEGER
DECLARE #ERR NVARCHAR(20) = 'NO IDS IN RANDOM ID'
SET #COUNT = (SELECT COUNT(NEXTID) FROM RandomIds WHERE [IsUsed] = 0)
SET #GENID = (SELECT TOP(1) [NEXTID] FROM RandomIds WHERE [IsUsed] = 0 ORDER BY [ID] ASC)
--SELECT #GENID AS ID
IF #COUNT = 0
BEGIN
SELECT #ERR AS ERROR
END
ELSE
BEGIN
SET #NEWID = #BRGYCODE + '-' + CAST(#GENID AS varchar(30))
UPDATE RandomIds SET [IsUsed] = 1 WHERE [NextId] = #GENID
INSERT INTO Residents ([NewResidentId] , [ResLogDate] ) VALUES (#NEWID , GETDATE())
SELECT * FROM Residents
END
END
this procedure will fetch data from your randomids table and perform some processing on nextid than after it directs insert it into resident table and if you want to insert some data through user you can use parameter after declaring procedure name
E.G
CREATE PROCEDURE [TESTID]
#PARAM1 DATATYPE,
#PARAM2 DATATYPE
AS
BEGIN
END
I'm not convinced that your requirement is a good one but here is a way to do it.
Bear in mind that concurrent sessions will not be able to read your update until it is committed so you have to kind of "lock" the update so you will get a block until you're going to commit or rollback. This is rubbish for concurrency, but that's a side effect of this requirement.
declare #cap table ( capturedValue int);
declare #GENID int;
update top (1) RandomIds set IsUsed=1
output inserted.NextID into #cap
where IsUsed=0;
set #GENID =(select max( capturedValue) from #cap )
A better way would be to use an IDENTITY or SEQUENCE to solve your problem. This would leave gaps but help concurrency.

Trigger After Update for a Specific Value

Is it possible to create a trigger on SQL Server that will execute when a column value is updated to a specific value. How can I do this?
Try with following
CREATE TRIGGER trgAfterUpdate ON [dbo].[Employee_Test]
FOR UPDATE
AS
declare #empid int;
declare #empname varchar(100);
declare #empsal decimal(10,2);
declare #audit_action varchar(100);
select #empid=i.Emp_ID from inserted i;
select #empname=i.Emp_Name from inserted i;
select #empsal=i.Emp_Sal from inserted i;
if update(Emp_Name)
set #audit_action='Updated Record -- After Update Trigger.';
if update(Emp_Sal)
set #audit_action='Updated Record -- After Update Trigger.';
insert into Employee_Test_Audit(Emp_ID,Emp_Name,Emp_Sal,Audit_Action,Audit_Timestamp)
values(#empid,#empname,#empsal,#audit_action,getdate());
PRINT 'AFTER UPDATE Trigger fired.'
GO
I write a simple trigger and hoping that it helps you.
CREATE TRIGGER [FUND].[TRU_FUND_FUND_PRICE] ON [FUND].[FUND_PRICE]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #PRICE_ID BIGINT
DECLARE #UNIT_PRICE DECIMAL(18,7)
DECLARE #UNIT_PRICE_BEFORE_UPDATE DECIMAL(18,7)
DECLARE #TOTAL_SHARE_BEFORE_UPDATE DECIMAL(18,7)
DECLARE #TOTAL_SHARE DECIMAL(18,7)
DECLARE #RECORD_STATUS_BEFORE_UPDATE CHAR(1)
DECLARE #RECORD_STATUS CHAR(1)
SELECT #UNIT_PRICE_BEFORE_UPDATE=UNIT_PRICE , #TOTAL_SHARE_BEFORE_UPDATE = TOTAL_SHARE, #RECORD_STATUS_BEFORE_UPDATE =RECORD_STATUS FROM DELETED
SELECT #PRICE_ID = PRICE_ID,#UNIT_PRICE=UNIT_PRICE,#TOTAL_SHARE=TOTAL_SHARE,#RECORD_STATUS=RECORD_STATUS FROM INSERTED
--You can compare to specific values instead of deleted data
IF #UNIT_PRICE=#UNIT_PRICE_BEFORE_UPDATE AND #TOTAL_SHARE =#TOTAL_SHARE_BEFORE_UPDATE AND #RECORD_STATUS_BEFORE_UPDATE = #RECORD_STATUS
BEGIN
RETURN
END
INSERT INTO FUND.FUND_PRICE_LOG (
[PRICE_ID],
[FUND_ID],
[PRICE_DATE],
[UNIT_PRICE],
[UPDATE_USER_CODE],
[UPDATE_PROG_CODE],
[UPDATE_DATE],
[OPERATION],
[RECORD_STATUS],
[TOTAL_SHARE],
[LIMIT_CHANGE_AMOUNT]
)
SELECT [PRICE_ID], [FUND_ID], [PRICE_DATE],
[UNIT_PRICE],
[UPDATE_USER_CODE],
[UPDATE_PROG_CODE],
GETDATE(),
'U',
[RECORD_STATUS],
[TOTAL_SHARE] ,
([TOTAL_SHARE] - #TOTAL_SHARE_BEFORE_UPDATE)
FROM INSERTED
END
GO

Invalid Object Name #tablename at second call of #tablename sql server 2005

When I execute the code :
declare #result int
exec recostcos #result
select #result
I get 'Msg 208, Level 16, State 0, Procedure RecostCOS, Line 138
Invalid object name '#rLB'.'
The strange thing is The first reference to #rLB did not produce any error but the second reference on line 138 produces the error above.
Please find below the code for recostcos :
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER proc [dbo].[RecostCOS](#result int output)
as
BEGIN TRY
BEGIN TRANSACTION
SET NOCOUNT ON
--variables
declare #itemName varchar(50)
declare #invoiceNo varchar(50)
declare #customerName varchar(100)
declare #recordedBy varchar(100)
declare #transDate datetime
declare #supCode varchar(50)
declare #decemQty1 decimal(18,6)
declare #decemQty2 decimal(18,6)
declare #decemQty3 decimal(18,6)
declare #recostQty decimal(18,6)
declare #count int
declare #innerCount int
declare #outputCode int
declare #outputCounter int
declare #recId int
declare #innerId int
create table #supcodes(supcode varchar(50),item_name varchar(50),
qty decimal(18,6),id int identity(1,1))
create table #recostList(invoice_no varchar(50),qty decimal(18,6), id int identity(1,1))
create table #rLB(invc_no varchar(50),qty decimal(18,6), id int identity(1,1))
--check for supplyCode needing reprocessing
select #count=count(*) from someTable where recosted=0;
if(#count<=0)
begin
commit
set #result=0
return #result
end
--
insert into #supcodes(supcode,item_name,qty)
select supply_code,item_name,quantity from someTable
where recosted=0
--recost each supply code needing recosting
while(#count>0)
begin
select top 1 #supCode=supcode,#itemName=item_name,#decemQty1=qty,#recId=id
from #supcodes
select #decemQty2=sum(current_qty) from someTable2 where item_name=#itemName
select #decemQty3=quantity from someTable3 where item_name=#itemName
if(#decemQty2 is null)
set #decemQty2=0
if(#decemQty3 is null)
set #decemQty3=0
--debug
if(#decemQty2<>#decemQty3)
begin
--check if there is a log of this error
select #count=count(*) from someTable4 where error_code=1 and
item_name=#itemName and supply_code=#supCode and resolved=0
if(#count<=0)
--error in stock quantities (this must be resolved b4 any recosting)
insert into someTable4(supply_code,item_name,error_code,error_source,error_detail)
values(#supCode,#itemName,1,'re-costing','Mismatch in stock quantities in stock state and supply codes tables')
end
else if(#decemQty1>#decemQty2)
begin
--check if there is a log of this error
select #count=count(*) from someTable4 where error_code=2 and
item_name=#itemName and supply_code=#supCode and resolved=0
--insufficient stock for recosting
if(#count<=0)
insert into someTable4(supply_code,item_name,error_code,error_source,error_detail)
values(#supCode,#itemName,2,'re-costing','insufficient stock for recosting')
end
else
begin
--recost cost of sales of item involved
--get list of invoices of item to be recosted
set #recostQty=#decemQty1
insert into #recostList(invoice_no,qty)
select invoice_no,quantity from someTable5
where supply_code=#supCode and item_name=#itemName
insert into #rLB(invc_no,qty) select invoice_no,qty from #recostList
--delete cost of sales relating to supcod and item in stock account
select #innerCount=count(*) from #recostList
while(#innerCount>0)
begin
select top 1 #invoiceNo=invoice_no,#decemQty2=qty,#innerId=id
from #recostList
delete someTable6 where description
like 'sales of '+ltrim(rtrim(convert(varchar(20),#decemQty2)))+'%'+
#itemName+'%'+#invoiceNo
delete someTable5 where supply_code=#supCode and item_name=#itemName
and quantity=#decemQty2
delete #recostList where id=#innerId
set #innerCount=#innerCount-1
end
--call costByFIFO to recost item
select #innerCount=count(*) from #rLB
set #outputCounter=#innerCount
while(#innerCount>0)
begin
select top 1 #invoiceNo=invc_no,#decemQty2=qty,#innerId=id
from #rLB
select #customerName=customer_name, #transDate=trans_date,
#recordedBy=recorded_by from someTable7 where invoice_no=#invoiceNo
exec #outputCode=costByFIFO #itemName,#invoiceNo,#customerName,
#decemQty2,#transDate,#recordedBy
--ensure each invoice is costed or reverse entire process
if(#outputCounter=0)
begin
set #outputCounter=#outputCounter-1
end
else
begin
set #result=3 --failed to cost all invoices involved
rollback
end
delete #rLB where id=#innerId
set #innerCount=#innerCount-1
end
--outputCounter must be 0 to indicate all invoices where costed
if(#outputCounter<>0)
begin
set #result=3 --failed to cost all invoices involved
rollback
end
else
begin
update someTable set recosted=1 where supply_code=#supCode
and item_name=#itemName
end
end
delete #supcodes where id=#recId
set #count=#count-1
end
SET NOCOUNT OFF
drop table #supcodes
drop table #recostList
drop table #rLB
set #result=0
COMMIT
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() ErrorNBR, ERROR_SEVERITY() Severity,
ERROR_LINE () ErrorLine, ERROR_MESSAGE() Msg
set #result=2 --unexpected error occured
ROLLBACK
END CATCH

Resources