June 29, 2007 at 9:47 am
We are migrating a db from Sybase SQLAny to SS2K5. One SQLAny developer used the the following technique integrate a "WhereClause" string parameter into a procedure cursor. Here's the gist:
CREATE PROCEDURE DBO.CancelProcess @WhereClause varchar(max) as....
declare @Select1 varchar(max)
set @Select1=' SELECT Projects.ProjectID, Projects.TargetStartDate, Projects.TargetFinishDate, Projects.StatusCode
from dbo.Projects WHERE ' + @WhereClause
begin
declare curCancel insensitive scroll cursor for @Select1
.....
The error we get is:
Incorrect syntax near '@Select1'
I've tried a few techniques that I know, like declaring the cursor in a Execute statement:
set @Select1= 'SET @curCancel = CURSOR scroll FOR SELECT Projects.ProjectID, Projects.TargetStartDate from DBO.Projects WHERE ' + @WhereClause ;
Execute @Select1
I'm not getting anywhere. What am I missing?
Takauma
June 29, 2007 at 7:38 pm
You are going to have to put the whole cursos declaration into a vaiable and execute it
DECLARE @SQL varchar(8000);
SET @SQL = 'DECLARE MY_CURSOR Cursor FOR Select ID, Event From Sometable where ' + @WhereClause;
exec (@SQL);
July 2, 2007 at 12:04 am
This isn't Oracle... stop using cursors.
--Jeff Moden
Change is inevitable... Change for the better is not.
July 2, 2007 at 1:45 am
What are you trying to do with the cursor? Post the entire code if possible, there's probably a better way.
Gail Shaw
Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability
July 2, 2007 at 2:04 am
Thank you. Down with cursors
Cheers,CrispinI can't die, there are too many people who still have to meet me!It's not a bug, SQL just misunderstood me!
July 2, 2007 at 9:26 am
I read "Down with cursors" Fine, but these individuals are suggesting any alternatives. How about some suggestions?
Takauma
July 3, 2007 at 12:06 am
Sure. Please post the entire piece of code, especially what's inside the cursor loop and I'll give it a shot.
Without knowing what you're doing with the cursor, it's near impossible to suggest an alternative.
Gail Shaw
Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability
July 3, 2007 at 6:23 am
Like a couple of folks have asked already... post the rest of the code that uses the cursor and describe what the code is supposed to do. We're pretty good at writing high speed alternatives to cursors, but not reading minds
--Jeff Moden
Change is inevitable... Change for the better is not.
July 3, 2007 at 10:11 am
Okay okay! Here is the code, which now works since I applied the solutionm that Pieter Huiberts provided in the initial reply to this thread. I normally try to glean it down to the specific issue at hand, this time I left all the code in.
Remember, the question now is: "What are the T-SQL alternatives to the typical declare/open cursor method?"
Thanks, Joel
CREATE PROCEDURE "DBO"."ProjectCancelProcess"(@WhereClause varchar(max), @NewStatus varchar(20)) as
begin
-- This procedure will perform additional Post Processing based on the settings in the StatusRules table.
declare @Select1 varchar(max) ,
@ProjectID integer,
@TargetStartDate date,
@TargetFinishDate date,
@Amount decimal(19,4),
@CancelAmount decimal(19,4),
@OldStatus varchar(20)
--
if @NewStatus = 'Cancelled'
begin
set @CancelAmount=0
set @Select1=' SELECT Projects.ProjectID, Projects.TargetStartDate, Projects.TargetFinishDate, Projects.StatusCode
from DBO.Projects join DBO.OwnerInformation on Projects.OwnerCode = OwnerInformation.OwnerCode WHERE ' + @WhereClause
begin
declare curCancel insensitive scroll cursor for @Select1
open curCancel
fetch next from curCancel into @ProjectID , @TargetStartDate, @TargetFinishDate , @OldStatus
While @@fetch_status = 0
begin
if exists(select 1 from DBO.StatusRules where FromStatusCode = @OldStatus and ToStatusCode = @NewStatus and
PostProcess = 1)
begin
--
-- insert the zero Forecast record if it does not exist..
select top 1 @Amount = Amount from DBO.ProjectForecast where ProjectID = @ProjectID order by
EnteredDate desc
if isnull(@Amount,1) != 0
insert into DBO.ProjectForecast( ProjectID,Amount,TargetStartDate,TargetEndDate,SystemCode) values(
@ProjectID,@CancelAmount,@TargetStartDate,@TargetFinishDate,@NewStatus)
end
--
-- Lock all the Project records on Cancel
--
update
DBO.ProjectChangeID
set ProjectChangeID.Lockflag = 1,ProjectChangeID.StatusCode = 'Cancelled'
from DBO.ProjectChangeID join
Projects on Projects.ProjectID = ProjectChangeID.ProjectID join
OwnerInformation on Projects.OwnerCode = OwnerInformation.OwnerCode where
ProjectChangeID.LockFlag != 1 and ProjectChangeID.StatusCode != 'Cancelled' and
Projects.ProjectID = @ProjectID
--also do the header
-- but only if the Project is not already locked
update DBO.Projects
set Projects.Lockflag = 1
from DBO.Projects join
DBO.ProjectChangeID on ProjectChangeID.ProjectID = Projects.ProjectID join
DBO.OwnerInformation on OwnerInformation.OwnerCode = Projects.OwnerCode where Projects.LockFlag != 1 and Projects.ProjectID = @ProjectID
end
--
fetch next from curCancel into @ProjectID , @TargetStartDate, @TargetFinishDate , @OldStatus
end
close curCancel
deallocate curCancel
end
end
Takauma
July 3, 2007 at 9:59 pm
Working on this but need some info... what column names will you use in the @WhereClause variable for this proc only? Please give an example of what you will pass in the variable...
Also remember... my request was to also post a purpose for the code...
-- This procedure will perform additional Post Processing based on the settings in the StatusRules table.
... just doesn't get it (WHAT kind of additional post processing???)... fortunately, I understand what the overall code is doing so you don't need to go there.
Don't forget about what I asked for in the first paragraph, please...
--Jeff Moden
Change is inevitable... Change for the better is not.
July 3, 2007 at 11:16 pm
Never mind about the @WhereClause... was going to try to make it so you didn't need the dynamic SQL, but I can't wait for it...
One of the common methods for cursor replacement is the use of a temp table as I've done below... but most folks make the horrible mistake of keeping the rest of the looping code and that's no better than a cursor (sometimes, it's worse). If you create a temp table to hold a full result set, use it with set-based code... not more WHILE loops...
While we're at it... you might want to adopt some readability and formatting standards which should include documentation standards.
Obviously, I've NOT tested this code because I just don't have your tables and data... but it should be pretty close... READ the comments because I think the original code had a couple of unnessary joins (unless they're just being used to prove that someone owned the projects... ie. NOT NULL owership)... I also don't think you need the 0 amount detectors in the code... just because a project has no cost associated with it, doesn't mean that you shouldn't be able to cancel it... but I kept it in the code because that's the way you wrote it.
CREATE PROCEDURE dbo.ProjectCancelProcess /********************************************************************************************************************** Purpose: This code accepts the criteria to be used in a WHERE clause and a new status. If the new status is 'Cancelled', the proc assigns the 'Cancelled' status to the ProjectForecast (zeros out amount as well), the Project, and the ProjectChangeID tables as well as setting the Lockflag.
Revisions: Rev 01 - 07/04/2007 - Rewritten to NOT use cursors or loops **********************************************************************************************************************/ --===== Define the input parameters ( @pWhereClause VARCHAR(MAX), @pNewStatus VARCHAR(20)) ) AS
--===== If the new status is not cancelled, nothing to do here... exit early... IF @pNewStatus <> 'Cancelled' RETURN
--===== Declare local variables DECLARE @SQLInsert VARCHAR(MAX)
--===== Create a temp table to hold common data to replace the cursor CREATE TABLE #Common ( ProjectID INT PRIMARY KEY, Amount DECIMAL(19,4), TargetStartDate DATE, TargetEndDate DATE, OwnerCode VARCHAR(100), --Make this match the data type of OwnerCode because not enough info to figure it out. SystemCode VARCHAR(20) )
--===== Insert cancellation rows (Amount = 0) into the temp table for each project found by the input criteria (@pWhereClause) -- This requires that the old status and new status meet the requirements laid out in the rules table. -- The ProjectID will be used for the other updates, as well. SET @SQLInsert = ' INSERT INTO #Common (ProjectID ,Amount ,TargetStartDate ,TargetEndDate ,OwnerCode , SystemCode) SELECT p.ProjectID, 0 AS Amount ,p.TargetStartDate ,p.TargetFinishDate ,p.OwnerCode, ''' + @pNewStatus + ''' FROM dbo.Projects p, dbo.ProjectForecast pf, (--==== Derived table "maxpf" finds latest entry date for each project SELECT ProjectID, MAX(EnteredDate) AS MaxEnteredDate FROM dbo.ProjectForecast ) maxpf, dbo.StatusRules sr WHERE p.ProjectID = pf.ProjectID AND pf.ProjectID = maxpf.ProjectID AND pf.EnteredDate = maxpf.MaxEnteredDate AND p.StatusCode = sr.FromStatusCode AND sr.ToStatusCode = ''' + @pNewStatus + ''' AND sr.PostProcess = 1 AND (pf.Amount <> 0 OR pf.Amount IS NULL) AND ' + @pWhereClause
EXEC (@SqlInsert)
--===== Insert the new forecast rows for cancelled projects INSERT INTO dbo.ProjectForecast (ProjectID, Amount, TargetStartDate, TargetFinishDate, SystemCode) SELECT ProjectID, Amount, TargetStartDate, TargetFinishDate, SystemCode FROM #Common
--===== Lock the project rows on cancel where not already locked UPDATE pc SET pc.Lockflag = 1, pc.StatusCode = c.SystemCode FROM dbo.ProjectChangeID pc, #Common c, dbo.OwnerInformation o -- Is this really needed??? WHERE pc.ProjectID = c.ProjectID AND c.OwnerCode = o.OwnerCode -- Is this really needed??? AND pc.Lockflag <> 1 AND pc.StatusCode <> c.SystemCode
--===== Lock the headers, too, where not already locked UPDATE p SET Lockflag = 1, StatusCode = c.SystemCode --Added this 'cause I thought it was necessary FROM dbo.Projects p, #Common c, dbo.OwnerInformation o -- Is this really needed??? WHERE p.ProjectID = c.ProjectID AND c.OwnerCode = o.OwnerCode -- Is this really needed??? AND p.Lockflag <> 1 AND p.StatusCode <> c.SystemCode
RETURN
--Jeff Moden
Change is inevitable... Change for the better is not.
July 17, 2007 at 10:13 pm
Kinda busted a hump trying to help you get rid of the cursor on this one, Joel... any feedback?
--Jeff Moden
Change is inevitable... Change for the better is not.
July 17, 2007 at 11:49 pm
Sorry for the delay Jeff:
I had thought my reply was already posted, but that most have been re some e other issue.
The SP that spawned my original post was eventually handled by another person in my group, so I have yet to apply the solution you laid out.
However... I did study it in detail. Many thanks!
Feedback: It really more like "d'oh!". We use this technique with statement level triggers whenever it fits, (in Sybase SQLA anyway), updates and inserts using joins with the "deleted" and/or "inserted" recordsets. Elegant.
I suppose that I was so blind to it, shows just how "iterative" we can get with procedures. I gotta take a step back now and then!
Joel
Takauma
July 18, 2007 at 7:58 pm
LOL... yeup... been in that same spot once or twice myself. Anyway, thanks for getting back to me.
--Jeff Moden
Change is inevitable... Change for the better is not.
Viewing 14 posts - 1 through 13 (of 13 total)
You must be logged in to reply to this topic. Login to reply