I have designed a script to get the inspector performance score which is based on different factors. Inspector is awarded grades based on their performance score. Script runs over night as a SQL job and updates all the inspectors (over 6500 inspectors) grades.
We are checking last 90 days progress but many inspectors who have done no work in last 90 days are getting full marks. To avoid this situation we have decided to look at last 90 days and if the number of reports is zero go back another 90 days for that inspector.
i.e. If out 6500 inspectors lets say 250 has done no job then script needs to go back another 90 days for those 250 inspectors and see if they have any work.
This could have been implemented in cursors very easily but i can't use cursor as it is taking too long as discussed here select query in Cursor taking too long
What are the other option? Should i write a function which will first check if there is any work been done in last 90 days for one inspector if not then go back another 90 days. But for doing this i would till need cursor?
ADDED
I have tried setting dates in temp table as mentioned by #Raj but it is taking too much time. This is a same query which took so long while using cursor. Other stats are running fine and i think something to do with query.
Requirements:
Number of visits for each inspectors where visits has uploaded document (1 or 2 or 13)
Tables:
Inspectors: InspectorID
InspectionScope: ScopeID, InspectorID (FK)
Visits: VisitID, VisitDate ScopeID (FK)
VisitsDoc: DocID, DocType, VisitID (FK)
DECLARE
#DateFrom90 date, #DateTo date, #DateFrom180 date, #DateFrom date;
SELECT #DateTo = CAST(GETDATE() AS DATE)
,#DateFrom90 = CAST(GETDATE() - 90 AS DATE)
,#DateFrom180 = CAST(GETDATE() - 180 AS DATE)
DECLARE #Inspectors TABLE (
InspectorID int,
InspectorGrade int,
DateFrom date,
DateTo date
);
insert into #inspectors (
InspectorID ,
InspectorGrade,
DateFrom ,
DateTo
)
select
tmp.InspectorID , tmp.InspectorGrade
,case when tmp.VisitWithReport = 0 then #DateFrom180 else #DateFrom90 end StartDate
,#DateTo EndDate
from
(
select
i.InspectorID , i.InspectorGrade
,VisitWithReport = (select COUNT(v.visitid) from visits v
inner join InspectionScope s on s.ScopeID = v.ScopeID
where v.ReportStandard not in (0,9) and v.VisitType = 1
and v.VisitDate BETWEEN #DateFrom90 and #DateTo
and s.InspectorID = i.InspectorID)
from inspectors i
)tmp;
--select * from #Inspectors
SELECT i.InspectorID , i.InspectorGrade
,TotalVisitsWithAtLeastOneReport = (select COUNT(distinct v.visitID) from Visits v
inner join InspectionScope s on s.ScopeID = v.ScopeID
inner join VisitDocs vd on vd.VisitID = v.VisitID
where vd.DocType IN (1,2,13) and s.InspectorID = i.InspectorID
and v.VisitDate BETWEEN i.DateFrom and i.DateTo
)
from #Inspectors i
You can identify the last job/work date first before applying any logic. Like, you can store InspectorID and LastWorkDay in a temp table (assuming LastWorkDay will be available in some table). Then based on LastWorkDay you can decide how many days you have to go back - 90 or 180. This will be another field (StartDate) in temp table which can be derived based on LastWorkDay column.
Related
I need help updating a column in a table based on a count of matching records in another table.
I've got 3 tables:
[EventDescriptions]
EventID, Description, StartDateTime
[EventEntries]
EntryID, EmployeeKey, EventID, Priority
[EventWinners]
WinnerID, EventID, EmployeeKey
When winners are drawn, I need to update the Priority column in the EventEntries table only for events that are in the future from today and where rows are found for an employee in the EventWinners table 90 days in the past from today. The Priority column gives people who haven't won an event higher chance of winning the next event, Priority 1 vs Priority 2 or 3.
Set Priority = 1 where EmployeeKey is not found in EventWinners where StartDateTime is for events no more than 90 days in the past from today.
Set Priority = 2 where EmployeeKey is found only 1 time in EventWinners where StartDateTime for events is no more than 90 days in the past from today.
Set Priority = 3 where EmployeeKey is found >= 2 in EventWinners where StartDateTime for events is no more than 90 days in the past from today
Use a CTE or subquery to get the winner counts by EmployeeKey for recent events. Next, join this CTE with EventEntries and filter EventEntries to future events only. You'll now have enough information in context to set Priority according to your rules.
--!!! Please backup your data before running the update, or do it as a transaction and test the result before committing. !!!
WITH [recent-event-winner-counts] AS (
SELECT [EmployeeKey], COUNT(*) AS [Occurrences]
FROM [EventWinners] AS [w]
INNER JOIN [EventDescriptions] AS [d]
ON [w].[EventID] = [d].[EventID]
WHERE [StartDateTime] BETWEEN DATEADD(DAY, -90, GETDATE()) AND GETDATE()
GROUP BY [EmployeeKey]
)
UPDATE [e]; -- <- remove this semicolon when you're ready to run this
SET Priority = CASE
WHEN [Occurrences] IS NULL THEN 1
WHEN [Occurrences] = 1 THEN 2
WHEN [Occurrences] >= 2 THEN 3
ELSE Priority -- leave unchanged
END
FROM [EventEntries] AS [e]
INNER JOIN [EventDescriptions] AS [d]
ON [e].[EventID] = [d].[EventID]
-- left join as we don't care about EmployeeKeys exclusively in EventWinners
LEFT JOIN [recent-event-winner-counts] AS [r]
ON [e].[EmployeeKey] = [r].[EmployeeKey]
WHERE [d].[StartDateTime] > GETDATE(); -- future events only
The table structure:
StaffingRecords
PersonnelId int
GroupId int
StaffingStartDateTime datetime
StaffingEndDateTime datetime
How can I get a list of staffing records, given a date and a group id that employees belong to, where the count of present employees fell below a threshold, say, 3, at any minute of the day?
The way my brain works, I would call a stored proc repeatedly with each minute of the day, but of course this would be horribly inefficient:
SELECT COUNT(PersonnelId)
FROM DailyRosters
WHERE GroupId=#GroupId
AND StaffingStartTime <= #TimeParam
AND StaffingEndTime > #TimeParam
AND COUNT(GroupId) < 3
GROUP BY GroupId
HAVING COUNT(PersonnelId) < 3
Edit: If it helps to refine the question, employees may come and go throughout the day. Personnel may have a staffing record from 0800 - 0815, and another from 1000 - 1045, for example.
Here is a solution where I find all of the distinct start and end times, and then query to see how many other people are clocked in at the time. Everytime the answer is less than 4, you know you are understaffed at that time, and presumably until the NEXT start time.
with meaningfulDtms(meaningfulTime, timeType, group_id)
as
(
select distinct StaffingStartTime , 'start' as timeType, group_id
from DailyRosters
union
select distinct StaffingEndTime , 'end' as timeType, group_id
from DailyRosters
)
select COUNT(*), meaningfulDtms.group_id, meaningfulDtms.meaningfulTime
from DailyRosters dr
inner join meaningfulDtms on dr.group_id = meaningfulDtms.group_id
and (
(dr.StaffingStartTime < meaningfulDtms.meaningfulTime
and dr.StaffingEndTime >= meaningfulDtms.meaningfulTime
and meaningfulDtms.timeType = 'start')
OR
(dr.StaffingStartTime <= meaningfulDtms.meaningfulTime
and dr.StaffingEndTime > meaningfulDtms.meaningfulTime
and meaningfulDtms.timeType = 'end')
)
group by meaningfulDtms.group_id, meaningfulDtms.meaningfulTime
having COUNT(*) < 4
Create a table with all minutes in the day with dt at PK
It will have 1440 rows
this will not give you count of zero - no staff
select allMiuntes.dt, worktime.grpID, count(distinct(worktime.personID))
from allMinutes
join worktime
on allMiuntes.dt > worktime.start
and allMiuntes.dt < worktime.end
group by allMiuntes.dt, worktime.grpID
having count(distinct(worktime.personID)) < 3
for times with zero I think the best way is a master of grpID
but I am not sure about this one
select allMiuntes.dt, grpMaster.grpID, count(distinct(worktime.personID))
from grpMaster
cross join allMinutes
left join worktime
on allMiuntes.dt > worktime.start
and allMiuntes.dt < worktime.end
and worktime.grpID = grpMaster.grpID
group by allMiuntes.dt, grpMaster.grpID
having count(distinct(worktime.personID)) < 3
Long time stalker, first time poster (and SQL beginner). My question is similar to this one SQL to find time elapsed from multiple overlapping intervals, except I'm able to use CTE, UDFs etc and am looking for more detail.
On a piece of large scale equipment I have a record of all faults that arise. Faults can arise on different sub-components of the system, some may take it offline completely (complete outage = yes), while others do not (complete outage = no). Faults can overlap in time, and may not have end times if the fault has not yet been repaired.
Outage_ID StartDateTime EndDateTime CompleteOutage
1 07:00 3-Jul-13 08:55 3-Jul13 Yes
2 08:30 3-Jul-13 10:00 4-Jul13 No
3 12:00 4-Jul-13 No
4 12:30 4-Jul13 12:35 4-Jul-13 No
1 |---------|
2 |---------|
3 |--------------------------------------------------------------
4 |---|
I need to be able to work out for a user defined time period, how long the total system is fully functional (no faults), how long its degraded (one or more non-complete outages) and how long inoperable (one or more complete outages). I also need to be able to work out for any given time period which faults were on the system. I was thinking of creating a "Stage Change" table anytime a fault is opened or closed, but I am stuck on the best way to do this - any help on this or better solutions would be appreciated!
This isn't a complete solution (I leave that as an exercise :)) but should illustrate the basic technique. The trick is to create a state table (as you say). If you record a 1 for a "start" event and a -1 for an "end" event then a running total in event date/time order gives you the current state at that particular event date/time. The SQL below is T-SQL but should be easily adaptable to whatever database server you're using.
Using your data for partial outage as an example:
DECLARE #Faults TABLE (
StartDateTime DATETIME NOT NULL,
EndDateTime DATETIME NULL
)
INSERT INTO #Faults (StartDateTime, EndDateTime)
SELECT '2013-07-03 08:30', '2013-07-04 10:00'
UNION ALL SELECT '2013-07-04 12:00', NULL
UNION ALL SELECT '2013-07-04 12:30', '2013-07-04 12:35'
-- "Unpivot" the events and assign 1 to a start and -1 to an end
;WITH FaultEvents AS (
SELECT *, Ord = ROW_NUMBER() OVER(ORDER BY EventDateTime)
FROM (
SELECT EventDateTime = StartDateTime, Evt = 1
FROM #Faults
UNION ALL SELECT EndDateTime, Evt = -1
FROM #Faults
WHERE EndDateTime IS NOT NULL
) X
)
-- Running total of Evt gives the current state at each date/time point
, FaultEventStates AS (
SELECT A.Ord, A.EventDateTime, A.Evt, [State] = (SELECT SUM(B.Evt) FROM FaultEvents B WHERE B.Ord <= A.Ord)
FROM FaultEvents A
)
SELECT StartDateTime = S.EventDateTime, EndDateTime = F.EventDateTime
FROM FaultEventStates S
OUTER APPLY (
-- Find the nearest transition to the no-fault state
SELECT TOP 1 *
FROM FaultEventStates B
WHERE B.[State] = 0
AND B.Ord > S.Ord
ORDER BY B.Ord
) F
-- Restrict to start events transitioning from the no-fault state
WHERE S.Evt = 1 AND S.[State] = 1
If you are using SQL Server 2012 then you have the option to calculate the running total using a windowing function.
The below is a rough guide to getting this working. It will compare against an interval table of dates and an interval table of 15 mins. It will then sum the outage events (1 event per interval), but not sum a partial outage if there is a full outage.
You could use a more granular time interval if you needed, I choose 15 mins for speed of coding.
I already had a date interval table set up "CAL.t_Calendar" so you would need to create one of your own to run this code.
Please note, this does not represent actual code you should use. It is only intended as a demonstration and to point you in a possible direction...
EDIT I've just realised I have't accounted for the null end dates. The code will need amending to check for NULL endDates and use #EndDate or GETDATE() if #EndDate is in the future
--drop table ##Events
CREATE TABLE #Events (OUTAGE_ID INT IDENTITY(1,1) PRIMARY KEY
,StartDateTime datetime
,EndDateTime datetime
, completeOutage bit)
INSERT INTO #Events VALUES ('2013-07-03 07:00','2013-07-03 08:55',1),('2013-07-03 08:30','2013-07-04 10:00',0)
,('2013-07-04 12:00',NULL,0),('2013-07-04 12:30','2013-07-04 12:35',0)
--drop table #FiveMins
CREATE TABLE #FiveMins (ID int IDENTITY(1,1) PRIMARY KEY, TimeInterval Time)
DECLARE #Time INT = 0
WHILE #Time <= 1410 --number of 15 min intervals in day * 15
BEGIN
INSERT INTO #FiveMins SELECT DATEADD(MINUTE , #Time, '00:00')
SET #Time = #Time + 15
END
SELECT * from #FiveMins
DECLARE #StartDate DATETIME = '2013-07-03'
DECLARE #EndDate DATETIME = '2013-07-04 23:59:59.999'
SELECT SUM(FullOutage) * 15 as MinutesFullOutage
,SUM(PartialOutage) * 15 as MinutesPartialOutage
,SUM(NoOutage) * 15 as MinutesNoOutage
FROM
(
SELECT DateAnc.EventDateTime
, CASE WHEN COUNT(OU.OUTAGE_ID) > 0 THEN 1 ELSE 0 END AS FullOutage
, CASE WHEN COUNT(OU.OUTAGE_ID) = 0 AND COUNT(pOU.OUTAGE_ID) > 0 THEN 1 ELSE 0 END AS PartialOutage
, CASE WHEN COUNT(OU.OUTAGE_ID) > 0 OR COUNT(pOU.OUTAGE_ID) > 0 THEN 0 ELSE 1 END AS NoOutage
FROM
(
SELECT CAL.calDate + MI.TimeInterval AS EventDateTime
FROM CAL.t_Calendar CAL
CROSS JOIN #FiveMins MI
WHERE CAL.calDate BETWEEN #StartDate AND #EndDate
) DateAnc
LEFT JOIN #Events OU
ON DateAnc.EventDateTime BETWEEN OU.StartDateTime AND OU.EndDateTime
AND OU.completeOutage = 1
LEFT JOIN #Events pOU
ON DateAnc.EventDateTime BETWEEN pOU.StartDateTime AND pOU.EndDateTime
AND pOU.completeOutage = 0
GROUP BY DateAnc.EventDateTime
) AllOutages
Trying to write a query to display the current status of services in a company department. What I have written is just not working. I feel like I am going in the wrong direction.
SELECT MAX(v_StatusEvents.EventTimeStamp) as EventTimeStamp
, MAX(v_StatusEvents.StatusTypeID) as StatusTypeID
, v_StatusEvents.ServiceID
, v_StatusEvents.StatusTypeDescription
, v_StatusEvents.ServiceName
, v_StatusEvents.CategoryName
FROM v_StatusEvents
WHERE v_StatusEvents.CategoryID = 100
AND YEAR(v_StatusEvents.EventTimeStamp) = YEAR(getdate())
AND MONTH(v_StatusEvents.EventTimeStamp) = MONTH(getdate())
AND DAY(v_StatusEvents.EventTimeStamp) = DAY(getdate())
GROUP BY v_StatusEvents.ServiceID
, v_StatusEvents.StatusTypeDescription
, v_StatusEvents.ServiceName
, v_StatusEvents.CategoryName
I have three CATEGORIES: (100 - Internet, 101 - Applications, and 102 - Network).
Each CATEGORY contains SERVICES.
As an example, I have three SERVICES that belong to the CATEGORY Interenet: (50 - Internal, 51 - External, 52 - Development).
Each SERVICE will always have at least one status record for the current date.
The CURRENT STATUS will be set to one of three different STATUS TYPES values: 1 = no issue, 2 = disruption, 3 = critical.
I want to show the highest STATUS TYPE for each category for today.
Here is a sample record set for today's date.
SeID CatID EventTimeStamp SvcID StatTypeID
201 100 11/11/2012 12:01am 52 1
202 100 11/11/2012 12:01am 51 1
203 100 11/11/2012 12:01am 50 1
204 100 11/11/2012 08:00am 51 3
205 100 11/11/2012 10:50am 50 2
206 100 11/11/2012 11:00am 50 1
207 100 11/11/2012 11:25am 52 2
As you can see, there was a disruption problem with the Internal web site at 10:50m, but it was resolved at 11:00am.
There is an ongoing critical issue with the External web site that has not yet been resolved. I would like the for the query to return the value 3 because this is the highest CURRENT STATUS for a SERVICE that has not been resolved.
(If all services had "no issue", I would expect the query to return the value 1)
Thanks,
crjunk
This is where I find CTEs (Common Table Expressions) useful. They allow you to break the problem apart into steps that you can easily solve. Let's apply that here.
First, get the max status for each service/day:
SELECT CatID, SvcID, MAX(StatTypeID) As MaxStatus
FROM v_StatusEvents
WHERE EventTimeStamp >= cast(cast(current_timestamp as Date) as datetime)
GROUP BY CatID, SvcID
Now that we have this information, we can find that most recent time today that each of these events occured:
WITH StatusInfo As
(
SELECT CatID, SvcID, MAX(StatTypeID) As MaxStatus
FROM v_StatusEvents
WHERE EventTimeStamp >= cast(cast(current_timestamp as Date) as datetime)
GROUP BY CatID, SvcID
)
SELECT se.CatID, se.SvcID, se.StatTypeID, MAX(EventTimeStamp) As EventTimeStamp
FROM v_StatusEvents se
INNER JOIN StatusInfo si ON se.CatID = si.CatID AND se.SvcID = si.SvcID AND se.StatTypeID = si.MaxStatus
WHERE EventTimeStamp >= cast(cast(current_timestamp as Date) as datetime)
You might choose instead to use the sequence ID to narrow it down here, in case you could have two events with the same timestamp for a service. Now that we have this information, we can go back to the table one more time to pick up any other fields we might want (in this case, sequence ID):
WITH StatusInfo As
(
SELECT CatID, SvcID, MAX(StatTypeID) As MaxStatus
FROM v_StatusEvents
WHERE EventTimeStamp >= cast(cast(current_timestamp as Date) as datetime)
GROUP BY CatID, SvcID
), StatusAndTimeInfo As
(
SELECT se.CatID, se.SvcID, se.StatTypeID, MAX(EventTimeStamp) As EventTimeStamp
FROM v_StatusEvents se
INNER JOIN StatusInfo si ON se.CatID = si.CatID AND se.SvcID = si.SvcID AND se.StatTypeID = si.MaxStatus
WHERE EventTimeStamp >= cast(cast(current_timestamp as Date) as datetime)
)
SELECT se.*
FROM v_StatusEvents se
INNER JOIN StatusAndTimeInfo sati ON se.CatID = sati.CatID AND se.SvcID = sati.SvcID AND se.StatTypeID = sati.StatTypeID AND se.EventTimeStamp = sati.EventTimeStamp
WHERE EventTimeStamp >= cast(cast(current_timestamp as Date) as datetime)
Note again that you might prefer to use the SeID (which I presume is a sequence ID) on this last iteration rather than timestamp. Note also that this is NOT the only way to solve this problem, or even likely the fastest. In fact, it would be possible to re-write this using only subqueries or joins. But this is any easy method you can use to get something that works, and can be easily understood later.
Ended up coming with the following solution:
SELECT (IsNull(MAX(tblStatusTypes.StatusTypeImgURL), (SELECT tblStatusTypes.StatusTypeImgURL FROM tblStatusTypes WHERE tblStatusTypes.StatusTypeID = 1)))
FROM tblStatusTypes
WHERE tblStatusTypes.StatusTypeID in (
SELECT MAX(StatusTypeID)
FROM
( SELECT vse.StatusTypeID, vse.ServiceID, vse.EventTimeStamp
FROM v_StatusEvents vse,
(SELECT MAX(EventTimeStamp)AS MaxDate
,ServiceID
FROM v_StatusEvents
WHERE EventTimeStamp >= Cast(Cast(CURRENT_TIMESTAMP AS DATE) AS DATETIME)
AND CategoryID = #CategoryID
GROUP BY ServiceID) MaxResults
WHERE vse.ServiceID = MaxResults.ServiceID
AND vse.EventTimeStamp = MaxResults.MaxDate
) MaxStatusType )
I have a rather complicated (and very inefficient) way of getting utilisation from a large list of periods (Code below).
Currently I'm running this for a period of 8 weeks and it's taking between 30 and 40 seconds to return data.
I need to run this regularly for periods of 6 months, 1 year and two years which will obviously take a massive amount of time.
Is there a smarter way to run this query to lower the number of table scans?
I have tried several ways of joining the data, all seem to return junk data.
I've tried to comment the code as much as I can but if anything is unclear let me know.
Table Sizes:
[Stock] ~12,000 records
[Contitems] ~90,000 records
Pseudocode for clarity:
For each week between Start and End:
Get list of unique items active between dates (~12,000 rows)
For each unique item
Loop through ContItems table (~90,000 rows)
Return matches
Group
Group
Return results
The Code
DECLARE #WEEKSTART DATETIME; -- Used to pass start of period to search
DECLARE #WEEKEND DATETIME; -- Used to pass end of period to search
DECLARE #DC DATETIME; -- Used to increment dates
DECLARE #INT INT; -- days to increment for each iteration (7 = weeks)
DECLARE #TBL TABLE(DT DATETIME, SG VARCHAR(20), SN VARCHAR(50), TT INT, US INT); -- Return table
SET #WEEKSTART = '2012-05-01'; -- Set start of period
SET #WEEKEND = '2012-06-25'; -- Set end of period
SET #DC = #WEEKSTART; -- Start counter at first date
SET #INT = 7; -- Set increment to weeks
WHILE (#DC < #WEEKEND) -- Loop through dates every [#INT] days (weeks)
BEGIN
SET #DC = DATEADD(D,#INT,#DC); -- Add 7 days to the counter
INSERT INTO #TBL (DT, SG, SN, TT, US) -- Insert results from subquery into return table
SELECT #DC, SUB.GRPCODE, SubGrp.NAME, SUM(SUB.TOTSTK), SUM(USED)
FROM
(
SELECT STK.GRPCODE, 1 AS TOTSTK, CASE (SELECT COUNT(*)
FROM ContItems -- Contains list of hires with a start and end date
WHERE STK.ITEMNO = ContItems.ITEMNO -- unique item reference
AND ContItems.DELDATE <= DATEADD(MS,-2,DATEADD(D,#INT,#DC)) -- Hires starting before end of week searching
AND (ContItems.DOCDATE#5 >= #DC -- Hires ending after start of week searching
OR ContItems.DOCDATE#5 = '1899-12-30 00:00:00.000')) -- Or hire is still active
WHEN 0 THEN 0 -- None found return zero
WHEN NULL THEN 0 -- NULL return zero
ELSE 1 END AS USED -- Otherwise return 1
FROM Stock STK - List of unique items
WHERE [UNIQUE] = 1 AND [TYPE] != 4 -- Business rules
AND DATEPURCH < #DC AND (DATESOLD = '1899-12-30 00:00:00.000' OR DATESOLD > DATEADD(MS,-2,DATEADD(D,#INT,#DC))) -- Stock is valid between selected week
) SUB
INNER JOIN SubGrp -- Used to get 'pretty' names
ON SUB.GRPCODE = SubGrp.CODE
GROUP BY SUB.GRPCODE, SubGrp.NAME
END
-- Next section gets data from temp table
SELECT SG, SN, SUM(TT) AS TOT, SUM(US) AS USED, CAST(SUM(US) AS FLOAT) / CAST(SUM(TT) AS FLOAT) AS UTIL
FROM #TBL
GROUP BY SG, SN
ORDER BY TOT DESC
I have two suggestions.
First, rewrite the query to move the "select" statement from the case statement to the from clause:
SELECT #DC, SUB.GRPCODE, SubGrp.NAME, SUM(SUB.TOTSTK), SUM(USED)
FROM (SELECT STK.GRPCODE, 1 AS TOTSTK,
(CASE MAX(Contgrp.cnt) -- Or hire is still active
WHEN 0 THEN 0 -- None found return zero
WHEN NULL THEN 0 -- NULL return zero
ELSE 1
END) AS USED -- Otherwise return 1
FROM Stock STK left outer join -- List of unique items
(SELECT itemno, COUNT(*) as cnt
FROM ContItems -- Contains list of hires with a start and end date
WHERE ContItems.DELDATE <= DATEADD(MS,-2,DATEADD(D,#INT,#DC)) AND -- Hires starting before end of week searching
(ContItems.DOCDATE#5 >= #DC OR -- Hires ending after start of week searching
ContItems.DOCDATE#5 = '1899-12-30 00:00:00.000'
)
group by ITEMNO
) ContGrp
on STK.ITEMNO = ContItems.ITEMNO
WHERE [UNIQUE] = 1 AND [TYPE] != 4 AND -- Business rules
DATEPURCH < #DC AND (DATESOLD = '1899-12-30 00:00:00.000' OR DATESOLD > DATEADD(MS,-2,DATEADD(D,#INT,#DC))) -- Stock is valid between selected week
) SUB INNER JOIN SubGrp -- Used to get 'pretty' names
ON SUB.GRPCODE = SubGrp.CODE
GROUP BY SUB.GRPCODE, SubGrp.NAME
In doing this, I found a something suspicious. The case statement is operating at the level of "ItemNo", but the grouping is by "GrpCode". So, the "Count(*)" is really returning the sum at the group level. Is this what you intend?
The second is to dispense with the WHILE loop, if you have multiple weeks. To do this, you just need to convert DatePurch to an appropriate week. However, if the code usually runs on just one or two weeks, this effort may not help very much.
Well, replacing the DATEADD functions in the WHERE clauses at first.
You already have
SET #DC = DATEADD(D,#INT,#DC);
Why not declare another local variable for deletion date:
WHILE (#DC < #WEEKEND) -- Loop through dates every [#INT] days (weeks)
BEGIN
SET #DC = DATEADD(D,#INT,#DC);
DECLARE #DeletionDate DATETIME = DATEADD(MS,-2,DATEADD(D,#INT,#DC));
And use it in the case statement:
CASE (SELECT COUNT(*) .... AND ContItems.DELDATE <= #DeletionDate ....
And also in the outer where clause...
Then you need to make sure that you have correctly indexed your tables.