So I've spent quite some time around Stack Overflow looking to make this bit of SQL that will populate a field in a view the amount of work days a project has been late.
The view joins data from two local tables on MS SQL Server (2008), and I've already written the stored procedure to calculate the work days in between two days, called by:
EXECUTE spWorkDaysLate '01/01/20XX', '01/02/20XX'
However I am not sure how to loop through each record in the view, as this needs to be done for every entry in the table. Pertinent fields in my table are startDate, endDate, and I would populate the final field (daysLate) with the result from the stored procedure above, using endDate and startDate as parameters. I found some recommendations online for using the cursor command to loop through the table, this would be the SQL I would want to run:
DECLARE #MyCursor CURSOR;
DECLARE #MyField int;
SET #MyField = 'daysLate' /* Do need to do this, or is the an argument I pass? */
BEGIN
SET #MyCursor = CURSOR FOR
select top 1000 daysLate from dbo.vQualityControl
OPEN #MyCursor
FETCH NEXT FROM #MyCursor
INTO #MyField
WHILE ##FETCH_STATUS = 0
BEGIN
/*
YOUR ALGORITHM GOES HERE
*/
FETCH NEXT FROM #MyCursor
INTO #MyField
END;
CLOSE #MyCursor ;
DEALLOCATE #MyCursor;
END;
I'm fairly new to SQL and so I know the pseudo code that I want to run is something like a for each loop, which in my mind I have looking like:
WHILE ##FETCH_STATUS = 0
BEGIN
/* Bad code */
daysLate.Value = EXECUTE spWorkDaysLate #startDate, #endDate;
FETCH NEXT FROM #MyCursor
INTO #MyField
END;
I know thats syntactically wrong, so what should I place in the loop so that the field 'daysLate' gets populated with the result of the called stored procedure?
Did you ever try FUNCTION, you can calculate the work days in between two days in your FUNCTION.
SELECT *, dbo.fn_WorkDaysLate (startDate, endDate) AS DaysLate FROM dbo.vQualityControl
--OR
UPDATE A
SET A.DaysLate = dbo.fn_WorkDaysLate (A.startDate, A.endDate)
FROM dbo.vQualityControl A
I am currently using a sql cursor to look up a table to update another table. I have a table which contains a lot of phrases. I want update another table to set 1 if any of those phrases falls into any of the column in the update table. I am using cursor and char to look for the phrase. The cursor is taking long time and I'm just wondering if I could use anything else instead of the cursor. Thanks. I'm using sql server and here's the code
declare #word varchar(max)
declare #aCursor cursor for
SELECT col from table
open acursor
fetch next from acursor into #word
while ##fetch_status=0
begin
SET #word = '' + #word + ''
UPDATE updatetable
SET updatecol = 'y'
FROM updatetable u, tableb b
WHERE u.id = b.id AND (CHARINDEX(#word, u.name) > 0 OR CHARINDEX(#word, u.city) >
fetch next from acursor into #word
end
close acursor
deallocate acursor
Take a look at: http://weblogs.sqlteam.com/jeffs/archive/2008/06/05/sql-server-cursor-removal.aspx that should get you way on your way. I, however, have come full cricle on the issue, for individual row manipulation, cursors ARE the way to go, performance is about the same as other methods and readability is 10x better than the other methods making maintaining the code a lot easier.
However, I don't have enough detail it seems to understand why you can't solve this with an update statement.
In T-SQL, when iterating results from a cursor, it seems to be common practice to repeat the FETCH statement before the WHILE loop. The below example from Microsoft:
DECLARE Employee_Cursor CURSOR FOR
SELECT EmployeeID, Title FROM AdventureWorks2012.HumanResources.Employee
WHERE JobTitle = 'Marketing Specialist';
OPEN Employee_Cursor;
FETCH NEXT FROM Employee_Cursor;
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM Employee_Cursor;
END;
CLOSE Employee_Cursor;
DEALLOCATE Employee_Cursor;
GO
(Notice how FETCH NEXT FROM Employee_Cursor; appears twice.)
If the FETCH selects into a long list of variables, then we have a large duplicated statement which is both ugly and of course, "non-DRY" code.
I'm not aware of a post-condition control-of-flow T-SQL statement so it seems I'd have to resort to a WHILE(TRUE) and then BREAK when ##FETCH_STATUS is not zero. This feels clunky to me.
What other options do I have?
There's a good structure posted online by Chris Oldwood which does it quite elegantly:
DECLARE #done bit = 0
WHILE (#done = 0)
BEGIN
-- Get the next author.
FETCH NEXT FROM authors_cursor
INTO #au_id, #au_fname, #au_lname
IF (##FETCH_STATUS <> 0)
BEGIN
SET #done = 1
CONTINUE
END
--
-- stuff done here with inner cursor elided
--
END
This is what I've resorted to (oh the shame of it):
WHILE (1=1)
BEGIN
FETCH NEXT FROM C1 INTO
#foo,
#bar,
#bufar,
#fubar,
#bah,
#fu,
#foobar,
#another,
#column,
#in,
#the,
#long,
#list,
#of,
#variables,
#used,
#to,
#retrieve,
#all,
#values,
#for,
#conversion
IF (##FETCH_STATUS <> 0)
BEGIN
BREAK
END
-- Use the variables here
END
CLOSE C1
DEALLOCATE C1
You can see why I posted a question. I don't like how the control of flow is hidden in an if statement when it should be in the while.
The first Fetch shouldn't be a Fetch next, just a fetch.
Then you're not repeating yourself.
I'd spend more effort getting rid of the cursor, and less on DRY dogma, (but if it really matters, you could use a GOTO :) - Sorry, M. Dijkstra)
GOTO Dry
WHILE ##FETCH_STATUS = 0
BEGIN
--- stuff here
Dry:
FETCH NEXT FROM Employee_Cursor;
END;
Here is my humble contribution. Single FETCH statement, no GOTO, no BREAK, no CONTINUE.
-- Sample table
DROP TABLE IF EXISTS #tblEmployee;
CREATE TABLE #tblEmployee(ID int, Title varchar(100));
INSERT INTO #tblEmployee VALUES (1, 'First One'), (2, 'Second Two'), (3, 'Third Three'), (3, '4th Four');
-- Cursor with one FETCH statement
DECLARE #bEOF bit=0, #sTitle varchar(200), #nID int;
DECLARE cur CURSOR LOCAL FOR SELECT ID, Title FROM #tblEmployee;
OPEN cur;
WHILE #bEOF=0
BEGIN
FETCH NEXT FROM cur INTO #nID, #sTitle;
IF ##FETCH_STATUS<>0
SET #bEOF=1;
ELSE
BEGIN
PRINT Str(#nID)+'. '+#sTitle;
END;
END;
CLOSE cur;
DEALLOCATE cur;
-- Cleanup
DROP TABLE IF EXISTS #tblEmployee;
It is obvious that a cursor is the pointer to the current row in the recordset. But mere pointing isn't gonna make sense unless it can be used. Here comes the Fetch statement into the scene. This takes data from the recordset, stores it in the variable(s) provided. so if you remove the first fetch statement the while loop won't work as there is not "FETCHED" record for manipulation, if you remove the last fetch statement, the "while" will not loop-through.
So it is necessary to have both the fetch statement to loop-through the complete recordset.
Simply said you can't... that's just how most where statements in SQL work. You need to get the first line before the loop and then do it again in the while statement.
The better question how to get rid of the cursor and try to solve your query without it.
Here I got a scenario: When I press a button in client application (developed in Delphi) a stored procedure is activated. The stored procedure first declares a cursor for a select statement which returns two columns-BankID and BankCategoryID.Then I need to fetch each row inside the cursor into a record and check for the BankCategoryID and return a resultset according to the BankCategoryID like:
CASE WHEN fetched_record.BankCategoryID=1 THEN
SELECT STATEMENT1 WHEN fetched_record.BankCategoryID=2 THEN
SELECT STATEMENT2 and so on...
and then I return the result set retrieved from any of the above cases to my client application. Is thi possible?
Perhaps you'd want to use an IF as a control statement within your cursor?
WHILE ##FETCH_STATUS = 0
BEGIN
IF #BankCategoryID=1
BEGIN
SELECT Baz From Bat;
DECLARE #Spam bit;
SELECT #Spam = 0;
END
IF #BankCategoryID=2
BEGIN
SELECT Foo FROM Bar;
END
FETCH NEXT FROM MyCursor INTO #BankID, #BankCategory
END
Here's a sample TSQL cursor. You'd be loading your two column values into 2 variables: #BankID and #BankCategoryID. i.e. FETCH NEXT FROM MyCursor INTO #BankID, #BankCategoryID
Aside: I'm wondering if this could be done all without a cursor? In either case, the above should work for you in implementing more TSQL statements in each iteration of your cursor.
I want to reference the nth row of the #temptable (at the second SQL comment is below). What expression will allow me to do so?
DECLARE #counter INT
SET #counter = 0
WHILE (#counter<count(#temptable))
--#temptable has one column and 0 or more rows
BEGIN
DECLARE #variab INT
EXEC #variab = get_next_ticket 3906, 'n', 1
INSERT INTO Student_Course_List
SELECT #student_id,
-- nth result set row in #temptable, where n is #count+1
#variab
SET #counter = #counter +1
END
Cursor (will this work?):
for record in (select id from #temptable) loop
--For statements, use record.id
end loop;
Normally in a relational database like SQL Server, you prefer to do set operations. So it would be best to simply have INSERT INTO tbl SOMECOMPLEXQUERY even with very complex queries. This is far preferable to row processing. In a complex system, using a cursor should be relatively rare.
In your case, it would appear that the get_next_ticket procedure performs some significant logic which is not able to be done in a set-oriented fashion. If you cannot perform it's function in an alternative set-oriented way, then you would use a CURSOR.
You would declare a CURSOR on your set SELECT whatever FROM #temptable, OPEN it, FETCH from the cursor into variables for each column and then use them in the insert.
Instead of using a while loop (with a counter like you are doing) to iterate the table you should use a cursor
Syntax would be:
DECLARE #id int
DECLARE c cursor for select id from #temptable
begin
open c
fetch next from c into #id
WHILE (##FETCH_STATUS = 0)
BEGIN
--Do stuff here
fetch next from c into #id
END
close c
deallocate c
end