views:

54

answers:

2

It's not hard to find developers who think cursors are gauche but I am wondering how to solve the following problem without one:

Let's say I have a proc called uspStudentDelete that takes as a parameter @StudentID.

uspStudentDelete applies a bunch of cascading soft delete logic, marking a flag on tables like "classes", "grades", and so on as inactive. uspStudentDelete is well vetted and has worked for some time.

What would be the best way to run uspStudentDelete on the results of a query (e.g. select studentid from students where ... ) in *TSQL*?

+3  A: 

That's exactly what cursors are intended for:

declare c cursor local for <your query here>
declare @ID int
open c
fetch next from c into @id
while @@fetch_status = 0 
    begin
    exec uspStudentDelete @id
    fetch next from c into @id
    end
close c
deallocate c

Most people who rail against cursors think you should do this in a proper client, like a C# desktop application.

Andomar
That is exactly what you should not do. This will kill performance.
HLGEM
@HLGem: The OP says his current proc is "well vetted", so I think reusing well vetted code is more important than performance. And hopefully he's not deleting 10,000 students every day :)
Andomar
@HLGem: @Andomar is correct in both counts - first, this procedure has existed for many of our development cycles as a crucial part of the process and that we are talking of a handful of records at most, nowhere near even the 50+ mark. My dilemma with what you're saying is that you are talking about copy/pasting existing SET based logic to exist in two places which would introduce another problem: maintainability.
David in Dakota
@HLGem: I'm with @Andomar on this one and do the same thing occasionally...
gbn
it depends on the complexity of the procedure, if it consists only of a bunch of `UPDATE..WHERE ID=@ParamID` (soft deletes) then rewrite it to work on a set of IDs (one ID and or many, no need for two versions). If it has a bunch of crazy IF logic or calls many other procedures, then loop over it. I'd try not to use an actual cursor, there are many ways to loop on a set of rows without an actual cursor.
KM
+3  A: 

The best solution is to write a set-based proc to handle the delete (try running this through a cursor to delete 10,000 records and you'll see why) or to add the set-based code to the current proc with a parameter to tell you wheter to run the set-based or single record part of the proc (this at least keeps it together for maintenance purposes).

In SQL Server 2008 you can use a table variable as an input variable. If you rewrite the proc to be set-based, you can have the same logic and run it no matter if the proc sends in one record or ten thousand. You may need to have a batch process in there to avoid deleting millions of records in one go though and locking up the tables for hours. Of course if you do this you will also need to adjust how the currect sp is being called.

HLGEM
+1 amen, brother! A little set-based refactoring can help easily avoid cursors here
marc_s