Cursor not looping correctly - sql-server

I have a cursor that I want to loop through a staging table, and merge each record into another table.
I cant get this cursor just to loop through the records and return a count.
DECLARE #curCatalogID int
DECLARE #curNomenclature varchar(200)
DECLARE #curMainCategory varchar(200)
DECLARE #curSubCategory varchar(200)
DECLARE #curManufacturer varchar(200)
DECLARE #curModelNo varchar(200)
DECLARE #curPrice varchar(200)
DECLARE #curProductDesc varchar(2000)
DECLARE #curImage varchar(200)
DECLARE #curPDFName varchar(200)
DECLARE #curInventory varchar(200)
DECLARE #curBatchID int
DECLARE #curAuditID int
DECLARE #nCnt int
SET #nCnt = 0
DECLARE import_loop CURSOR FOR
SELECT * FROM tblCatalogStaging
OPEN import_loop
FETCH NEXT FROM import_loop
INTO #curCatalogID,
#curNomenclature,
#curMainCategory,
#curSubCategory,
#curManufacturer,
#curModelNo,
#curPrice,
#curProductDesc,
#curImage,
#curPDFName,
#curInventory,
#curBatchID,
#curAuditID
WHILE ##FETCH_STATUS = 0
BEGIN
SET #nCnt = ##ROWCOUNT;
FETCH NEXT FROM import_loop
INTO #curCatalogID,
#curNomenclature,
#curMainCategory,
#curSubCategory,
#curManufacturer,
#curModelNo,
#curPrice,
#curProductDesc,
#curImage,
#curPDFName,
#curInventory,
#curBatchID,
#curAuditID
END
CLOSE import_loop
DEALLOCATE import_loop
SELECT #nCnt
It should just return 1 value of 2036 ( number of rows in the staging table ) but im getting back like 2036 rows affected, 4072 rows affected, etc etc

I'm not so sure ##ROWCOUNT is meant to be used inside a CURSOR.
You might have better luck with:
DECLARE #nCnt INT
SET #nCnt = 0
...
SET #nCnt = #nCnt + 1;
Note: A TRIGGER is probably the right place to be doing this validation on row insert/update. Unless you really only want the validation to happen sometimes. (Also, it's SQL errors you'll be raising, not exceptions)

i m not sure if its as simple as this but do you just want:
select count (*) FROM tblCatalogStaging

##Rowcount provides the number of rows that were affected by the last statemenet to be executed. I do not think this is what you want, and I would agree with ebyrob about just using a counter variable.
But that is if you really need to do this in a cursor. AS marc_s suggest, you may want to rework your actual procedure so that it is using sets. If that is not possible and you need to, as you said in your response, deal with exceptions in your loop, you may want to look at Phil Factors recent article on that topic.

Related

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.

FOR DO in SQL Server

Just curios can I do this in SQL Server
FOR
SELECT columns
FROM table_name
DO
---do some logic
--proc call
ENDFOR;
which means for every record from first select do something in DO block.
This perfectly works for Ingres DB, but not sure if it will work with SQL Server, or I should use only cursor?
This Syntax is not supported in SQL Server's T-SQL. But - as you mention yourself in your question - there is CURSOR:
--Some *mockup* data
DECLARE #tbl TABLE(ID INT IDENTITY, SomeData VARCHAR(100));
INSERT INTO #tbl VALUES('Row 1'),('Row 2'),('Row 3');
--Declare variables to puffer all row's values
DECLARE #WorkingVariable VARCHAR(100);
--never forget the `ORDER BY` if sort order matters!
DECLARE cur CURSOR FOR SELECT SomeData FROM #tbl ORDER BY ID;
OPEN cur;
--a first fetch outside of the loop
FETCH NEXT FROM cur INTO #WorkingVariable
--loop until nothing more to read
WHILE ##FETCH_STATUS=0
BEGIN
--Do whatever you like with the value(s) read into your variable(s).
SELECT #WorkingVariable;
--Pick the next value
FETCH NEXT FROM cur INTO #WorkingVariable
END
--Don't forget to get rid of the used resources
CLOSE cur;
DEALLOCATE cur;
But please keep in mind, that using a loop (however it is coded) is procedural thinking and against the principles of set-based thinking. There are very rare situations! where a CURSOR (or any other kind of loop) is the right choice...
SQL Server doesn't do any behind-the-scenes work for building WHILE loops.
One way to do something like this in SQL Server would look like:
declare #indexTable table (fieldIndex bigint identity(1,1), field (whatever your type of field is))
insert into #indexTable(field)
select field
from table_name
declare #pointer bigint = 1
declare #maxIndexValue bigint = (select max(fieldIndex) from #indexTable)
declare #fieldValue (fieldtype)
while #pointer <= #maxIndexValue
BEGIN
select #fieldValue = field from #indexTable where fieldIndex = #pointer
---do some logic
--proc call
set #pointer = #pointer + 1
END
This is an alternative to using a cursor to loop over your rowset.

how to use declare variable in select query in stored procedure using sql server

Hello I want to concate two things one is string and other is int variable. Now, these thing I want to store in one variable and use that variable in select query as a into type to create a temptable in stored procedure using sql server.
Here is my query
USE [FlightExamSoftware]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- For Storing Question in Temp table
-- EXEC [GetQuestionListPerSubjectRatioWise] 1,11
ALTER PROCEDURE [dbo].[GetQuestionListPerSubjectRatioWise]
#SubjectID INT,
#NumberOfQue INT,
#UserID int
AS
BEGIN
DECLARE #strQuery VARCHAR(MAX);
DECLARE #PerChapQue INT;
DECLARE #tempTable VARCHAR(MAX) = 'tempTestUser' + #UserID;
SELECT #PerChapQue = COUNT(appQueID)/#NumberOfQue FROM tblQuestion WHERE appQueSubID=#SubjectID
SELECT COUNT(appQueID)/#PerChapQue ChapwiseQue
,CASE WHEN COUNT(appQueID)>=#PerChapQue THEN COUNT(appQueID)/#PerChapQue ELSE 1 END ChapWiseQuePlusOne
,appQueChapID into #tempTable
FROM tblQuestion
WHERE appQueSubID=#SubjectID
GROUP BY appQueChapID
END
Now, I am talking about these line
DECLARE #tempTable VARCHAR(MAX) = 'tempTestUser' + #UserID;
In these line two things are concate one is string and other is int. And store in varchar variable.
And use in following select query i.e.
SELECT COUNT(appQueID)/#PerChapQue ChapwiseQue
,CASE WHEN COUNT(appQueID)>=#PerChapQue THEN COUNT(appQueID)/#PerChapQue ELSE 1 END ChapWiseQuePlusOne
,appQueChapID into #tempTable
FROM tblQuestion
WHERE appQueSubID=#SubjectID
GROUP BY appQueChapID
END
Now, in these query I want to create a temptable named #tempTable.
But, in these line it showing error i.e. Incorrect syntax near '#tempTable'.
Confuse that where is the syntax is wrong.
Thank You.
There are a number of things wrong with your code.
When concatenating an int to a string, you must first cast the int to varchar. Otherwise, SQL Server will try to implicitly convert the string to int, that will result with an error.
So this: DECLARE #tempTable VARCHAR(MAX) = 'tempTestUser' + #UserID; should become this:
DECLARE #tempTable VARCHAR(MAX) = 'tempTestUser' + CAST(#UserID AS VARCHAR(11)); (you need 11 chars to be able to fit the minimum value of int: -2,147,483,648)
You can't use select...into with a table variable.
You can only use it for actual tables (temporary or regular).
your #tempTable isn't even a table variable (not that it will help with a select...into).
Even if you would use select...into the correct way, unless you are going to use a global temporary table (and that doesn't come without it's risks), Unless your stored procedure uses this temporary table later on, it will be useless, since temporary tables are bound to scope.
Taking all of that into consideration I'm not sure what output you are actually looking for. If you could edit your question to include the desired output of your stored procedure as well as some sample data as DDL+DML, it would be easier to help you write better code.
Hope this Dynamic Query helps you:
Try like this:
USE [FlightExamSoftware]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- For Storing Question in Temp table
-- EXEC [GetQuestionListPerSubjectRatioWise] 1,11
ALTER PROCEDURE [dbo].[GetQuestionListPerSubjectRatioWise]
#SubjectID INT,
#NumberOfQue INT,
#UserID int
AS
BEGIN
DECLARE #strQuery VARCHAR(MAX);
DECLARE #PerChapQue INT;
DECLARE #tempTable VARCHAR(MAX) = 'tempTestUser' + CAST(#UserID AS VARCHAR);
SELECT #PerChapQue = COUNT(appQueID)/#NumberOfQue FROM tblQuestion WHERE appQueSubID=#SubjectID
SET #strQuery='
SELECT COUNT(appQueID)/'+CAST(#PerChapQue AS VARCHAR)+' ChapwiseQue
,CASE WHEN COUNT(appQueID)>='+CAST(#PerChapQue AS VARCHAR)+' THEN COUNT(appQueID)/'+CAST(#PerChapQue AS VARCHAR)+' ELSE 1 END ChapWiseQuePlusOne
,appQueChapID
INTO '+#tempTable+'
FROM tblQuestion
WHERE appQueSubID='+CAST(#SubjectID AS VARCHAR)+'
GROUP BY appQueChapID
/*.................................
And you have to use the temp table inside the String only
.................................*/
'
EXEC (#strQuery)
END

performance problem sql server 2005 update sentence

I have a table "OFICIAL3" with 500k rows. and 30 columns. and table INSIS with 150k rows and 20 columns.
OFICIAL3.NUMERO_TITULO has an index.
INSIS.NumeroDocumento has an index too.
update sentence take long time. this process will take 9 hours in my machine
my machine is a core 2 duo 2.GHZ and 2GB RAM
ALTER PROCEDURE [dbo].[CompletarDatos] AS
declare #cantidad int;
declare #CONTADOR int;
declare #NRO_TITULO VARCHAR(600);
declare #POYECTO VARCHAR(200);
DECLARE #I_PROYECTO VARCHAR(500);
DECLARE #I_AREA_INT VARCHAR(500);
SET NOCOUNT ON
BEGIN
SET #cantidad =(select count(*) from OFICIAL3)
SET #CONTADOR=1
declare CURSORITO cursor for
select NUMERO_TITULO from OFICIAL3
open CURSORITO
fetch next from CURSORITO
into #NRO_TITULO
while ##fetch_status = 0
begin
SET #CONTADOR=#CONTADOR+1
PRINT 'ROW='+CONVERT(NVARCHAR(30),#CONTADOR)+' NRO TITULO='+#NRO_TITULO
SET #I_PROYECTO = (SELECT PROYECTO FROM INSIS WHERE NumeroDocumento=#NRO_TITULO)
SET #I_AREA_INT = (SELECT I_AREA_INTERVENCION FROM INSIS WHERE NumeroDocumento=#NRO_TITULO)
UPDATE OFICIAL3 SET PROYECT=#I_PROYECTO , COD_AREA=#I_AREA_INT WHERE NUMERO_TITULO=#NRO_TITULO
fetch next from CURSORITO into #NRO_TITULO
end
-- cerramos el cursor
close CURSORITO
deallocate CURSORITO
END
Assuming OFICIAL4 is a typo, this should work as a single update:
UPDATE o
SET PROYECT = i.PROYECTO,
COD_AREA = i.I_AREA_INTERVENCION
FROM OFICIAL3 o
INNER JOIN
INSIS i
ON o.NUMERO_TITULO = i.NumeroDocumento
As others have commented, an approach that avoids the CURSOR is vastly preferable from a performance point of view. Another thought is that a covering index on `INSIS (NumeroDocumento, PROYECTO, I_AREA_INTERVENCION) would speed things up further for this query.
Is there any way you can do this without a cursor? Removing the iteration should help it considerably.

t-sql replace on text field

I have hit a classic problem of needing to do a string replace on a text field in an sql 2000 database. This could either be an update over a whole column or a single field I'm not fussy.
I have found a few examples of how to use updatetext to achieve it but they tend to be in stored procedures, does anyone know of a similar thing that is wrapped into a function so I can use it like I would usually use Replace(). The problem with the Replace() function for anyone who isn't aware is that it doesn't support text fields.
Edit: I realised I could probably get away with varchar(8000) so have swapped the fields to this type which fixes the issue. I never found a true solution.
Here is the sample query to update table with text column using REPLACE function. Hope this is useful for you.
UPDATE <Table> set textcolumn=
REPLACE(SUBSTRING(textcolumn,1,DATALENGTH(textcolumn)),'findtext','replacetext')
WHERE <Condition>
I am afraid you cannot do it within a function
When you try to declare a function like:
create function dbo.textReplace(
#inText as text)
returns text
as
begin
return 'a' -- just dummy code
end
You will get the following error:
The text data type is invalid for return values.
In other words you could not write a simple equivalent of REPLACE function for the text data type
This is my code snippet for this scenario:
DECLARE #oldtext varchar(1000)
DECLARE #newtext varchar(1000)
DECLARE #textlen int
DECLARE #ptr binary(16)
DECLARE #pos int
DECLARE #id uniqueidentifier
SET #oldtext = 'oldtext'
SET #newtext = 'newtext'
SET #textlen = LEN(#oldtext)
DECLARE mycursor CURSOR LOCAL FAST_FORWARD
FOR
SELECT [UniqueID]
,TEXTPTR([Text])
,CHARINDEX(#oldtext, [Text]) - 1
FROM [dbo].[myTable]
WHERE [Text] LIKE '%' + #oldtext +'%'
OPEN mycursor
FETCH NEXT FROM mycursor into #id, #ptr, #pos
WHILE ##fetch_status = 0
BEGIN
UPDATETEXT [dbo].[myTable].Text #ptr #pos #textlen #newtext
FETCH NEXT FROM mycursor into #id, #ptr, #pos
END
CLOSE mycursor
DEALLOCATE mycursor
You would have to cast the text field to a varchar(8000) or nvarchar(4000) if you are replacing over an ntext field.
MyField = REPLACE(CAST(MyField as VARCHAR(4000)), "string1", "string2")
This ofcourse will only work if you can guarantee the content in the field is <= 4000/8000 characters in length.
You can also use the SUBSTRING() function, which returns a varchar when passed a text value.
For instance:
MyVarchar = SUBSTRING(myTextField, 1, DATALENGTH(myTextField))
If you're populating a varchar with a specific length, you can truncate to fit:
MyVarchar100 = SUBSTRING(myTextField, 1, 100)

Resources