So my data looks like this:
DATE TEMPERATURE
2012-01-13 23:15:00 UTC 0
2012-01-14 01:35:00 UTC 5
2012-01-14 02:15:00 UTC 6
2012-01-14 03:15:00 UTC 8
2012-01-14 04:15:00 UTC 0
2012-01-14 04:55:00 UTC 0
2012-01-14 05:15:00 UTC -2
2012-01-14 05:35:00 UTC 0
I am trying to calculate the amount of time a zip code temperature will drop to 0 or below on any given day. On the 13th, it only happens for a very short amount of time so we don't really care. I want to know how to calculate the number of minutes this happens on the 14th, since it looks like a significantly (and consistently) cold day.
I want the query to add two more columns.
The first column added would be the time difference between the rows on a given date. So row 3- row 2=40 mins and row 4-row3=60 mins.
The second column would total the amount of minutes for a whole day the minutes the temperature has dropped to 0 or below. Here row 2-4 would be ignored. From row 5-8, total time that the temperature was 0 or below would be about 90 mins
It should end up looking like this:
DATE TEMPERATURE MINUTES_DIFFERENCE TOTAL_MINUTES
2012-01-13 23:15:00 UTC 0 0 0
2012-01-14 01:35:00 UTC 5 140 0
2012-01-14 02:15:00 UTC 6 40 0
2012-01-14 03:15:00 UTC 8 60 0
2012-01-14 04:15:00 UTC 0 60 60
2012-01-14 04:55:00 UTC 0 30 90
2012-01-14 05:15:00 UTC-2 20 110
2012-01-14 05:35:00 UTC 0 20 130
Use below
select *,
sum(minutes_difference) over(order by date) total_minutes
from (
select *,
ifnull(timestamp_diff(timestamp(date), lag(timestamp(date)) over(order by date), minute), 0) as minutes_difference
from your_table
)
if applied to sample data in your question - output is
Update to answer updated question
select * except(new_grp, grp),
sum(if(temperature > 0, 0, minutes_difference)) over(partition by grp order by date) total_minutes
from (
select *, countif(new_grp) over(order by date) as grp
from (
select *,
ifnull(timestamp_diff(timestamp(date), lag(timestamp(date)) over(order by date), minute), 0) as minutes_difference,
ifnull(((temperature <= 0) and (lag(temperature) over(order by date) > 0)) or
((temperature > 0) and (lag(temperature) over(order by date) <= 0)), true) as new_grp
from your_table
)
)
with output
I am using SQL Server 2005
I have two tables:
CheckInOut
TR BadgeNum USERID Dated Time CHECKTYPE
------- --------- ------ ----------------------- ----------------------- ----------
2337334 4 1 2018-04-01 00:00:00.000 2018-04-14 10:10:58.000 I
2337334 4 1 2018-04-01 00:00:00.000 2018-04-14 18:10:00.000 O
2337334 4 1 2018-04-02 00:00:00.000 2018-04-14 10:00:10.000 I
2337335 4 1 2018-04-02 00:00:00.000 2018-04-14 18:14:27.000 O
2337336 4 1 2018-04-03 00:00:00.000 2018-04-14 10:22:10.000 I
2337334 4 1 2018-04-03 00:00:00.000 2018-04-14 18:03:11.000 O
2337337 44 5 2018-04-01 00:00:00.000 2018-04-14 09:27:03.000 I
2337337 44 5 2018-04-01 00:00:00.000 2018-04-14 18:27:42.000 O
2337337 44 5 2018-04-02 00:00:00.000 2018-04-14 10:00:50.000 I
2337337 44 5 2018-04-02 00:00:00.000 2018-04-14 18:02:25.000 O
2337337 44 5 2018-04-03 00:00:00.000 2018-04-14 08:58:36.000 I
2337337 44 5 2018-04-03 00:00:00.000 2018-04-14 18:12:18.000 O
UserInfo
Tr UserID BadgeNumber Name
----- ------- ----------- --------------
13652 44 5 SAMIA NAZ
13653 4 1 Waqar Yousufzai
I need to calculate presence hours for each day for each user. My below query is working fine for given day. But I need to calculate for a given range. How do I get expected result?
Select isnull(max(ch.userid), 0)As 'ID'
,isnull(max(ch.badgenum), 0)as 'Badge#'
,isnull(max(convert(Char(10), ch.dated, 103)), '00:00')as 'Date'
,isnull(max(ui.name),'Empty')as 'Name'
,isnull(min(convert(VARCHAR(26), ch.time, 108)), '00:00') as 'Time In'
,case when min(ch.time) = max(ch.time) then '' else isnull(max(convert(VARCHAR(26), ch.time, 108)), '00:00') end as 'TimeOut'
,case when min(ch.time) = max(ch.time) then 'Absent' else 'Present' end as 'Status'
,isnull(CONVERT(varchar(3),DATEDIFF(minute,min(ch.time), max(ch.time))/60) + ' hrs and ' +
RIGHT('0' + CONVERT(varchar(2),DATEDIFF(minute,min(ch.time),max(ch.time))%60),2) + 'Min' , 0) as 'Total Hrs'
From CHECKINOUT ch left Join userinfo ui on ch.badgenum = ui.badgenumber
Where ch.Dated between '2018-04-01' and '2018-04-03' GROUP BY ch.badgenum
Query result
ID Badge# Date Name Time In TimeOut Status Total Hrs
--- ------ ---------- --------------- -------- ---------- -------- -----------------
4 1 03/04/2018 Waqar Yousufzai 11:33:34 18:24:23 Present 30 hrs and 14Min
82 3 03/04/2018 TANVEER ANSARI 09:37:14 19:18:22 Present 32 hrs and 37Min
13 4 03/04/2018 07:19:26 09:30:17 Present 21 hrs and 49Min
44 5 03/04/2018 SAMIA NAZ 08:53:15 18:25:21 Present 33 hrs and 24Min
28 7 03/04/2018 Anees Ahmad 08:34:57 22:00:38 Present 61 hrs and 25Min
46 8 03/04/2018 Shazia - OT 08:10:41 16:15:05 Present 32 hrs and 01Min
Expected result
ID Badge# Date Name Time In TimeOut Status Total Hrs
--- ------ ---------- --------------- -------- ---------- -------- -----------------
4 1 01/04/2018 Waqar Yousufzai 10:30:00 18:00:00 Present 7 hrs and 30Min
4 1 02/04/2018 Waqar Yousufzai 10:30:00 18:00:00 Present 7 hrs and 30Min
4 1 03/04/2018 Waqar Yousufzai 10:00:00 18:00:00 Present 8 hrs and 00Min
44 5 01/04/2018 SAMIA 08:00:00 18:00:00 Present 10 hrs and 00Min
44 5 02/04/2018 SAMIA 08:30:00 18:00:00 Present 9 hrs and 30Min
44 5 03/04/2018 SAMIA 08:00:00 18:00:00 Present 10 hrs and 00Min
You shouldn't do aggregation on date value, it must be part of grouping. Get time out and time in using conditional aggregation. And count total hours worked. Your query should be something like:
select
BadgeNum, USERID, Dated, Name
, right('0' + cast(datediff(mi, [in], [out]) / 60 as varchar(10)), 2) + ':'
+ right('0' + cast(datediff(mi, [in], [out]) % 60 as varchar(10)), 2)
from (
select
ch.BadgeNum, ch.USERID, dated = cast(ch.Dated as date), ui.Name
, [in] = min(case when ch.CHECKTYPE = 'I' then ch.Time end)
, [out] = min(case when ch.CHECKTYPE = 'O' then ch.Time end)
from
CheckInOut ch
left join UserInfo ui on ch.USERID = ui.badgenumber
where
ch.Dated >= '20180401'
and ch.Dated < '20180404'
group by ch.BadgeNum, ch.USERID, cast(ch.Dated as date), ui.Name
) t
My table has info similar to below
Emp Date START_TIME END_TIME Code Minutes
--- -------- ------------------- ------------------- ---- -------
E1 11/1/2012 11/1/2012 6:55:00 AM 11/1/2012 7:01:00 AM C1 6
E1 11/1/2012 11/1/2012 6:57:00 AM 11/1/2012 8:01:00 AM C2 64
E2 11/1/2012 11/1/2012 6:57:00 AM 11/1/2012 8:00:00 AM C2 63
E1 11/2/2012 11/2/2012 7:35:00 AM 11/2/2012 8:01:00 AM C1 26
Expected Output is
Date Code Range Minutes
--------- ---- ----------------------- -------
11/1/2012 C1 6:30:00 AM-7:00:00 AM 5
11/1/2012 C1 7:00:00 AM-7:30:00 AM 1
11/1/2012 C2 6:30:00 AM-7:00:00 AM 6
11/1/2012 C2 7:00:00 AM-7:30:00 AM 60
11/1/2012 C2 7:30:00 AM-8:00:00 AM 60
11/1/2012 C2 8:00:00 AM-8:30:00 AM 1
11/2/2012 C1 7:30:00 AM-8:00:00 AM 25
11/2/2012 C1 8:00:00 AM-8:30:00 AM 1
Leaving out Emp field, I want to group by date, and code with total time spent in a span of 30 minutes each. And the limitation I have is to achieve this using select statements i.e. only through SQL queries coz PL/SQL is not allowed. Thanks in advance!
a solution involving the model clause.
first lets compute the amount of 30 minute blocks we need per entry.
SQL> select emp, start_time, end_time, code,
2 trunc(start_time, 'mi')
3 - (mod(to_char(trunc(start_time, 'mi'), 'mi'), 30) / 1440) start_block,
4 ceil(2*24*(end_time-(trunc(start_time, 'mi')
5 - (mod(to_char(trunc(start_time, 'mi'), 'mi'), 30) / 1440)))) blocks
6 from tab f
7 /
EM START_TIME END_TIME CO START_BLOCK BLOCKS
-- ---------------------- ---------------------- -- ---------------------- ----------
E1 11/01/2012 06:55:00 am 11/01/2012 07:01:00 am C1 11/01/2012 06:30:00 am 2
E1 11/01/2012 06:57:00 am 11/01/2012 08:01:00 am C2 11/01/2012 06:30:00 am 4
E2 11/01/2012 06:57:00 am 11/01/2012 08:00:00 am C2 11/01/2012 06:30:00 am 3
E1 11/02/2012 07:35:00 am 11/02/2012 08:01:00 am C1 11/02/2012 07:30:00 am 2
now, we generate rows to break this into 30 minute periods using the model clause.
SQL> with foo as (select rownum id, emp, start_time, end_time, code,
2 trunc(start_time, 'mi')
3 - (mod(to_char(trunc(start_time, 'mi'), 'mi'), 30) / 1440) start_block,
4 ceil(2*24*(end_time-(trunc(start_time, 'mi')
5 - (mod(to_char(trunc(start_time, 'mi'), 'mi'), 30) / 1440)))) blocks
6 from tab f)
7 select trunc(start_time) thedate, code, emp, range, minutes
8 from foo
9 model partition by(id)
10 dimension by(0 as f)
11 measures(code, emp, start_time, end_time, start_block, blocks,
12 sysdate as start_range,
13 sysdate as end_range,
14 cast(0 as number) minutes,
15 cast('' as varchar2(50)) range)
16 rules (start_range [for f from 0 to blocks[0]-1 increment 1] = start_block[0] + (30*cv(f)/1440),
17 end_range[any] = start_range[cv()] + (30/1440),
18 code[any] = code[0],
19 emp[any] = emp[0],
20 start_time[any] = start_time[0],
21 end_time[any] = end_time[0],
22 range [any] = to_char(start_range[cv()], 'dd/mm/yyyy hh:mi:ss am') || ' - ' || to_char(end_range[cv()], 'dd/mm/yyyy hh24:mi:ss am'),
23 minutes [any] = case
24 when start_time[0] between start_range[cv()] and end_range[cv()]
25 then 1440 *(end_range[cv()] - start_time[0])
26 when end_time[0] between start_range[cv()] and end_range[cv()]
27 then 1440 *(end_time[0] - start_range[cv()])
28 else 1440 * (end_range[cv()] - start_range[cv()])
29 end );
CO EM RANGE MINUTES
-- -- -------------------------------------------------- ----------
C2 E2 11/01/2012 06:30:00 am - 11/01/2012 07:00:00 am 3
C2 E2 11/01/2012 07:00:00 am - 11/01/2012 07:30:00 am 30
C2 E2 11/01/2012 07:30:00 am - 11/01/2012 08:00:00 am 30
C1 E1 11/01/2012 06:30:00 am - 11/01/2012 07:00:00 am 5
C1 E1 11/01/2012 07:00:00 am - 11/01/2012 07:30:00 am 1
C1 E1 11/02/2012 07:30:00 am - 11/02/2012 08:00:00 am 25
C1 E1 11/02/2012 08:00:00 am - 11/02/2012 08:30:00 am 1
C2 E1 11/01/2012 06:30:00 am - 11/01/2012 07:00:00 am 3
C2 E1 11/01/2012 07:00:00 am - 11/01/2012 07:30:00 am 30
C2 E1 11/01/2012 07:30:00 am - 11/01/2012 08:00:00 am 30
C2 E1 11/01/2012 08:00:00 am - 11/01/2012 08:30:00 am 1
11 rows selected.
so we are partitioning by:
partition by(id)
ie by a unique reference. then we are going to generate rows with our dimension
dimension by(0 as f)
in conjuction with part of the rules:
for f from 0 to blocks[0]-1 increment 1
so the start_range column is generated with
start_range [for f from 0 to blocks[0]-1 increment 1] = start_block[0] + (30*cv(f)/1440),
start_block[0] is in the first query, eg:
EM START_TIME END_TIME CO START_BLOCK BLOCKS
-- ---------------------- ---------------------- -- ---------------------- ----------
E1 11/01/2012 06:55:00 am 11/01/2012 07:01:00 am C1 11/01/2012 06:30:00 am 2
so for this row, it evaluates to
start_range[0 to 1] = 11/01/2012 06:30:00 am + (30minutes * the value of f)
i.e.
start_range[0] = 11/01/2012 06:30:00 am + (30min*0) = 11/01/2012 06:30:00 am
start_range[1] = 11/01/2012 06:30:00 am + (30min*1) = 11/01/2012 07:00:00 am
the rest is pretty straight forward:
end_range[any] = start_range[cv()] + (30/1440),
means that for end-range on the current row, we take start_range and add 30 minutes.
the range column is a concatenation of start_range and end_range:
range [any] = to_char(start_range[cv()], 'dd/mm/yyyy hh:mi:ss am') || ' - ' || to_char(end_range[cv()], 'dd/mm/yyyy hh24:mi:ss am'),
finally in order to calc minutes in that range:
minutes [any] = case
when start_time[0] between start_range[cv()] and end_range[cv()]
then 1440 *(end_range[cv()] - start_time[0])
when end_time[0] between start_range[cv()] and end_range[cv()]
then 1440 *(end_time[0] - start_range[cv()])
else 1440 * (end_range[cv()] - start_range[cv()])
end );
if start_time sits in the range, take the end of the range - start
time
if end_time sits in the range, take the end_time - start of the range
otherwise its end_range - start_range.
1440 just gets the answer as minutes.
now we can just group that all up:
SQL> with foo as (select rownum id, emp, start_time, end_time, code,
2 trunc(start_time, 'mi')
3 - (mod(to_char(trunc(start_time, 'mi'), 'mi'), 30) / 1440) start_block,
4 ceil(2*24*(end_time-(trunc(start_time, 'mi')
5 - (mod(to_char(trunc(start_time, 'mi'), 'mi'), 30) / 1440)))) blocks
6 from tab f)
7 select thedate, code, range, sum(minutes) minutes
8 from (select trunc(start_time) thedate, code, emp, range, minutes
9 from foo
10 model partition by(id)
11 dimension by(0 as f)
12 measures(code, emp, start_time, end_time, start_block, blocks,
13 sysdate as start_range,
14 sysdate as end_range,
15 cast(0 as number) minutes,
16 cast('' as varchar2(50)) range)
17 rules (start_range [for f from 0 to blocks[0]-1 increment 1] = start_block[0] + (30*cv(f)/1440),
18 code[any] = code[0],
19 emp[any] = emp[0],
20 end_range[any] = start_range[cv()] + (30/1440),
21 start_time[any] = start_time[0],
22 end_time[any] = end_time[0],
23 range [any] = to_char(start_range[cv()], 'dd/mm/yyyy hh:mi:ss am') || ' - ' || to_char(end_range[cv()], 'dd/mm/yyyy hh24:mi:ss am'),
24 minutes [any] = case
25 when start_time[0] between start_range[cv()] and end_range[cv()]
26 then 1440 *(end_range[cv()] - start_time[0])
27 when end_time[0] between start_range[cv()] and end_range[cv()]
28 then 1440 *(end_time[0] - start_range[cv()])
29 else 1440 * (end_range[cv()] - start_range[cv()])
30 end ))
31 group by thedate, code, range
32 order by thedate, code, range;
THEDATE CO RANGE MINUTES
---------- -- -------------------------------------------------- ----------
11/01/2012 C1 11/01/2012 06:30:00 am - 11/01/2012 07:00:00 am 5
11/01/2012 C1 11/01/2012 07:00:00 am - 11/01/2012 07:30:00 am 1
11/01/2012 C2 11/01/2012 06:30:00 am - 11/01/2012 07:00:00 am 6
11/01/2012 C2 11/01/2012 07:00:00 am - 11/01/2012 07:30:00 am 60
11/01/2012 C2 11/01/2012 07:30:00 am - 11/01/2012 08:00:00 am 60
11/01/2012 C2 11/01/2012 08:00:00 am - 11/01/2012 08:30:00 am 1
11/02/2012 C1 11/02/2012 07:30:00 am - 11/02/2012 08:00:00 am 25
11/02/2012 C1 11/02/2012 08:00:00 am - 11/02/2012 08:30:00 am 1
I am pretty sure this can be cleaned up, and made more legible and more efficient as Oracle is not one of my strong suits, but it works and should give an idea of how to accomplish the task.
The key here is joining to a list of numbers to break up your records into half hour periods.
SELECT "Date",
"Code",
"RangeStart" + ((r - 1) / 48.0) AS "RangeStart",
"RangeStart" + (r / 48.0) AS "RangeEnd",
SUM(CASE WHEN r = 1 THEN "StartMinutes"
WHEN "END_TIME" >= "RangeStart" + ((r - 1) / 48.0) AND "END_TIME" < "RangeStart" + (r / 48.0) THEN "EndMinutes"
ELSE 30
END) AS "TotalMinutes"
FROM ( SELECT "Emp",
"Date",
"START_TIME",
"END_TIME",
"Code",
CASE WHEN EXTRACT(MINUTE from "START_TIME") > 30 THEN 60 ELSE 30 END - EXTRACT(MINUTE from "START_TIME") AS "StartMinutes",
EXTRACT(MINUTE from END_TIME) - CASE WHEN EXTRACT(MINUTE from "END_TIME") > 30 THEN 30 ELSE 0 END AS "EndMinutes",
"START_TIME" - (EXTRACT(MINUTE from "START_TIME") - CASE WHEN EXTRACT(MINUTE from "START_TIME") > 30 THEN 30 ELSE 0 END) / (60 * 24.0) AS "RangeStart"
FROM T
) T
INNER JOIN
( SELECT Rownum r
FROM dual
CONNECT BY Rownum <= 100
) r
ON "END_TIME" > ("RangeStart" + ((r - 1) / 48.0))
GROUP BY "Date", "Code", "RangeStart" + ((r - 1) / 48.0), "RangeStart" + (r / 48.0)
ORDER BY "Code", "Date", "RangeStart";
EXAMPLE ON SQL FIDDLE
Here's another solution (it's not very elegant and uses hard-coded date literals to obtain the boundaries for the buckets - should probably be replaced by a sub-query to obtain them):
with v_data as (
select 1 pk, 'E1' emp, to_date('2012-11-01', 'YYYY-MM-DD') as date1, to_date('2012-11-01 06:55:00', 'YYYY-MM-DD hh24:mi:ss') as start_time, to_date('2012-11-01 07:01:00', 'YYYY-MM-DD hh24:mi:ss') as end_time, 'C1' as code, 6 as minutes from dual union all
select 2 pk, 'E1' emp, to_date('2012-11-01', 'YYYY-MM-DD') as date1, to_date('2012-11-01 06:57:00', 'YYYY-MM-DD hh24:mi:ss') as start_time, to_date('2012-11-01 08:01:00', 'YYYY-MM-DD hh24:mi:ss') as end_time, 'C2' as code, 64 as minutes from dual union all
select 3 pk, 'E2' emp, to_date('2012-11-01', 'YYYY-MM-DD') as date1, to_date('2012-11-01 06:57:00', 'YYYY-MM-DD hh24:mi:ss') as start_time, to_date('2012-11-01 08:00:00', 'YYYY-MM-DD hh24:mi:ss') as end_time, 'C2' as code, 63 as minutes from dual union all
select 4 pk, 'E1' emp, to_date('2012-11-02', 'YYYY-MM-DD') as date1, to_date('2012-11-02 07:35:00', 'YYYY-MM-DD hh24:mi:ss') as start_time, to_date('2012-11-02 08:01:00', 'YYYY-MM-DD hh24:mi:ss') as end_time, 'C1' as code, 26 as minutes from dual),
v_buckets as (
select
to_date('2012-11-01', 'YYYY-MM-DD') + (rownum-1)/48 as bucket_start,
to_date('2012-11-01', 'YYYY-MM-DD') + rownum/48 as bucket_end
from dual
connect by rownum <96
)
select v3.date1, v3.bucket_start, v3.bucket_end, v3.code, sum(v3.time_spent_in_bucket) as minutes
from (
select v2.*, (least(end_time, bucket_end) - greatest(start_time, bucket_start))*1440 as time_spent_in_bucket from
(
select buck.*,
v1.*
from v_buckets buck
join v_data v1
on (
-- time slot completely contained in one bucket
(v1.start_time >= buck.bucket_start and v1.start_time < buck.bucket_end and
v1.end_time >= buck.bucket_start and v1.end_time < buck.bucket_end)
-- time slot starts in bucket, expands to next bucket
or (v1.start_time >= buck.bucket_start and v1.start_time < buck.bucket_end and
v1.end_time >= buck.bucket_end)
-- time slot started in previous bucket, ends in this bucket)
or (v1.start_time < buck.bucket_start and v1.end_time > buck.bucket_start and
v1.end_time <= buck.bucket_end)
-- time slot began in previous bucket, expands to next bucket
or (v1.start_time < buck.bucket_start and v1.end_time >= buck.bucket_end)
)
) v2
) v3
where start_time is not null
group by date1, bucket_start, bucket_end, code
order by bucket_start, code
This is my try:
select trunc(trunc_start) as datetime, code, range , sum(duration) minutes
from (
select code, end_time, start_time, TRUNC_START ,
to_char(trunc_start,'hh:mi:ss AM')||'-'||to_char(trunc_start+1/48,'hh:mi:ss AM') as range,
case
when end_time-trunc_start between 0 and 1/48 then (end_time-trunc_start)*1440
when start_time-trunc_start between 0 and 1/48 then (trunc_start-start_time)*1440+30
else 30
end as duration
from(
select s.*, n ,
trunc(start_time) + trunc((start_time-trunc(start_time))*48)/48 + (n-1)/48 as trunc_start
from s
join (select level n from dual connect by level <=48) a
on n-2 <= (end_time-start_time)*100
)b
)
where trunc_start < end_time --eliminating fake intervals
group by code, trunc(trunc_start), range
order by 1, 3
;
sorry for the where :)
SQLFIDDLE
Here's some general example of ranges for you:
SELECT job
, sum(decode(greatest(sal,2999), least(sal,6000), 1, 0)) "Range 3000-6000"
, sum(decode(greatest(sal,1000), least(sal,2999), 1, 0)) "Range 1000-3000"
, sum(decode(greatest(sal,0), least(sal,999), 1, 0)) "Range 0-1000"
FROM scott.emp
GROUP BY job
/