Question : SQL Code needs changing or optimising - using Cursor at present

Hi

I have inherited the following code at a new role - written by a developer many moons ago.
Issues with it are that it takes a large amount of time to return and a lot of resources on the current woefully underspecced server.
The database is in 2k compatibility mode on a SQL 2005 server.

I would appreciate a look at the code and a re-write of the code to optimise it
Or even a better way to do it differently.
Im busy on other matters so dont have time to do this process myself hence why im looking for a complete answer that I can just run.
Id also be interested in what people think is wrong with this ...

Please dont offer a part solution.

I can offer any other information needed.
i have attached a file with the code - which shows the contents of the view that is called in the sproc.

Thank you

Mo
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
CREATE procedure [dbo].[xPLT_WTAAbsenceSpanNPD]
(
    @p_BeginDate        datetime,
    @p_EndDate          datetime	
)
as

/*

	This Version - 1.0
	
	Ver 1.0 -	Created per U0158 in support of Annualised Hours
	23/06/2009
		
*/

declare
 @w_RetCode	        int,
 @w_RetStatus		int,
 @w_RetMsg          varchar(255),
 @r_PrevWorkDay     datetime,
 @r_NextWorkDay     datetime,
 @w_EmployeeID		char(6),
 @w_EmployeeCode	int,
 @w_NPDDay			datetime

select 
 @w_RetCode = 0

declare emp_cur CURSOR for
 select
  eiv.EmployeeID,
  tsd.EMPLOYEECODE,
  tsd.ADMINISTRATIONDATE
  from 
   dbo.xPLT_EmpInfo_Vw eiv with (nolock)
    inner join dbo.TIMESUMMARYDAY tsd with (nolock)
     on eiv.EmployeeCode = tsd.EmployeeCode
  where
   tsd.ADMINISTRATIONDATE between @p_BeginDate and @p_EndDate and
   tsd.DAILYPATTERNCODE = 68206 and -- non-production 
   tsd.STANDARDMINSINDAILYPATTERN = 0
   and exists (select
                * 
                from 
                 dbo.TIMESUMMARYDAY with (nolock) 
                where EmployeeCode = tsd.EmployeeCode and 
                AdministrationDate between dateadd(wk, -3, @p_BeginDate) and dateadd(wk, +3, @p_EndDate) and 
                STANDARDMINSINDAILYPATTERN > 0 and 
                STANDARDMINUTESACHIEVED = 0) -- only check if possibly relevant full day absences within the 'window'
  order by
   eiv.EmployeeID,
   tsd.ADMINISTRATIONDATE
 for read only

create table #tmp9001
(
 EmployeeID			char(6),
 NPDDay				datetime,
 PrevWorkDayAbs		datetime,
 NextWorkDayAbs		datetime
)

open emp_cur
fetch next from emp_cur into @w_EmployeeID, @w_EmployeeCode, @w_NPDDay

if @@fetch_status <> 0
 goto end_ret -- no qualifying data to process

while @@fetch_status = 0
 begin -- cursor loop

  -- determine previous & next work days
  exec @w_RetStatus = dbo.xPLT_PrevNextWorkDay @w_EmployeeId, @w_NPDDay, 0, @r_PrevWorkDay output, @r_NextWorkDay output
  if @w_RetStatus <> 0
   begin
    select @w_RetMsg = 'xPLT_WTAAbsenceSpanNPD : call to xPLT_PrevNextWorkDay failed with return code : ' + convert(char(1), @w_RetStatus)
    raiserror ('%s',18,1,@w_RetMsg)
    select @w_RetCode = 1
    goto end_ret
   end

  -- test for full days absence on previous & next work days
  if 
   (
    select 
     count(*)
    from 
     dbo.TIMESUMMARYDAY tsd with (nolock)
    where 
     tsd.EMPLOYEECODE = @w_EmployeeCode and
     (tsd.ADMINISTRATIONDATE = @r_PrevWorkDay or tsd.ADMINISTRATIONDATE = @r_NextWorkDay) and
     tsd.STANDARDMINSINDAILYPATTERN > 0 and
     tsd.STANDARDMINUTESACHIEVED = 0
   ) = 2
   begin
    insert into #tmp9001(EmployeeID, NPDDay, PrevWorkDayAbs, NextWorkDayAbs)
     values (@w_EmployeeID, @w_NPDDay, @r_PrevWorkDay, @r_NextWorkDay)
   end 
     
    
  fetch next from emp_cur into @w_EmployeeID, @w_EmployeeCode, @w_NPDDay
  
 end -- cursor loop


-- return resultset
select 
 t1.*,
 eiv.Surname,
 eiv.Forenames,
 eiv.Centre,
 eiv.Team
 from 
  #tmp9001 t1
   inner join dbo.xPLT_EmpInfo_Vw eiv
    on t1.EmployeeID = eiv.EmployeeID COLLATE Latin1_General_CI_AS
 order by 
  t1.EmployeeID,
  t1.NPDDay

end_ret:
close emp_cur
deallocate emp_cur
drop table #tmp9001
return @w_RetCode


GO
 
view called in sproc
 

Answer : SQL Code needs changing or optimising - using Cursor at present

mooriginal

After looking a your situation, it is clear that since you have no way of archiving older (historical)
data, your performance will suffer as time goes on and more records are added.

I ran this code with 5 Million TIMESUMMARYDAY records and 15 Thousand EMPLOYEE records
(Randomly Generated) on an old SQL 2005 system and returned 11k results in less than 50 sec.

Have yet to do any optimization, or attemped to get rid of the cursor.  The idea is to create 2
temp table and use them to create the result set.  Next is to create these tables on the database
(do not yet have any indexes)  and truncate, fill, and index the data and then run the query.
This should improve the performance.    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
--NEW CURSOR RESULTS

declare
 @w_RetCode	        int,
 @w_RetStatus		int,

 @w_RetMsg          varchar(255),
 @r_PrevWorkDay     datetime,
 @r_NextWorkDay     datetime,
 @w_EmployeeID		char(6),
 @w_EmployeeCode	int,
 @w_NPDDay			datetime,

 @p_BeginDate        datetime,
 @p_EndDate          datetime



select @w_RetCode = 0
select @p_BeginDate = '08/01/10'
select @p_EndDate = '08/10/10'



DECLARE
	 @TMP_BeginDate        datetime,
	 @TMP_EndDate          datetime

SET @TMP_BeginDate = DATEADD(wk, -3, @p_BeginDate)
SET @TMP_EndDate = DATEADD(wk, +3, @p_EndDate)

DECLARE @TSD TABLE ( CNT[int] NOT NULL,
	[EMPLOYEECODE] [int] NOT NULL,
	[ADMINISTRATIONDATE] [datetime] NULL,
	[DAILYPATTERNCODE] [int] NULL,
	[STANDARDMINSINDAILYPATTERN] [smallint] NULL,
	[STANDARDMINUTESACHIEVED] [smallint] NOT NULL )

INSERT INTO @TSD
	SELECT	CNT
			,EMPLOYEECODE
			,ADMINISTRATIONDATE
			,DAILYPATTERNCODE
			,STANDARDMINSINDAILYPATTERN
			,STANDARDMINUTESACHIEVED
	FROM dbo.TIMESUMMARYDAY
	WHERE DAILYPATTERNCODE = 68206 AND
    ADMINISTRATIONDATE 	BETWEEN @TMP_BeginDate AND @TMP_EndDate

DELETE FROM  @TSD 
WHERE (STANDARDMINSINDAILYPATTERN = 0 AND STANDARDMINUTESACHIEVED <> 0)


DECLARE @EMP TABLE (
	[EmployeeId] [varchar](15) NOT NULL,
	[EmployeeCode] [int] NULL,
	[Surname] [varchar](40) NULL,
	[Forenames] [varchar](40) NULL,
	[Team] [varchar](16) NULL,
	[Centre] [varchar](5) NULL )


INSERT INTO @EMP
	SELECT	EmployeeId
			,EmployeeCode
			,Surname
			,Forenames
			,Centre
			,Team
	FROM dbo.xPLT_EmpInfo 
	WHERE active > 0

DECLARE emp_cur CURSOR for
	
 SELECT
  eiv.EmployeeID,
  tsd.EMPLOYEECODE,
  tsd.ADMINISTRATIONDATE
  FROM @EMP eiv 
  inner join @TSD tsd
  on eiv.EmployeeCode = tsd.EmployeeCode
  WHERE
   tsd.ADMINISTRATIONDATE between @p_BeginDate and @p_EndDate and
   tsd.STANDARDMINSINDAILYPATTERN = 0



CREATE TABLE #tmp9001
(
 EmployeeID			char(6),
 NPDDay				datetime,
 PrevWorkDayAbs		datetime,
 NextWorkDayAbs		datetime
)

open emp_cur
fetch next from emp_cur into @w_EmployeeID, @w_EmployeeCode, @w_NPDDay

if @@fetch_status <> 0
 goto end_ret -- no qualifying data to process

while @@fetch_status = 0
 begin -- cursor loop

  -- determine previous & next work days

  exec @w_RetStatus = dbo.xPLT_PrevNextWorkDay @w_EmployeeId, @w_NPDDay, 0, @r_PrevWorkDay output, @r_NextWorkDay output
  if @w_RetStatus <> 0
   begin
    select @w_RetMsg = 'xPLT_WTAAbsenceSpanNPD : call to xPLT_PrevNextWorkDay failed with return code : ' + convert(char(1), @w_RetStatus)
    raiserror ('%s',18,1,@w_RetMsg)
    select @w_RetCode = 1
    goto end_ret
   end

  -- test for full days absence on previous & next work days
  if 
   (
    select 
     count(*)
    from 
     @TSD tsd
    where 
     tsd.EMPLOYEECODE = @w_EmployeeCode and
     (tsd.ADMINISTRATIONDATE = @r_PrevWorkDay or tsd.ADMINISTRATIONDATE = @r_NextWorkDay) and
     tsd.STANDARDMINSINDAILYPATTERN > 0 and
     tsd.STANDARDMINUTESACHIEVED = 0
   ) = 2
   begin
    insert into #tmp9001(EmployeeID, NPDDay, PrevWorkDayAbs, NextWorkDayAbs)
     values (@w_EmployeeID, @w_NPDDay, @r_PrevWorkDay, @r_NextWorkDay)
   end 

  fetch next from emp_cur into @w_EmployeeID, @w_EmployeeCode, @w_NPDDay

 end -- cursor loop


-- return resultset

select 
 t1.*,
 eiv.Surname,
 eiv.Forenames,
 eiv.Centre,
 eiv.Team
 from 
  #tmp9001 t1
   inner join dbo.xPLT_EmpInfo_Vw eiv
    on t1.EmployeeID = eiv.EmployeeID COLLATE Latin1_General_CI_AS
 order by 
  t1.EmployeeID,
  t1.NPDDay


--select * from  #tmp9001 t1

end_ret:


close emp_cur
deallocate emp_cur
drop table #tmp9001

--return @w_RetCode
select @w_RetCode

GO
Random Solutions  
 
programming4us programming4us