Difference in Start and End times grouping by hour - sql

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
/

Related

Generate Appointment Time Slots

I have branch timing view as follows. Date will change based on sysdate
TIME_FROM TIME_TO
09/08/2020 07:00:00 AM 09/08/2020 02:00:00 PM
09/08/2020 04:00:00 PM 09/08/2020 06:00:00 PM
I want to generate appointment slots with 60 minutes duration like the following. 60 minutes is variable and i will pass it as parameter. I want to get the query result like this
7.00 AM
8.00 AM
9.00 AM
10.00 AM
11.00 AM
12.00 PM
1.00 PM
4.00 PM
5.00 PM
Exclude shift ending times( 2.00 PM and 06:00 PM) as no point in including them
Here is a recursive CTE approach:
with cte (time_from, time_to, lev) as (
select time_from, time_to, 1 as lev
from t
union all
select time_from + interval '1' hour, time_to, lev + 1
from cte
where time_from < time_to - interval '1' hour
)
select time_from
from cte;
And a db<>fiddle.
Another, non-recursive CTE approach, might be
SQL> with test (time_from, time_to) as
2 (select to_date('09.08.2020 07:00', 'dd.mm.yyyy hh24:mi'),
3 to_date('09.08.2020 14:00', 'dd.mm.yyyy hh24:mi')
4 from dual union all
5 select to_date('09.08.2020 16:00', 'dd.mm.yyyy hh24:mi'),
6 to_date('09.08.2020 18:00', 'dd.mm.yyyy hh24:mi')
7 from dual
8 )
9 select time_from + ((column_value - 1) * 60) / (24 * 60) time
10 from test cross join
11 table(cast(multiset(select level from dual
12 connect by level <= (time_to - time_from) * 24
13 ) as sys.odcinumberlist));
TIME
----------------
09.08.2020 07:00
09.08.2020 08:00
09.08.2020 09:00
09.08.2020 10:00
09.08.2020 11:00
09.08.2020 12:00
09.08.2020 13:00
09.08.2020 16:00
09.08.2020 17:00
9 rows selected.
SQL>
These are dates with times - you'd apply TO_CHAR with desired format mask to display it as you want, e.g.
select to_char(time_from + ((column_value - 1) * 60) / (24 * 60), 'hh:mi am') time
which results in
TIME
--------
07:00 AM
08:00 AM
09:00 AM
10:00 AM
11:00 AM
12:00 PM
01:00 PM
04:00 PM
05:00 PM
9 rows selected.
If you want to use "number of minutes" as parameter, then modify lines #9 and #12:
SQL> with test (time_from, time_to) as
2 (select to_date('09.08.2020 07:00', 'dd.mm.yyyy hh24:mi'),
3 to_date('09.08.2020 14:00', 'dd.mm.yyyy hh24:mi')
4 from dual union all
5 select to_date('09.08.2020 16:00', 'dd.mm.yyyy hh24:mi'),
6 to_date('09.08.2020 18:00', 'dd.mm.yyyy hh24:mi')
7 from dual
8 )
9 select to_char(time_from + ((column_value - 1) * &&par_minutes) / (24 * 60), 'hh:mi am') time
10 from test cross join
11 table(cast(multiset(select level from dual
12 connect by level <= (time_to - time_from) * 24 * (60 / &&par_minutes)
13 ) as sys.odcinumberlist));
Enter value for par_minutes: 20
old 9: select to_char(time_from + ((column_value - 1) * &&par_minutes) / (24 * 60), 'hh:mi am') time
new 9: select to_char(time_from + ((column_value - 1) * 20) / (24 * 60), 'hh:mi am') time
old 12: connect by level <= (time_to - time_from) * 24 * (60 / &&par_minutes)
new 12: connect by level <= (time_to - time_from) * 24 * (60 / 20)
TIME
--------
07:00 AM
07:20 AM
07:40 AM
08:00 AM
08:20 AM
08:40 AM
09:00 AM
09:20 AM
09:40 AM
10:00 AM
10:20 AM
10:40 AM
11:00 AM
11:20 AM
11:40 AM
12:00 PM
12:20 PM
12:40 PM
01:00 PM
01:20 PM
01:40 PM
04:00 PM
04:20 PM
04:40 PM
05:00 PM
05:20 PM
05:40 PM
27 rows selected.
SQL>

Add target hours onto the next working day if job is logged after a certain time

Bit of a specific request / query but hope that I can explain it correctly, and that it makes sense.
The working day is 8am-5pm Monday-Friday
Each job has a target response time eg 1 hour, 2 hours, 4 hours
Some jobs it shows the target response time as outside of the working day eg a 4 hour job logged at 4:15pm will show a target response time of 8:15pm.
What I would like to do (and not even sure if it is possible) is:
If the priority_code is GC04 (1 hour job) and the time logged is after 4pm on a Monday-Fri take whatever time is before 5pm and add the remainder on to the next working day from 8am.
So an example would be 1 hour job logged at 4:15pm on Monday would show a target response time of 8:15am on Tuesday morning. (45 minutes used on Monday and 15 minutes carried over to Tuesday).
If the priority_code is GC05 (2 hour job) and the time logged is after 3pm on a Monday-Fri take whatever time is before 5pm and add the remainder on to the next working day from 8am.
So an example would be 2 hour job logged at 3:15pm on Monday would show a target response time of 8:15am on Tuesday morning. (1 hour 45 minutes used on Monday and 15 minutes carried over to Tuesday).
If the priority_code is GC06 (4 hour job) and the time logged is after 1pm on a Monday-Fri take whatever time is before 5pm and add the remainder on to the next working day from 8am.
So an example would be 4 hour job logged at 1:15pm on Monday would show a target response time of 8:15am on Tuesday morning. (3 hours 45 minutes used on Monday and 15 minutes carried over to Tuesday).
THANK YOU TO ALEX POOLE I'VE NOW GOT IT WORKING
Coding is below
select job_number, priority_code, job_entry_date, clock_start,
target_comp_date,
case
when to_char(target_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((target_time - trunc(target_time)) * 24) >= 17
then target_time + 2 + 63/24
when floor((target_time - trunc(target_time)) * 24) >= 17
then target_time + 15/24
else target_time
end as target_time
from (
select job_number, priority_code, job_entry_date, clock_start,
TARGET_COMP_DATE,
CASE
WHEN PRIORITY_CODE IN ('GC01','GC02','GC03','GC04','GC05','GC06','GC07')
THEN
clock_start
+ case priority_code
when 'GC01' then 1
when 'GC02' then 2
when 'GC03' then 0.5
when 'GC04' then 1
when 'GC05' then 2
when 'GC06' then 4
when 'GC07' then 24
end
/ 24
ELSE
TARGET_COMP_DATE END as target_time
from (
select job_number, priority_code, job_entry_date, target_comp_date,
case
when to_char(job_entry_date, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((job_entry_date - trunc(job_entry_date)) * 24) >= 17
then trunc(job_entry_date) + 80/24
when to_char(job_entry_date, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sat'
then trunc(job_entry_date) + 56/24
when to_char(job_entry_date, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') =
'Sun'
or floor((job_entry_date - trunc(job_entry_date)) * 24) >= 17
then trunc(job_entry_date) + 32/24
when floor((job_entry_date - trunc(job_entry_date)) * 24) < 8
then trunc(job_entry_date) + 8/24
else job_entry_date
end as clock_start
from job
)
)
A slightly convoluted approach, which assumes your logged_time column is a timestamp (easy to adapt if it's a date), and that it can't be out-of-hours:
select id, priority_code, logged_time,
logged_time
+
-- response time
(
interval '1' hour
* case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
)
+
-- actual time adjustment
(
-- possible time adjustment...
(
-- gap between 17:00 and 08:00
interval '15' hour
+
-- weekend days, only if Friday
(
interval '2' day
* case when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
then 1 else 0 end
)
)
*
-- ... but only if target exceeds 17:00
case when extract
(
hour from logged_time
+
-- response time
(
interval '1' hour
* case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
)
) > 16 then 1 else 0 end
)
as target_time
from your_table;
which with some sample data like yours and just before your cut-offs, both on a Friday and Monday, gives:
ID PRIO LOGGED_TIME TARGET_TIME
---------- ---- --------------------- ---------------------
1 GC06 2019-05-26 12:59:59.0 2019-05-26 16:59:59.0
2 GC06 2019-05-26 13:15:00.0 2019-05-27 08:15:00.0
3 GC05 2019-05-26 14:59:59.0 2019-05-26 16:59:59.0
4 GC05 2019-05-26 15:15:00.0 2019-05-27 08:15:00.0
5 GC04 2019-05-26 15:59:59.0 2019-05-26 16:59:59.0
6 GC04 2019-05-26 16:15:00.0 2019-05-27 08:15:00.0
7 GC06 2019-05-31 12:59:59.0 2019-05-31 16:59:59.0
8 GC06 2019-05-31 13:15:00.0 2019-06-03 08:15:00.0
9 GC05 2019-05-31 14:59:59.0 2019-05-31 16:59:59.0
10 GC05 2019-05-31 15:15:00.0 2019-06-03 08:15:00.0
11 GC04 2019-05-31 15:59:59.0 2019-05-31 16:59:59.0
12 GC04 2019-05-31 16:15:00.0 2019-06-03 08:15:00.0
You can reduce some of the duplication with a CTE or inline view:
select id, priority_code, logged_time,
raw_target_time
+
-- actual time adjustment
(
-- possible time adjustment...
(
-- gap between 17:00 and 08:00
interval '15' hour
+
-- weekend days, only if Friday
(
interval '2' day
* case when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
then 1 else 0 end
)
)
*
-- ... but only if target exceeds 17:00
case when extract (hour from raw_target_time) > 16 then 1 else 0 end
)
as target_time
from (
select id, priority_code, logged_time,
logged_time
+
-- response time
(
interval '1' hour
* case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
)
as raw_target_time
from your_table
);
and of course it doesn't need to be laid out like that, I was just trying to make the logic a bit clearer.
jobs can be logged at any time of the day using online web-forms, not just between 8am-5pm
This means that if a job is logged out-of-hours you need to treat it as if it was actually logged at the start of the next working day. (Note that I'm following your question in treating all Mon-Fri as working days - there is nothing in your question about public holidays for instance. Dealing with those would probably be a separate question.)
If you want to break it down you can first figure out when the clock starts on a given job, based on whether it was logged inside or outside the workday. There are a few ways to do this but since you have to deal with weekends I've chosen to do this as:
select id, priority_code, logged_time,
case
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((logged_time - trunc(logged_time)) * 24) >= 17
then trunc(logged_time) + 56/24
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sat'
then trunc(logged_time) + 56/24
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sun'
or floor((logged_time - trunc(logged_time)) * 24) >= 17
then trunc(logged_time) + 32/24
when floor((logged_time - trunc(logged_time)) * 24) < 8
then trunc(logged_time) + 8/24
else logged_time
end as clock_start
from your_table;
The floor((logged_time - trunc(logged_time)) * 24) gives you the hour that a job was logged, so you can see if that was lower than 8 (i.e. 8am) or greater than or equal to 17 (i.e. 5pm). Jobs logged at or after 17:00 on Friday or any time at weekends have their clock-start time pushed to the following Monday; jobs logged at or after 17:00 on other days are pushed to the following morning. That's using date arithmetic - 8/24 is 8 hours, 32/24 is 1 day and 8 hours, 56/24 is 2 days and 8 hours etc.
You can then put that into an inline view or CTE to simplify further calculations:
with cte1 (id, priority_code, logged_time, clock_start) as (
...
)
select id, priority_code, logged_time, clock_start,
clock_start
+ case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
/ 24 as target_time
from cte1;
which will give you the basic target time; and then you can adjust that using similar logic to my earlier answer about timestamps and working-day-only logging, but this time using more date manipulation with fractional days instead of with intervals:
with cte1 (id, priority_code, logged_time, clock_start) as (
select id, priority_code, logged_time,
case
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((logged_time - trunc(logged_time)) * 24) >= 17
then trunc(logged_time) + 80/24
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sat'
then trunc(logged_time) + 56/24
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sun'
or floor((logged_time - trunc(logged_time)) * 24) >= 17
then trunc(logged_time) + 32/24
when floor((logged_time - trunc(logged_time)) * 24) < 8
then trunc(logged_time) + 8/24
else logged_time
end as clock_start
from your_table
),
cte2 (id, priority_code, logged_time, clock_start, target_time) as (
select id, priority_code, logged_time, clock_start,
clock_start
+ case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
/ 24 as target_time
from cte1
)
select id, priority_code, logged_time, clock_start,
case
when to_char(target_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((target_time - trunc(target_time)) * 24) >= 17
then target_time + 63/24
when floor((target_time - trunc(target_time)) * 24) >= 17
then target_time + 15/24
else target_time
end as target_time
from cte2;
which with some made-up data for the scenarios I've thought about gives, with the days added just for info to make things hopefully slightly clearer:
ID PRIO LOGGED_TIME CLOCK_START TARGET_TIME LOGGED_DAY CLOCK_DAY TARGET_DAY
--- ---- ------------------- ------------------- ------------------- ---------- --------- ----------
1 GC06 2019-05-27 12:59:59 2019-05-27 12:59:59 2019-05-27 16:59:59 Mon Mon Mon
2 GC06 2019-05-27 13:15:00 2019-05-27 13:15:00 2019-05-28 08:15:00 Mon Mon Tue
3 GC05 2019-05-27 14:59:59 2019-05-27 14:59:59 2019-05-27 16:59:59 Mon Mon Mon
4 GC05 2019-05-27 15:15:00 2019-05-27 15:15:00 2019-05-28 08:15:00 Mon Mon Tue
5 GC04 2019-05-27 15:59:59 2019-05-27 15:59:59 2019-05-27 16:59:59 Mon Mon Mon
6 GC04 2019-05-27 16:15:00 2019-05-27 16:15:00 2019-05-28 08:15:00 Mon Mon Tue
7 GC04 2019-05-27 16:59:59 2019-05-27 16:59:59 2019-05-28 08:59:59 Mon Mon Tue
8 GC04 2019-05-27 17:00:00 2019-05-28 08:00:00 2019-05-28 09:00:00 Mon Tue Tue
9 GC04 2019-05-28 07:59:59 2019-05-28 08:00:00 2019-05-28 09:00:00 Tue Tue Tue
10 GC04 2019-05-28 08:00:00 2019-05-28 08:00:00 2019-05-28 09:00:00 Tue Tue Tue
11 GC06 2019-05-31 12:59:59 2019-05-31 12:59:59 2019-05-31 16:59:59 Fri Fri Fri
12 GC06 2019-05-31 13:15:00 2019-05-31 13:15:00 2019-06-03 08:15:00 Fri Fri Mon
13 GC05 2019-05-31 14:59:59 2019-05-31 14:59:59 2019-05-31 16:59:59 Fri Fri Fri
14 GC05 2019-05-31 15:15:00 2019-05-31 15:15:00 2019-06-03 08:15:00 Fri Fri Mon
15 GC04 2019-05-31 15:59:59 2019-05-31 15:59:59 2019-05-31 16:59:59 Fri Fri Fri
16 GC04 2019-05-31 16:15:00 2019-05-31 16:15:00 2019-06-03 08:15:00 Fri Fri Mon
17 GC04 2019-05-31 16:59:59 2019-05-31 16:59:59 2019-06-03 08:59:59 Fri Fri Mon
18 GC04 2019-05-31 17:00:00 2019-06-03 08:00:00 2019-06-03 09:00:00 Fri Mon Mon
19 GC04 2019-06-01 12:00:00 2019-06-03 08:00:00 2019-06-03 09:00:00 Sat Mon Mon
20 GC04 2019-06-02 12:00:00 2019-06-03 08:00:00 2019-06-03 09:00:00 Sun Mon Mon
21 GC04 2019-06-03 07:59:59 2019-06-03 08:00:00 2019-06-03 09:00:00 Mon Mon Mon
22 GC04 2019-06-03 08:00:00 2019-06-03 08:00:00 2019-06-03 09:00:00 Mon Mon Mon
db<>fiddle
Note that the CTE construct is providing aliases for the column expression from the with (...) clause, and that those do not have table aliases - as those aliases are unrelated to the tables inside the CTE. So it's with cte1 (id, priority_code, ... and not with cte1 (your_table.id, your_table.priority_code, ....
Also note that the semicolon at the end is a statement separator, required or optional (or configurable) in some clients, but invalid in others - it can cause ORA-00933 or ORA-00911 errors, and possibly others, in dynamic SQL, JDBC etc.; so ODBC probably also doesn't expect to see that final semicolon character.
If ODBC (or your version) doesn't allow CTEs - suggested by the 'not a SELECT statement' error you mentioned in a comment - then you can use inline views instead:
select id, priority_code, logged_time, clock_start,
case
when to_char(target_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((target_time - trunc(target_time)) * 24) >= 17
then target_time + 2 + 63/24
when floor((target_time - trunc(target_time)) * 24) >= 17
then target_time + 15/24
else target_time
end as target_time
from (
select id, priority_code, logged_time, clock_start,
clock_start
+ case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
/ 24 as target_time
from (
select id, priority_code, logged_time,
case
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Fri'
and floor((logged_time - trunc(logged_time)) * 24) >= 17
then trunc(logged_time) + 80/24
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sat'
then trunc(logged_time) + 56/24
when to_char(logged_time, 'Dy', 'NLS_DATE_LANGUAGE=ENGLISH') = 'Sun'
or floor((logged_time - trunc(logged_time)) * 24) >= 17
then trunc(logged_time) + 32/24
when floor((logged_time - trunc(logged_time)) * 24) < 8
then trunc(logged_time) + 8/24
else logged_time
end as clock_start
from your_table
)
);
Added to db<>fiddle.
It's a little unclear but to keep a pre-calculated target_comp_date for other priorities you could change the first inline view (based on cte2) to have nested case expressions:
...
from (
select id, priority_code, logged_time, clock_start,
case when priority_code in ('GC04', 'GC05', 'GC06') then
-- for these, calculate the target time based on clock-start as before
clock_start
+ case priority_code when 'GC04' then 1 when 'GC05' then 2 when 'GC06' then 4 end
/ 24
else
-- for any other priority use the original pre-calculated time
target_comp_date
end as target_time
from (
select id, priority_code, logged_time, target_comp_date,
...
The innermost inline view needs to include that extra column in its select list, so it's visible to that nested case expression.
The system will show the target_comp_date incorrect as 12am
You should probably be fixing that existing code then, rather than trying to fudge the result it gives you.

Oracle, SQL, how to get intervals between dates

I need help with a problem. Actually, I do not know if it will be possible to solve it directly in SQL.
I have a list of works. Each work has a start date and ending date, with this format
YYYY/MM/DD HH24:MI:SS
I need to calculate the cost of those jobs, the hour price depends on the time intervals in which the work has been done:
Nigth time: 22:00 to 6:00, for example: 20 €/h
Normal time: the rest 17 €/h
So, if I have a sample like this:
wo start end
21 2017/11/16 21:25:00 2017/11/16 22:55:00
22 2017/11/17 05:45:00 2017/11/17 07:05:00
23 2017/11/18 23:00:00 2017/11/19 1:10:00
24 2017/11/17 18:00:00 2017/11/17 19:00:00
I would need to calculate the intervals of the dates between the 22h and 6h and the rest to multiply them by their corresponding price
wo rest(minutes) night(minutes)
21 35 55
22 15 65
23 0 130
24 1 0
Thank for your help in advance.
Heh. If you really wish it :)
Fifth record (started at 2016-10-30) had been added for testing purposes.
SQL> with
2 src as (select timestamp '2017-11-16 21:25:00' b, timestamp '2017-11-16 22:55:00' f from dual union all
3 select timestamp '2017-11-17 05:45:00' b, timestamp '2017-11-17 07:05:00' f from dual union all
4 select timestamp '2017-11-18 23:00:00' b, timestamp '2017-11-19 1:10:00' f from dual union all
5 select timestamp '2017-11-17 18:00:00' b, timestamp '2017-11-17 19:00:00' f from dual union all
6 select timestamp '2016-10-30 00:00:00' b, timestamp '2016-11-03 23:00:00' f from dual),
7 srd as (select b, f, f - b t from src),
8 mmm as (select min(trunc(b)) b, max(trunc(f)) f from src),
9 rws as (select b + 6/24 + rownum - 1 b, b + 22/24 + rownum - 1 f from mmm connect by level <= f - b + 1),
10 mix as (select s.b, s.f, s.t, r.b rb, r.f rf from srd s, rws r where s.f >= r.b (+) and r.f (+) >= s.b),
11 clc as (select b, f, t, nvl(numtodsinterval(sum((least(f, rf) + 0) - (greatest(b, rb) + 0)), 'DAY'), interval '0' second) d from mix group by b, f, t)
12 select
13 to_char(b, 'dd.mm.yyyy hh24:mi') as "datetime begin",
14 to_char(f, 'dd.mm.yyyy hh24:mi') as "datetime finish",
15 cast(t as interval day to second(0)) as "total time",
16 cast(d as interval day to second(0)) as "daytime",
17 cast(t - d as interval day to second(0)) as "nighttime"
18 from
19 clc
20 order by
21 1, 2;
datetime begin datetime finish total time daytime nighttime
------------------ ------------------ -------------- -------------- --------------
16.11.2017 21:25 16.11.2017 22:55 +00 01:30:00 +00 00:35:00 +00 00:55:00
17.11.2017 05:45 17.11.2017 07:05 +00 01:20:00 +00 01:05:00 +00 00:15:00
17.11.2017 18:00 17.11.2017 19:00 +00 01:00:00 +00 01:00:00 +00 00:00:00
18.11.2017 23:00 19.11.2017 01:10 +00 02:10:00 +00 00:00:00 +00 02:10:00
30.10.2016 00:00 03.11.2016 23:00 +04 23:00:00 +03 08:00:00 +01 15:00:00
A different approach is more brute force one, but it allows to distinct the interval configuration from the reporting.
It goes in three stept:
1) define the rate type for aech minute of the day (change the granularity if required)
create table day_config as
with helper as (
select
rownum -1 minute_id
from dual connect by level <= 24*60),
helper2 as (
select
minute_id,
trunc(minute_id/60) hour_no,
mod(minute_id,60) minute_no
from helper)
select
minute_id,hour_no, minute_no,
case when hour_no >= 22 or hour_no <= 5 then 0 else 1 end rate_id
from helper2;
select * from day_config order by minute_id;
MINUTE_ID HOUR_NO MINUTE_NO RATE_ID
---------- ---------- ---------- ----------
0 0 0 0
1 0 1 0
2 0 2 0
3 0 3 0
4 0 4 0
5 0 5 0
6 0 6 0
7 0 7 0
8 0 8 0
9 0 9 0
Here rate_id means nigth, rate_id 1 means a day.
Advantage is, that you can introduce as much rate types as required.
2) expand the configuration for the required interval e.g. to whole year.
So now we have for each minute of the year the configuration, which rate is to be applied.
create or replace view year_config as
select my_date + MINUTE_ID / (24*60) minute_ts , MINUTE_ID, HOUR_NO, MINUTE_NO, RATE_ID from day_config
cross join
(select DATE '2017-01-01' + rownum -1 as my_date from dual connect by level <= 365)
order by 1,2;
select * from (
select * from year_config
order by 1)
where rownum <= 5;
MINUTE_TS MINUTE_ID HOUR_NO MINUTE_NO RATE_ID
------------------- ---------- ---------- ---------- ----------
01-01-2017 00:00:00 0 0 0 0
01-01-2017 00:01:00 1 0 1 0
01-01-2017 00:02:00 2 0 2 0
01-01-2017 00:03:00 3 0 3 0
01-01-2017 00:04:00 4 0 4 0
3) the reporting is as easy as joining to our config table constraining the interval (half open) and grouping in the RATE.
select b, f,RATE_ID, count(*) minute_cnt
from tst join year_config c on c.MINUTE_TS >= tst.b and c.MINUTE_TS < tst.f
group by b, f,RATE_ID
order by b, f,RATE_ID;
B F RATE_ID MINUTE_CNT
------------------- ------------------- ---------- ----------
16-11-2017 21:25:00 16-11-2017 22:55:00 0 55
16-11-2017 21:25:00 16-11-2017 22:55:00 1 35
17-11-2017 05:45:00 17-11-2017 07:05:00 0 15
17-11-2017 05:45:00 17-11-2017 07:05:00 1 65
17-11-2017 18:00:00 17-11-2017 19:00:00 1 60
18-11-2017 23:00:00 19-11-2017 01:10:00 0 130
The easiest way is probably to get all minutes worked in a recursive WITH clause and then see in which time range the minutes fall. As Oracle doesn't have a TIME datatype unfortunately, we'll have to work with times strings ('00'00' till '23:59').
with shifts as
(
select 'night' as shift, '00:00' as starttime, '05:59' as endtime, 20 as cost from dual
union all
select 'normal' as shift, '06:00' as starttime, '21:59' as endtime, 17 as cost from dual
union all
select 'night' as shift, '22:00' as starttime, '23:59' as endtime, 20 as cost from dual
)
, workminutes(wo, workminute, thetime, endtime) as
(
select wo, to_char(starttime, 'hh24:mi') as workminute, starttime as thetime, endtime
from mytable
union all
select
wo,
to_char(thetime + interval '1' minute, 'hh24:mi') as workminute,
thetime + interval '1' minute as thetime,
endtime
from workminutes
where thetime + interval '1' minute < endtime
)
select
wo,
count(case when s.shift = 'normal' then 1 end) as normal_time,
coalesce(sum(case when m.workminute between '06:00' and '21:59' then s.cost end), 0)
as normal_cost,
count(case when s.shift = 'night' then 1 end) as night_time,
coalesce(sum(case when m.workminute not between '06:00' and '21:59' then s.cost end), 0)
as night_cost,
count(*) as total_time,
coalesce(sum(s.cost), 0)
as total_cost
from workminutes m
join shifts s on m.workminute between s.starttime and s.endtime
group by wo
order by wo;
Output:
WO NORMAL_TIME NORMAL_COST NIGHT_TIME NIGHT_COST TOTAL_TIME TOTAL_COST
21 35 595 55 1100 90 1695
22 65 1105 15 300 80 1405
23 0 0 130 2600 130 2600
24 60 1020 0 0 60 1020
25 4800 81600 2340 46800 7140 128400
(This query looks a lot nicer of course, if you have a real shifts table and don't have to make one up on-the-fly. Also, you may not need all those seven columns I have in my result.)

Oracle query to fetch every minute between two timestamps

I need a oracle query which returns every minute between given two timestamps. I referred this Stack Overflow question.
Can we improve the same query?
To get all the minutes between two datetime elements using Row Generator technique, you need to convert the difference between the dates into the number of minutes. Rest remains same in the CONNECT BY clause.
For example, to get all the minutes between 11/09/2015 11:00:00 and 11/09/2015 11:15:00:
SQL> WITH DATA AS
2 (SELECT to_date('11/09/2015 11:00:00', 'DD/MM/YYYY HH24:MI:SS') date_start,
3 to_date('11/09/2015 11:15:00', 'DD/MM/YYYY HH24:MI:SS') date_end
4 FROM dual
5 )
6 SELECT TO_CHAR(date_start+(LEVEL -1)/(24*60), 'DD/MM/YYYY HH24:MI:SS') the_date
7 FROM DATA
8 CONNECT BY level <= (date_end - date_start)*(24*60) +1
9 /
THE_DATE
-------------------
11/09/2015 11:00:00
11/09/2015 11:01:00
11/09/2015 11:02:00
11/09/2015 11:03:00
11/09/2015 11:04:00
11/09/2015 11:05:00
11/09/2015 11:06:00
11/09/2015 11:07:00
11/09/2015 11:08:00
11/09/2015 11:09:00
11/09/2015 11:10:00
11/09/2015 11:11:00
11/09/2015 11:12:00
11/09/2015 11:13:00
11/09/2015 11:14:00
11/09/2015 11:15:00
16 rows selected.
Above, CONNECT BY level <= (date_end - date_start)*(24*60) +1 means that we are generating rows as many as the number (date_end - date_start)*(24*60) +1. You get 16 rows, because it includes both the start and end window for the minutes.
You can create like this if you want all minutes from sysdate to 15 NOV:
SELECT to_char(TRUNC(sysdate) + numtodsinterval(level - 1, 'minute'),
'dd.mm.yyyy hh24:mi') min
FROM dual
CONNECT BY LEVEL <=
(trunc((TO_DATE('16-NOV-2015','dd-mon-yyyy')) - sysdate) * 24 * 60);
you can also use below and give your values instead of systimestamp and systimestamp+1
select (systimestamp)+level/(24*60) as Rang_values
from
dual
connect by level
<=
(
select extract( minute from diff)+
extract(day from diff)*24*60 +
extract(hour from diff)*60 as diff
from
(
select systimestamp+1-systimestamp diff from dual
)
)

Conditional querying based on the current date & time

I have an sql query in which I need to select the records from table where the time is between 3:00 PM yesterday to 3:00 PM today if today's time is more than 3:00 PM.
If today's time is less than that, like if today's time is 1:00 PM. then then my query should take today's time as 1:00 PM (which should return me records).
I need to get the time between 3:00pm yesterday to 3:00pm today if todays time is more than 3:00pm
if todays time is less than 3:00pm then get the 3:00pm yesterday to current time today
The best way of handling this is to use an IF statement:
IF TO_CHAR(SYSDATE, 'HH24') >= 15 THEN
SELECT x.*
FROM YOUR_TABLE x
WHERE x.date_column BETWEEN TO_DATE(TO_CHAR(SYSDATE -1, 'YYYY-MM-DD')|| ' 15:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND TO_DATE(TO_CHAR(SYSDATE, 'YYYY-MM-DD')|| ' 15:00:00', 'YYYY-MM-DD HH24:MI:SS')
ELSE
SELECT x.*
FROM YOUR_TABLE x
WHERE x.date_column BETWEEN TO_DATE(TO_CHAR(SYSDATE -1, 'YYYY-MM-DD')|| ' 15:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND SYSDATE
END IF;
Conditional WHERE clauses are non-sargable.
Previously:
If I understand correctly, you want to get records within the last day. If the current time is 3 PM or later, the time should be set to 3 PM. If earlier than 3 PM, take the current time...
SELECT x.*
FROM YOUR_TABLE x
JOIN (SELECT CASE
WHEN TO_CHAR(SYSDATE, 'HH24') >= 15 THEN
TO_DATE(TO_CHAR(SYSDATE, 'YYYY-MM-DD')|| ' 15:00:00', 'YYYY-MM-DD HH24:MI:SS')
ELSE SYSDATE
END AS dt
FROM DUAL) y ON x.date_column BETWEEN dt - 1 AND dt
Note:
dt - 1 means that 24 hours will be subtracted from the Oracle DATE.
Reference:
TO_CHAR
TO_DATE
There is no need for an IF statement. This can be solved easily with simple SQL.
My table T23 has some records with dates; here is a sample with times at 3.00pm:
SQL> select id, some_date from t23
2 where to_char(some_date,'HH24') = '15'
3 /
ID SOME_DATE
---------- ---------
14 16-MAY-11
38 17-MAY-11
62 18-MAY-11
81 19-MAY-11
SQL>
As the current time is before 3.00pm my query will return records from 17-MAY and 18-MAY but not the record where ID=62...
SQL> select to_char(sysdate, 'DD-MON-YYYY HH24:MI') as time_now
2 from dual
3 /
TIME_NOW
-----------------
18-MAY-2011 10:45
SQL> select id, to_char(some_date, 'DD-MON-YYYY HH24:MI') as dt
2 from t23
3 where some_date between trunc(sysdate-1)+(15/24)
4 and least( trunc(sysdate)+(15/24), sysdate)
5 /
ID DT
---------- -----------------
38 17-MAY-2011 15:00
39 17-MAY-2011 16:00
40 17-MAY-2011 17:00
41 17-MAY-2011 18:00
42 17-MAY-2011 19:00
43 17-MAY-2011 20:00
44 17-MAY-2011 21:00
45 17-MAY-2011 22:00
46 17-MAY-2011 23:00
47 18-MAY-2011 00:00
48 18-MAY-2011 01:00
49 18-MAY-2011 02:00
50 18-MAY-2011 03:00
51 18-MAY-2011 04:00
52 18-MAY-2011 05:00
53 18-MAY-2011 06:00
54 18-MAY-2011 07:00
55 18-MAY-2011 08:00
56 18-MAY-2011 09:00
57 18-MAY-2011 10:00
20 rows selected.
SQL>