Related
I have a list of tasks,
for each I have a TaskID, startTime and StopTime as milliseconds from 1-1-1970 and a list of users (#Tasks).
I need to calculate the time spent by each user on the task splitted by day/night time, week or weekend, regular/overtime considering nighttime hours from 10:00 PM to 06:00 AM.
Surely there's a better solution but so far I got this:
IF OBJECT_ID('tempdb..#Tasks') IS NULL
BEGIN
create table #Tasks
(
TaskID nvarchar(50),
DateStart bigint,
DateStop bigint,
Staff nvarchar(100)
)
insert into #Tasks values
('C001',1554181200000,1554190200000,'john,jack'),
('C002',1554202800000,1554212700000,'tom,john'),
('C003',1554228000000,1554246900000,'john,franck'),
('C004',1554613200000,1554626700000,'john')
END
GO
declare
#UserName nvarchar(50)='john',
#DateFrom datetime='2019-04-01',
#DateTo datetime='2019-04-30',
#nStart time='06:00:00',
#nStop time='22:00:00'
select
startday as [Day],
sum([WeekDay]) as [WeekDay],
sum([WeekNight]) as [WeekNight],
sum([WeekendDay]) as [WeekendDay],
sum([WeekendNight]) as [WeekendNight],
sum([TotalMinutes]) as [TotalMinutes],
0 WeekDayOverTime,
0 WeekNightOvertime,
0 WeekendDayOvertime,
0 WeekendNightOvertime,
[UserName]
,timeframe
from
(
select
iif(isWeekend=1,NightMinutes,0) WeekendNight,
iif(isWeekend=0,NightMinutes,0) WeekNight,
iif(isWeekend=1,DayMinutes,0) WeekendDay,
iif(isWeekend=0,DayMinutes,0) [WeekDay],
TotalMinutes,
username,
startday,
timeframe
from
(
select
iif(Before6>0,Before6,0)+ iif(After22>0,After22,0) NightMinutes,
TotalMinutes-iif(Before6>0,Before6,0)- iif(After22>0,After22,0) DayMinutes,
TotalMinutes,
startday,
isWeekend,
UserName,
timeframe
from
(
Select
(t.datestop-t.datestart)/60000 TotalMinutes,
datediff(n,convert(time,DATEADD(SECOND,t.DateStart/1000,'1970-01-01')),#nStart) Before6,
datediff(n,#nStop,convert(time,DATEADD(SECOND,t.DateStop/1000,'1970-01-01'))) After22,
iif((((DATEPART(DW, convert(datetime,DATEADD(SECOND,t.DateStart/1000,'1970-01-01'))) - 1 ) + ##DATEFIRST ) % 7) IN (0,6),1,0) isWeekend,
convert(varchar(10),DATEADD(SECOND,t.DateStart/1000,'1970-01-01'),126) startday,
STUFF(( SELECT distinct ' ' + convert(varchar(5),DATEADD(SECOND,t.DateStart/1000,'1970-01-01'),108)+'-'+convert(varchar(5),DATEADD(SECOND,t.DateStop/1000,'1970-01-01'),108) AS [text()]
FROM #Tasks tt
--WHERE tt.taskID=t.TaskID
FOR XML PATH('') ), 1, 1, '' ) AS [timeframe],
#UserName UserName
FROM #Tasks t
WHERE t.Staff like '%'+#UserName+'%'
and DATEADD(SECOND,t.DateStart/1000,'1970-01-01') between #DateFrom and #DateTo
) z
) zz
) zzz
group by startday,username,timeframe
order by startday
I need now to :
1) group result by day, summing up WeekDay,WeekNight,WeekendDay,WeekendNight and TotalMinutes, and concatenating timeframe so to have for example on 2nd April "05:00-07:30|11:00-13:45|18:00-23:00"
2) Not sum up time between 12:00 and 12:30 (if applicable) since it is lunch time
3) considering that after 8 hours daily it has to be calculated as overtime, I have to split the total minutes between in time and overtime, but depending if overtime is by daytime or nighttime or on the weekend
4) eventually using a holiday table
in other words we should have this:
Day TotalMinutes WeekDay WeekNight WeekendDay WeekendNight WeekDayOverTime WeekNightOvertime WeekendDayOvertime WeekendNightOvertime UserName timeframe
02/04/2019 630 420 60 0 0 45 75 0 0 john 05:00-07:30|11:00-13:45|18:00-23:00
07/04/2019 225 0 0 165 60 0 0 0 0 john 05:00-08:45
because (on 2nd April) we have:
First Task:
60 minutes of Regular NightTime
90 minutes of Regular DayTime
Second Task:
165 minutes of Regular DayTime, but have to count only 135 due to lunch time
Third Task:
240 DayTime
75 NightTime
but since with Task 1 and 2 we sum up 285 minutes, only the first 185 Minutes of Third task are Regular DayTime: the remaining 45 are Overtime DayTime, and the following 75 of NightTime are actually OvertimeNightTime
In this approach the first CTE (properDates) get the Start and Stop Datetimes, then you don't need to repeat that formula over the query.
The second CTE(splittedMinutes) is to get the same data you get in your current approach, except for the first CROSS APPLY, which is splitting the timeframes crossing with lunch time. The second CROSS APPLY gets the number of minutes and isWeekend value.
In the third CTE(qualifiedMinutes) I am using a window partition to get the accumulated minutes and generate the overtimes when applies.
At the end I used a selective SUM to separate weekdays and weekends in the aggregates
;with properDates AS (
SELECT TaskID, DATEADD(SECOND,t.DateStart/1000,'1970-01-01') as DateStart,DATEADD(SECOND,t.DateStop/1000,'1970-01-01') as DateStop, Staff
FROM #Tasks t
WHERE Staff LIKE '%' + #UserName + '%'
), splittedMinutes AS (
select
CAST(p.DateStart AS DATE) as [Day],
TotalMinutes,
SUM(TotalMinutes) OVER (PARTITION BY CAST(p.DateStart AS DATE) ORDER BY b.start) AS cumulate,
TotalMinutes - EarlyMinutes - LateMinutes as DayTime,
EarlyMinutes + LateMinutes as NightTime,
isWeekend,
CONVERT(VARCHAR(5),b.Start,108) + '-' + CONVERT(VARCHAR(5),b.Stop,108) as [timeframe]
from properdates p
cross apply (
select CAST(p.DateStart As TIME) AS Start, #bStart as Stop WHERE CAST(p.DateStart AS TIME) < #bStart and CAST(p.DateStop AS TIME) > #bStart
union
select #bStop as Start, CAST(DateStop AS TIME) AS Stop WHERE CAST(p.DateStop AS TIME) > #bStop and CAST(p.DateStart AS TIME) < #bStop
union
select CAST(p.DateStart AS TIME) AS Start, CAST(p.DateStop AS TIME) AS Stop WHERE NOT (CAST(p.DateStart AS TIME) < #bStart and CAST(p.DateStop AS TIME) > #bStart) AND NOT (CAST(p.DateStop AS TIME) > #bStop and CAST(p.DateStart AS TIME) < #bStop)
) b
cross apply (
select
DATEDIFF(Minute, b.Start, b.Stop) as TotalMinutes,
(DATEDIFF(Minute, CAST(b.Start AS TIME), #nStart) + ABS(DATEDIFF(Minute, CAST(b.Start AS TIME), #nStart))) / 2 as EarlyMinutes,
(DATEDIFF(Minute, #nStop, CAST(b.Stop AS TIME)) + ABS(DATEDIFF(Minute, #nStop, CAST(b.Stop AS TIME)))) / 2 as LateMinutes,
CASE WHEN DATEPART(DW, p.DateStart) IN (1,7) THEN 1 ELSE 0 END AS isWeekend
) c
), qualifiedMinutes As (
SELECT Day, TotalMinutes, RegularDay, RegularNight, OvertimeDay, OvertimeNight, isWeekend, timeframe
FROM splittedMinutes
OUTER APPLY (
SELECT RegularDay = CASE WHEN cumulate <= #maxTime THEN DayTime WHEN DayTime - (cumulate - TotalMinutes - #maxTime) > 0 THEN ABS(cumulate - TotalMinutes - #maxTime) ELSE 0 END
) RD
OUTER APPLY (
SELECT OvertimeDay = DayTime - RegularDay
) OWD
OUTER APPLY (
SELECT RegularNight = CASE WHEN cumulate <= #maxTime THEN NightTime WHEN (cumulate - TotalMinutes - #maxTime + RegularDay) < 0 THEN NightTime + (cumulate - TotalMinutes - #maxTime + RegularDay) ELSE 0 END
) RWN
OUTER APPLY (
SELECT OvertimeNight = NightTime - RegularNight
) OWN
)
SELECT
Day,
#UserName and UserName,
SUM(TotalMinutes) AS TotalMinutes,
SUM(CASE WHEN isWeekend = 0 THEN RegularDay ELSE 0 END) AS WeekDay,
SUM(CASE WHEN isWeekend = 0 THEN RegularNight ELSE 0 END) AS WeekNight,
SUM(CASE WHEN isWeekend = 1 THEN RegularDay ELSE 0 END) AS WeekendDay,
SUM(CASE WHEN isWeekend = 1 THEN RegularNight ELSE 0 END) AS WeekendNight,
SUM(CASE WHEN isWeekend = 0 THEN OvertimeDay ELSE 0 END) AS WeekDayOverTime,
SUM(CASE WHEN isWeekend = 0 THEN OvertimeNight ELSE 0 END) AS WeekNightOvertime,
SUM(CASE WHEN isWeekend = 1 THEN OvertimeDay ELSE 0 END) AS WeekendDayOverTime,
SUM(CASE WHEN isWeekend = 1 THEN OvertimeNight ELSE 0 END) AS WeekendNightOvertime,
STUFF((SELECT '|' + timeframe FROM qualifiedMinutes tt WHERE tt.Day = q.Day ORDER BY timeframe FOR XML PATH('') ), 1, 1, '' ) AS [timeframe]
FROM qualifiedMinutes q
GROUP BY Day
I am having a big issue with a SQL Server query here and I really don't know how to go on with it.
The aim is to receive a table differentiated by different time-intervals going from 00:00 - 00:29 to 23:30 - 23:59. In each of these intervals I want to sum up the total minutes of entities which waited during these times. This information can be received by a starttime, and endtime and the status of the entity, which looks like this:
startdate | finishdate | resourcestatus | id
2015-03-19 10:22:56.8490000 | 2015-03-19 10:32:56.8490000 | 8 | asdsdasdsad
As you see such an entity can have the status 8 from one interval (10:00 - 10:30) into another (10:30 - 11:00).
Until now I solved this by defining 4 groups of time-intervals (finish and start are both in interval, start out of interval but finish in, start in interval but finish out, both start and finish out of interval) these 4 groups are joined by the time-intervals.
I would post the code here but it is too much. My result looks like this. Here are the beginnings of the different parts of the query:
select zr.nr,
zr.interval,
case when outOfInterval.waittime is not null
then SUM(outOfInterval.waittime)
else 0
end
+
case when inInterval.waittime is not null
then SUM(inInterval.waittime)
else 0
end
+
case when startInInterval.waittime is not null
then SUM(startInInterval.waittime)
else 0
end
+
case when finishInInterval.waittime is not null
then sum(finishInInterval.waittime)
else 0
end
as waitingMinutes
From (select 1 as nr,'00:00 - 00:29' as interval, 0 as waittime
union select 2,'00:30 - 00:59', 0
union select 3,'01:00 - 01:29', 0 ...
) zr
left join (select case when CONVERT(time, rt.startedat, 8) < '00:00' and CONVERT(time, rt.finishedat , 8) > '00:30' then '00:00 - 00:29' end as inter, 30 as waittime from T_resourcetracking rt where rt.resource_id is not null and rt.resourcestatus = 8 AND CONVERT(Date, rt.startedat) >= '02.02.2015' AND CONVERT(Date, rt.finishedat) < DateAdd(day,1,CONVERT ( datetime , '08.05.2015', 120 ))
...
) outOfInterval on outOfInterval.inter = zr.interval
left join (select case when CONVERT(time, rt.startedat, 8) >= '00:00' and CONVERT(time, rt.finishedat , 8) <= '00:30' then '00:00 - 00:29' end as inter, SUM(DATEDIFF(minute, rt.STARTEDAT, rt.FINISHEDAT)) as waittime from T_resourcetracking rt where rt.resource_id is not null and rt.resourcestatus = 8 AND CONVERT(Date, rt.startedat) >= '02.02.2015' AND CONVERT(Date, rt.finishedat) <= DateAdd(day,1,CONVERT ( datetime , '08.05.2015', 120 )) group by rt.startedat, rt.finishedat
...
) inInterval on inInterval.inter = zr.interval
left join (select case when CONVERT(time, rt.startedat, 8) >= '00:00' and CONVERT(time, rt.startedat, 8) < '00:30'and CONVERT(time, rt.finishedat , 8) >= '00:30' then '00:00 - 00:29' end as inter, (30-DATEPART(minute, rt.STARTEDAT)) as waittime from T_resourcetracking rt where rt.resource_id is not null and rt.resourcestatus = 8 AND CONVERT(Date, rt.startedat) >= '02.02.2015' AND CONVERT(Date, rt.finishedat) <= DateAdd(day,1,CONVERT ( datetime , '08.05.2015', 120 )) group by rt.startedat, rt.finishedat
...
) startInInterval on startInInterval.inter = zr.interval
left join (select case when CONVERT(time, rt.startedat, 8) >= '00:00' and CONVERT(time,rt.finishedat, 8) < '00:30'and CONVERT(time, rt.STARTEDAT , 8) < '00:00' then '00:00 - 00:29' end as inter, DATEPART(minute, rt.finishedat) as waittime from T_resourcetracking rt where rt.resource_id is not null and rt.resourcestatus = 8 AND CONVERT(Date, rt.startedat) >= '02.02.2015' AND CONVERT(Date, rt.finishedat) <= DateAdd(day,1,CONVERT ( datetime , '08.05.2015', 120 )) group by rt.startedat, rt.finishedat
...
) finishInInterval on finishInInterval.inter = zr.interval
group by zr.interval, outOfInterval.waittime, inInterval.waittime, startInInterval.waittime, finishInInterval.waittime, zr.nr
And this is the result:
nr | interval | waitingMinutes
1 | 00:00 - 00:29 | 2
2 | 00:30 - 00:59 | 7
...
24 | 11:30 - 11:59 | 8
24 | 11:30 - 11:59 | 51
...
So as you see I have more then one of an interval in my result set.
Do you have any idea how to join the groups to one and sum the minutes up? I am really done with it, every kind of aggregate function did not work for me.
Thanks in advance!
#EDIT: If this was not difficult enough we need a second specification which I forgot to explain: We do not want to see all waitingtimes during the 48 time-intervals but the SUM of all those within a specific date-interval.
Let's say we want to know the summed up minutes from the last month. Then the result set should look like:
nr | interval | waitingMinutes
1 | 00:00 - 00:29 | 0
2 | 00:30 - 00:59 | 0
...
20 | 09:30 - 09:59 | 0
21 | 10:00 - 10:29 | 8
22 | 10:30 - 10:59 | 73
23 | 11:00 - 11:29 | 20
...
The minutes are summed up over all time-intervals of the last month. So for example from 11:00 - 11:29 in the last 30 days the entities waited 20 minutes in total (e.g. yesterday 10 minutes and the day before 10 minutes).
This is so difficult that I have really no clue anymore thinking that this is too much for SQL...
Any suggestions?
I would break you problem down something like this. I may have a few factors slightly off here but hopefully you can see where I'm going with this.
I'll break up the script with commentary, but the actual thing should be run as one single query:
declare #StartDate date
declare #EndDate date
select #StartDate = '20150202',#EndDate='20150508'
I've broken the start and end dates out as parameters as I guess these are subject to change and so this gives us one place to change them rather than many
;With Dates as (
select CAST(#StartDate as datetime) as Day
union all
select DATEADD(day,1,Day) from Dates where Day < #EndDate
)
First CTE, Dates, generates all dates within the period of interest. If you have a calendar table in your database, just select from it instead
, PMNs as (
select ROW_NUMBER() OVER (ORDER BY number)-1 as n
from master..spt_values
)
Next CTE, PMNs is my "poor man's numbers table" - if you have a real numbers table in your database, you can substitute that instead
, DateTimes as (
select
n+1 as nr,
DATEADD(minute,30*n,Day) as StartInclusive,
DATEADD(minute,30*(n+1),Day) as EndExclusive
from
Dates d
inner join
PMNs p
on
p.n between 0 and 47
)
Now, the real fun one. We combine the first two CTEs to generate DateTimes - the complete set of all half hour long periods across all dates of interest
select
nr,
CAST(time,StartInclusive) as StartTime,
CAST(time,EndInclusive) as EndTime,
SUM(
DATEDIFF(minute,
CASE WHEN dt.StartInclusive < rt.StartedAt THEN rt.StartedAt
ELSE dt.StartInclusive END,
CASE WHEN dt.EndExclusive > rt.finishedAt THEN rt.FinishedAt
ELSE dt.EndExclusive END
)) as TotalMinutes
from
DateTimes dt
inner join
T_resourcetracking rt
on
dt.StartInclusive < rt.finishedAt and
rt.startedAt < dt.EndExclusive
group by
nr,
CAST(time,StartInclusive),
CAST(time,EndInclusive)
And finally, we combine the data together. We find where a resourceTracking period overlaps one of our DateTimes periods (note the on clause for the join identifies all overlaps). And then a little manipulation inside some CASE expressions to work out the latter of the two start datetimes and the earlier of the two end datetimes - those are the two values we want to subtract.
If your T_resourcetracking isn't also (as with my DateTimes) computing intervals with a semi-open interval (inclusive start time, exclusive end time) you probably want to make some adjustments so that it does seem to be.
The idea is producing all 48 intervals with TALLY using CTE and joining to your data so that 2 intervals intersect. They intersect if any of vertice is between other vertices:
a-----------------b
c------------------------d
a-----------------b
c-----------------d
a------------------b
c----d
a------------------b
c----------d
The last select is just grouping and correct calculation depending on case.
DECLARE #t TABLE
(
sd DATETIME ,
ed DATETIME ,
st INT
)
INSERT INTO #t
VALUES ( '2015-03-19 10:31:56', '2015-03-19 10:42:56', 8 ),
( '2015-03-19 10:25:56', '2015-03-19 10:35:56', 8 ),
( '2015-03-19 10:31:56', '2015-03-19 11:10:56', 8 ),
( '2015-03-19 10:25:56', '2015-03-19 11:10:56', 8 );
WITH cte
AS ( SELECT DATEADD(mi,
30 * ( -1
+ ROW_NUMBER() OVER ( ORDER BY ( SELECT
1
) ) ),
CAST('00:00:00' AS TIME)) sp ,
DATEADD(mi,
-1 + 30
* ROW_NUMBER() OVER ( ORDER BY ( SELECT
1
) ),
CAST('00:00:00' AS TIME)) ep
FROM ( VALUES ( 1), ( 1), ( 1), ( 1), ( 1), ( 1), ( 1),
( 1) ) t1 ( n )
CROSS JOIN ( VALUES ( 1), ( 1), ( 1), ( 1),
( 1), ( 1) ) t2 ( n )
)
SELECT sp, ep,
SUM(CASE WHEN CAST(t.sd AS TIME) < c.sp
AND CAST (t.ed AS TIME) > c.ep THEN DATEDIFF(mi, sp, ep)
WHEN CAST(t.sd AS TIME) BETWEEN c.sp AND c.ep
AND CAST(t.ed AS TIME) BETWEEN c.sp AND c.ep
THEN DATEDIFF(mi, CAST(sd AS TIME), CAST(ed AS TIME))
WHEN CAST(t.sd AS TIME) BETWEEN c.sp AND c.ep
THEN DATEDIFF(mi, CAST(sd AS TIME), ep)
ELSE DATEDIFF(mi, sp, CAST(ed AS TIME))
END) AS Mi
FROM cte c
JOIN #t t ON CAST(t.sd AS TIME) BETWEEN c.sp AND c.ep
OR CAST(t.ed AS TIME) BETWEEN c.sp AND c.ep
OR c.sp BETWEEN CAST(t.sd AS TIME) AND CAST(t.ed AS TIME)
OR c.ep BETWEEN CAST(t.sd AS TIME) AND CAST(t.ed AS TIME)
GROUP BY sp, ep
Output:
sp ep Mi
10:00:00.0000000 10:29:00.0000000 8
10:30:00.0000000 10:59:00.0000000 73
11:00:00.0000000 11:29:00.0000000 20
Change JOIN to LEFT JOIN in order to get all intervals.
You should tweak this to get 0s using ISNULL on SUM. Also this considers only one day.
Please try this solution. You can use it even if the finishdate is on an other day than the startdate.
;with event_time as (
/*this is the input*/
select 1 id, convert(datetime,'2015-05-11 23:11') startdate, convert(datetime,'2015-05-12 00:15') finishdate
)
, event_time_convert as (
/*convert the input to calculation*/
select i.id, convert(time,i.startdate) startdate, DATEDIFF(MINUTE, i.startdate, i.finishdate) time_until_end
from event_time i
)
, intervall as (
/*create the intervall groups*/
select 1 id, CONVERT(time,'00:00') startdate, CONVERT(time,'00:29') finishdate
union all
select cs.id+1 id, DATEADD(minute,30,cs.startdate) startdate, DATEADD(minute,30,cs.finishdate) finishdate
from intervall cs
where cs.id<48
)
, event_time_in_intervall as (
/*calculate the waiting minutes in intervall*/
select i.id
, cs.id intervall_id
, case when DATEDIFF(minute,i.startdate, cs.finishdate) > i.time_until_end then i.time_until_end else DATEDIFF(minute,i.startdate, cs.finishdate) end time_in_intervall
, case when DATEDIFF(minute,i.startdate, cs.finishdate) > i.time_until_end then null else DATEADD(minute,1,cs.finishdate) end new_startdate
, case when DATEDIFF(minute,i.startdate, cs.finishdate) > i.time_until_end then 0 else i.time_until_end - DATEDIFF(minute,i.startdate, cs.finishdate)+1 end new_time_until_end
from event_time_convert i
join intervall cs on i.startdate between cs.startdate and cs.finishdate /*this is the first intervall*/
union all
select i.id
, cs.id intervall_id
, case when DATEDIFF(minute,i.new_startdate, cs.finishdate) > i.new_time_until_end then i.new_time_until_end else DATEDIFF(minute,i.new_startdate, cs.finishdate)+1 end time_in_intervall
, case when DATEDIFF(minute,i.new_startdate, cs.finishdate) > i.new_time_until_end then null else DATEADD(minute,1,cs.finishdate) end new_startdate
, case when DATEDIFF(minute,i.new_startdate, cs.finishdate) > i.new_time_until_end then 0 else i.new_time_until_end - DATEDIFF(minute,i.new_startdate, cs.finishdate)+1 end new_time_until_end
from event_time_in_intervall i
join intervall cs on i.new_startdate between cs.startdate and cs.finishdate
where i.new_time_until_end>0 /*if there is remaining time, I calculate with a recursion*/
)
/*the result*/
select i.id, CONVERT(varchar(5),i.startdate) + ' - ' + CONVERT(varchar(5), i.finishdate) intervall, s.sum_time_in_intervall waitingMinutes
from (
select i.intervall_id, SUM(i.time_in_intervall) sum_time_in_intervall
from event_time_in_intervall i
group by i.intervall_id
) s
join intervall i on s.intervall_id = i.id
I have a query that is doing some math and in there are times where a number could be a zero that its dividing by which would cause an error.
I found something on this website about how to fix it but now the number doesnt change at all.
Example Data:
los.shortTermLosses = 1
A.shortTerm = 15
Giving the equation of 1/15*12 = 0.8
COALESCE(los.shortTermLosses / NULLIF(A.shortTerm,0),0.00)* 12 AS shortTermAttrition
It has to be something I did to prevent the divide by zero error but not sure how to get it to work correctly. The current result is always the 0.00
Update
For those who want to see the whole query..
SELECT A.QID,
(SELECT TOP 1 E.[FirstName],
E.[LastName],
E.[NTID],
E.[TitleDesc],
A.[countOfDirects],
A.[longTerm],
A.[shortTerm],
COALESCE(los.totalLosses,0) totalLosses,
COALESCE(los.longTermLosses, 0) longTermLosses,
COALESCE(los.shortTermLosses,0) shortTermLosses,
COALESCE(los.shortTermLosses / NULLIF(A.shortTerm,0),0.00)* 12 AS shortTermAttrition,
COALESCE(los.longTermLosses / NULLIF(A.longTerm,0),0.00)* 12 AS longTermAttrition,
COALESCE(los.totalLosses / NULLIF(A.countOfDirects,0),0.00)* 12 AS totalAttrition
FROM employeeTable_historical AS E
OUTER APPLY (SELECT COUNT(b.leaver) as [totalLosses],
sum(case when b.term = 'LTA' then 1 else 0 end) as [longTermLosses],
sum(case when b.term = 'STA' then 1 else 0 end) as [shortTermLosses]
FROM dbo.attritionData AS B
WHERE E.QID = B.supervisor
AND MONTH(B.leaveDate) = #month
AND YEAR(B.leaveDate) = #year
GROUP BY b.supervisor
)los
WHERE E.qid = A.[QID]
AND CONVERT (DATE, dateadd(mm, (#year - 1900) * 12 + #month - 1 , #day - 1)) >= CONVERT (DATE, E.[Meta_LogDate])
ORDER BY meta_logDate DESC
FOR XML PATH (''), TYPE, ELEMENTS)
FROM (SELECT QID,
[timestamp],
[countOfDirects],
[longTerm],
[shortTerm]
FROM (SELECT QID,
[timestamp],
[countOfDirects],
[shortTerm],
[longTerm],
ROW_NUMBER() OVER (PARTITION BY QID ORDER BY [Timestamp]) AS Row
FROM [red].[dbo].[attritionCounts]
WHERE [mgrQID] = #director
AND YEAR(CAST ([timestamp] AS DATE)) = #year
AND MONTH(CAST ([timestamp] AS DATE)) = #month) AS Tmp1
WHERE Row = 1) AS A
FOR XML PATH ('DirectReport'), TYPE, ELEMENTS, ROOT ('Root');
You error does not come from the NULLIF, you are just dividing an Integer e.g. 1 / 15 = 0
, just change your term to:
COALESCE(CAST(los.shortTermLosses as float) / NULLIF(A.shortTerm,0),0.00)* 12 AS shortTermAttrition
The closest transformation for you XML export might be casting the Result as money
Declare #shortTermLosses int = 1
Declare #shortTerm int = 15
select
CAST(
COALESCE(CAST(#shortTermLosses AS float) / Cast(NULLIF(#shortTerm,0) AS float),0.00)* 12
as Money)
AS shortTermAttrition
FOR XML PATH (''), TYPE, ELEMENTS
I have a data from database Like this :
HDiffrence MDiffrence Interv
2 14 2 Hours 14 Minutes
0 4 0 Hours 4 Minutes
so i need to convert both of H and M into time format and I do some logic thing in other query
I do some check like this, here's to check if HH:MM is more than 15 minutes:
count((select(case when((select convert(time,(HDiffrence +':'+MDiffrence),114)) > ((select convert(time,('00' +':'+'15'),114))) )then 1 when ((select convert(time,(HDiffrence +':'+MDiffrence),114)) is null) then null else 0 end)))
and I put the check into :
select contractor , COUNT(pm.PantauID) as total ,
count((select(case when((select convert(time,(HDiffrence +':'+MDiffrence),114)) > ((select convert(time,('00' +':'+'15'),114))) )then 1 when ((select convert(time,(HDiffrence +':'+MDiffrence),114)) is null) then null else 0 end)))
from Pantau p
left join PantauMSG pm
on p.PantauID = pm.PantauID
where PantauType = 'PT2' and PantauStatus <> 'PS1' and
(CAST(SCH_DATE AS DATE) = (SELECT CONVERT (DATE, GETDATE(), 103) AS Expr1))
group by CONTRACTOR
but yes, absoulutely get error :
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
so based on my logic to check the time , is it other way more simplified to count value more than 15 minutes
If you're just working with integer values for Hours and Minutes, just use basic maths instead of converting it to a Time value:
declare #hours int = 1
declare #mins int = 35
declare #total_mins int = (#hours * 60) + #mins
select #total_mins - 15
Then use it something like this:
select contractor, COUNT(pm.PantauID) as total, sum(HDiffrence) as Hours,
sum(MDiffrence) as Minutes, sum(HDiffrence) * 60 + sum(MDiffrence) as TotalMinutes
from Pantau p
left join PantauMSG pm
on p.PantauID = pm.PantauID
where PantauType = 'PT2' and PantauStatus <> 'PS1' and
(CAST(SCH_DATE AS DATE) = (SELECT CONVERT (DATE, GETDATE(), 103) AS Expr1))
group by CONTRACTOR
Okay based on Tanner Idea I change my query and yes, finally it works:
select contractor , COUNT(pm.PantauID) as total ,
count(case when ((convert(int, HDiffrence)* 60 )+convert(int,MDiffrence)) > 15 then ((convert(int, HDiffrence)* 60 )+convert(int,MDiffrence)) else null end) as Morethan15,
count(case when ((convert(int, HDiffrence)* 60 )+convert(int,MDiffrence)) < 15 then ((convert(int, HDiffrence)* 60 )+convert(int,MDiffrence)) else null end) as lessthan15
from Pantau p
left join PantauMSG pm
on p.PantauID = pm.PantauID
where PantauType = 'PT2' and PantauStatus <> 'PS1' and
(CAST(SCH_DATE AS DATE) = (SELECT CONVERT (DATE, GETDATE(), 103) AS Expr1))
group by CONTRACTOR
I have 4 million rows in my table with a blank column called Cancelled Bookings divided into 3 years 2010, 2011 and 2012
Booking_Skey BookingNumber ArrivalDate DepartureDate BookingDate CancelledDate BookingValue PitchType_Skey Site_Skey
124532 B00124532 2010-12-31 2011-01-02 2010-12-31 NULL 10.00 7 2
What I need to do is create a code where I can change the % of cancelations for the year I want to update:
So for 2010 I need the following
--Cancelled Bookings--
8% of the total bookings are cancelled in the Year 2010, the cancellation date can be equal too or less than the Arrival Date and equal to or greater than the Booking Date
20% of the 8% are cancelled on the same day as the Arrival Date
20% of the 8% are cancelled the day before the Arrival Date
20% of the 8% are cancelled 7 days prior to the Arrival Date
The rest of the cancellations are randomised between 1 and 90 days
.
USE Occupancy
SELECT ArrivalDate,
DATEADD(day,
CASE WHEN Rand(CHECKSUM(NEWID())) BETWEEN 0 and 0.92 THEN NULL ELSE
CASE WHEN Rand(CHECKSUM(NEWID())) BETWEEN 0.92 and 0.94 THEN 0 ELSE
CASE WHEN Rand(CHECKSUM(NEWID())) BETWEEN 0.94 and 0.96 THEN -1 ELSE
CASE WHEN Rand(CHECKSUM(NEWID())) BETWEEN 0.96 and 0.98 THEN -7 ELSE
Round(Rand(CHECKSUM(NEWID())) * -90,0) END END END END, ArrivalDate) AS DaystoReduce
FROM Bookings
WHERE DATEPART(Year,ArrivalDate) = '2010' and CancelledDate BETWEEN ArrivalDate AND DepartureDate
Can you help?
Thanks
Wayne
How about something like the following? Its not very pretty so will leave that as an exercise for you, but basically its along the lines of calculating the number of each type and then apply the rules against the row number...
declare #shareCancelled float, #shareSameDay float, #sharePrevDay float, #shareSevenDays float
select #shareCancelled = 0.08, #shareSameDay = 0.20, #sharePrevDay = 0.20, #shareSevenDays = 0.20
declare #count int, #cancelled int, #sameDay int, #prevDay int, #sevenDays int
select #count = COUNT(*) from Bookings WHERE DATEPART(Year,ArrivalDate) = '2010' and CancelledDate BETWEEN ArrivalDate AND DepartureDate
select #cancelled = #count * #shareCancelled,
#sameDay = #count * #shareCancelled * #shareSameDay,
#prevDay = #count * #shareCancelled * #sharePrevDay,
#sevenDays = #count * #shareCancelled * #shareSevenDays
select ArrivalDate,
DATEADD(day,
CASE WHEN a.RowNum <= #sameDay THEN 0
WHEN a.RowNum <= #sameDay + #prevDay THEN -1
WHEN a.RowNum <= #sameDay + #prevDay + #sevenDays THEN -7
WHEN a.RowNum <= #cancelled THEN -(ABS(CAST(NEWID() AS binary(6)) %90) + 1)
ELSE NULL END
, GETDATE()) as DaystoReduce
from (
select *, ROW_NUMBER() OVER(ORDER BY NEWID()) as RowNum from Bookings WHERE DATEPART(Year,ArrivalDate) = '2010' and CancelledDate BETWEEN ArrivalDate AND DepartureDate
) as a