Closest date/time to top of hour - oracle

I have a table with normal DATE field in it:
5/9/2022 2:32:02 AM
5/9/2022 12:33:02 PM
5/9/2022 10:34:02 AM
5/9/2022 10:20:02 AM
5/9/2022 6:54:02 AM
5/9/2022 4:28:02 AM
5/9/2022 5:08:02 AM
I need a query that I can run that will give me the record that is the closest to the top of the hour I am asking for. So like, which is closest to 5:00am, or 7:00am, or 12:00pm.
Thanks!

A query like the one below can be used to show each record that is closest to the "top of the hour". In your sample data, none of the records overlap to the nearest "top of the hour", so all the records will be shown. If you add additional dates that are closer together to the top of the hour, then only the the dates closest to the "top of the hour" will be shown.
Query
WITH
sample_dates (dt)
AS
(SELECT TO_DATE ('5/9/2022 2:32:02 AM', 'MM/DD/YYYY HH:MI:SS PM') FROM DUAL
UNION ALL
SELECT TO_DATE ('5/9/2022 12:33:02 PM', 'MM/DD/YYYY HH:MI:SS PM') FROM DUAL
UNION ALL
SELECT TO_DATE ('5/9/2022 10:34:02 AM', 'MM/DD/YYYY HH:MI:SS PM') FROM DUAL
UNION ALL
SELECT TO_DATE ('5/9/2022 10:20:02 AM', 'MM/DD/YYYY HH:MI:SS PM') FROM DUAL
UNION ALL
SELECT TO_DATE ('5/9/2022 6:54:02 AM', 'MM/DD/YYYY HH:MI:SS PM') FROM DUAL
UNION ALL
SELECT TO_DATE ('5/9/2022 4:28:02 AM', 'MM/DD/YYYY HH:MI:SS PM') FROM DUAL
UNION ALL
SELECT TO_DATE ('5/9/2022 5:08:02 AM', 'MM/DD/YYYY HH:MI:SS PM') FROM DUAL)
SELECT TO_CHAR (dt, 'MM/DD/YYYY HH:MI:SS PM') AS dt,
TO_CHAR (nearest_hour, 'MM/DD/YYYY HH:MI:SS PM') AS nearest_hour
FROM (SELECT dt,
nearest_hour,
ROW_NUMBER () OVER (PARTITION BY nearest_hour ORDER BY time_from_hour) AS time_rank
FROM (SELECT dt,
CASE
WHEN dt - TRUNC (dt, 'HH') <
ABS ((TRUNC (dt, 'HH') + INTERVAL '1' HOUR) - dt)
THEN
TRUNC (dt, 'HH')
ELSE
TRUNC (dt, 'HH') + INTERVAL '1' HOUR
END AS nearest_hour,
LEAST (dt - TRUNC (dt, 'HH'),
ABS ((TRUNC (dt, 'HH') + INTERVAL '1' HOUR) - dt)) AS time_from_hour
FROM sample_dates))
WHERE time_rank = 1
ORDER BY 1;
Result
DT NEAREST_HOUR
_________________________ _________________________
05/09/2022 02:32:02 AM 05/09/2022 03:00:00 AM
05/09/2022 04:28:02 AM 05/09/2022 04:00:00 AM
05/09/2022 05:08:02 AM 05/09/2022 05:00:00 AM
05/09/2022 06:54:02 AM 05/09/2022 07:00:00 AM
05/09/2022 10:20:02 AM 05/09/2022 10:00:00 AM
05/09/2022 10:34:02 AM 05/09/2022 11:00:00 AM
05/09/2022 12:33:02 PM 05/09/2022 01:00:00 PM

To get the closest row to a particular hour on any day then, from Oracle 12, you can use:
SELECT *
FROM table_name
ORDER BY ABS(
TRUNC(yourDate) + INTERVAL '07:00:00' HOUR TO SECOND
- yourDate
)
FETCH FIRST ROW ONLY;
Which, for the sample data:
CREATE TABLE table_name (id, yourDate) AS
SELECT 1, DATE '2022-05-09' + INTERVAL '02:32:02' HOUR TO SECOND FROM DUAL UNION ALL
SELECT 2, DATE '2022-05-09' + INTERVAL '12:33:02' HOUR TO SECOND FROM DUAL UNION ALL
SELECT 3, DATE '2022-05-09' + INTERVAL '10:34:02' HOUR TO SECOND FROM DUAL UNION ALL
SELECT 4, DATE '2022-05-09' + INTERVAL '10:20:02' HOUR TO SECOND FROM DUAL UNION ALL
SELECT 5, DATE '2022-05-09' + INTERVAL '06:54:02' HOUR TO SECOND FROM DUAL UNION ALL
SELECT 6, DATE '2022-05-09' + INTERVAL '04:28:02' HOUR TO SECOND FROM DUAL UNION ALL
SELECT 7, DATE '2022-05-09' + INTERVAL '05:08:02' HOUR TO SECOND FROM DUAL;
Outputs:
ID
YOURDATE
5
2022-05-09 06:54:02
If you want the closest hour to each of your rows then:
SELECT t.*,
TRUNC(yourDate, 'HH') + ROUND((yourDate - TRUNC(yourDate, 'HH'))*24)/24
AS nearest_hour
FROM table_name t
Outputs:
ID
YOURDATE
NEAREST_HOUR
1
2022-05-09 02:32:02
2022-05-09 03:00:00
2
2022-05-09 12:33:02
2022-05-09 13:00:00
3
2022-05-09 10:34:02
2022-05-09 11:00:00
4
2022-05-09 10:20:02
2022-05-09 10:00:00
5
2022-05-09 06:54:02
2022-05-09 07:00:00
6
2022-05-09 04:28:02
2022-05-09 04:00:00
7
2022-05-09 05:08:02
2022-05-09 05:00:00
If, for each closest hour, you want the single closest row then:
SELECT *
FROM (
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY nearest_hour ORDER BY ABS(nearest_hour - yourDate))
AS rn
FROM (
SELECT t.*,
TRUNC(yourDate, 'HH') + ROUND((yourDate - TRUNC(yourDate, 'HH'))*24)/24
AS nearest_hour
FROM table_name t
) t
)
WHERE rn = 1
Outputs:
ID
YOURDATE
NEAREST_HOUR
RN
1
2022-05-09 02:32:02
2022-05-09 03:00:00
1
6
2022-05-09 04:28:02
2022-05-09 04:00:00
1
7
2022-05-09 05:08:02
2022-05-09 05:00:00
1
5
2022-05-09 06:54:02
2022-05-09 07:00:00
1
4
2022-05-09 10:20:02
2022-05-09 10:00:00
1
3
2022-05-09 10:34:02
2022-05-09 11:00:00
1
2
2022-05-09 12:33:02
2022-05-09 13:00:00
1
db<>fiddle here

Related

I would like to find the Hours between two date time and need to exclude weekend hours( saturday & sunday) without function and procedure

i need to find the processing hours for below data between start and end date and i need to exclude saturday and sunday in between those dates
Sample data
Query i use for finding hours but not able to find the weekend calculation
SELECT
name,
StartDate,
EndDate,
to_char(StartDate, 'dd/mm/yyyy hh24:mi:ss') StartDate,
to_char(EndDate, 'dd/mm/yyyy hh24:mi:ss') EndDate,
to_char(datediff('hh', StartDate, EndDate), 'fm9999999.90') hours
FROM
MyTable
WHERE
StartDate > '01/01/2022'
AND EndDate < '08/08/2022'
ORDER BY
StartDate DESC;
Although there us no datediff command for Oracle here is an implementation, which can be used.
create or replace function datediff( p_what in varchar2, p_d1 in date, p_d2 in date ) return number
as
l_result number;
begin
select (p_d2-p_d1) *
decode( upper(p_what),
'SS', 24*60*60, 'MI', 24*60, 'HH', 24, NULL )
into l_result from dual;
return l_result;
end;
/
select datediff( 'ss',
to_date('01-AUG-2022 12:02:04', 'dd-mon-yyyy hh24:mi:ss'),
to_date('09-AUG-2022 18:22:34', 'dd-mon-yyyy hh24:mi:ss' )) seconds FROM DUAL;
SECONDS
714029.9999999999999999999999999999999999
select datediff( 'mi',
to_date('01-AUG-2022 12:02:04', 'dd-mon-yyyy hh24:mi:ss'),
to_date('09-AUG-2022 18:22:34', 'dd-mon-yyyy hh24:mi:ss' )) minutes FROM DUAL;
MINUTES
11900.5
select datediff( 'hh',
to_date('01-AUG-2022 12:02:04', 'dd-mon-yyyy hh24:mi:ss'),
to_date('09-AUG-2022 18:22:34', 'dd-mon-yyyy hh24:mi:ss' )) hours FROM DUAL;
HOURS
198.341666666666666666666666666666666667
CREATE TABLE table_name (name, startdate, enddate) AS
SELECT 'DOC1', TIMESTAMP '2022-08-15 09:00:00',
TIMESTAMP '2022-08-15 17:00:00' FROM DUAL UNION ALL
SELECT 'DOC2', TIMESTAMP'2022-08-16 09:00:00',
TIMESTAMP '2022-08-16 17:00:00' FROM DUAL UNION ALL
SELECT 'DOC3', TIMESTAMP '2022-08-17 09:00:00',
TIMESTAMP '2022-08-17 17:00:00' FROM DUAL UNION ALL
SELECT 'DOC4', TIMESTAMP '2022-08-18 09:00:00',
TIMESTAMP '2022-08-18 17:00:00' FROM DUAL UNION ALL
SELECT 'DOC5', TIMESTAMP '2022-08-19 09:00:00',
TIMESTAMP '2022-08-19 17:00:00' FROM DUAL UNION ALL
SELECT 'DOC6', TIMESTAMP '2022-08-20 09:00:00',
TIMESTAMP '2022-08-20 17:00:00' FROM DUAL UNION ALL
SELECT 'DOC7', TIMESTAMP '2022-08-21 09:00:00',
TIMESTAMP '2022-08-21 17:00:00' FROM DUAL;
SELECT
name,
StartDate,
EndDate,
datediff( 'hh', startdate, enddate)
FROM
TABLE_NAME
WHERE startdate between to_date( '01-AUG-2022 00:00:00', 'dd-mon-yyyy hh24:mi:ss' )
and to_date( '31-AUG-2022 23:59:59', 'dd-mon-yyyy hh24:mi:ss' )
AND
TO_CHAR(startdate, 'DY') NOT IN ('SAT', 'SUN');
ORDER BY 1,2
NAME STARTDATE ENDDATE DATEDIFF('HH',STARTDATE,ENDDATE)
DOC1 15-AUG-22 09.00.00.000000 AM 15-AUG-22 05.00.00.000000 PM 8
DOC2 16-AUG-22 09.00.00.000000 AM 16-AUG-22 05.00.00.000000 PM 8
DOC3 17-AUG-22 09.00.00.000000 AM 17-AUG-22 05.00.00.000000 PM 8
DOC4 18-AUG-22 09.00.00.000000 AM 18-AUG-22 05.00.00.000000 PM 8
DOC5 19-AUG-22 09.00.00.000000 AM 19-AUG-22 05.00.00.000000 PM

Why does the date only shows up but not the time in Oracle?

I've inserted a record into reserve table like this:
INSERT INTO reserve(garage_code, vunit_id, reserve_date_time_placed, renter_no)
SELECT *
FROM (SELECT vehicle_unit.garage_code, vehicle_unit.vunit_id
FROM vehicle_unit
INNER JOIN garage ON vehicle_unit.garage_code=garage.garage_code
WHERE vehicle_insurance_id ='sports-ute-449-12b' AND garage_name= 'Melbourne Central VIC'),
(SELECT TO_DATE('2019/05/04 04:00 PM', 'yyyy/mm/dd hh:mi AM') FROM dual),
(SELECT renter_no FROM RENTER WHERE renter_fname='Van' AND renter_lname='DIESEL');
However, only the specified dates shows up but not the time. I tried
(SELECT TO_CHAR(TO_DATE('2019/05/04 04:00 PM', 'yyyy/mm/dd hh:mi AM'), 'yyyy/mm/dd hh:mi AM') FROM dual)
But it returns error 'invalid string literal'?

I have one requirement where I have to show the records between specific date and time every day of one week

I have one requirement where I have to show the records between specific date and time every day in one week duration.
in one week duration( 2019-04-01 till 2019-04-06) ,for instance record of 2019-04-01 at 19 PM till 8 Am of 2019-04-02 ,and record of 2019-04-02 at 19 PM till 08 AM of 2019-04-03 and ...
would you please help me!
Use recursive query to create proper periods then join with your data or do it simpler with condition like here:
select callbegin, callerno
from table4
where callerno in ('7032','750')
and callbegin between timestamp '2019-04-01 19:00:00'
and timestamp '2019-04-06 08:00:00'
and ('19' <= to_char(callbegin, 'hh24') or to_char(callbegin, 'hh24') < '08');
demo
Here's how I understood the question.
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi';
Session altered.
SQL> break on period;
SQL> with
2 data (id, datum) as
3 (select 1, to_date('01.04.2019 15:30', 'dd.mm.yyyy hh24:mi') from dual union all
4 select 2, to_date('01.04.2019 20:00', 'dd.mm.yyyy hh24:mi') from dual union all -- 1st
5 select 3, to_date('02.04.2019 01:15', 'dd.mm.yyyy hh24:mi') from dual union all -- 1st perios
6 select 4, to_date('02.04.2019 11:00', 'dd.mm.yyyy hh24:mi') from dual union all
7 select 5, to_date('02.04.2019 23:15', 'dd.mm.yyyy hh24:mi') from dual union all -- 2nd period
8 select 6, to_date('03.04.2019 00:10', 'dd.mm.yyyy hh24:mi') from dual union all -- 2nd
9 select 7, to_date('04.04.2019 22:20', 'dd.mm.yyyy hh24:mi') from dual -- 3rd period
10 ),
11 test as
12 (select date '2019-04-01' dstart,
13 date '2019-04-06' dend
14 from dual
15 ),
16 inter as
17 (select dstart + level - 1 datum
18 from test
19 connect by level <= dend - dstart + 1
20 ),
21 from_to as
22 (select datum + 19/24 date_from,
23 lead(datum) over (order by datum) + 8/24 date_to
24 from inter
25 )
26 select f.date_From ||' - '|| f.date_to period,
27 d.id,
28 d.datum
29 from data d join from_to f on 1 = 1
30 where d.datum between f.date_from and f.date_to
31 order by f.date_From, d.id;
PERIOD ID DATUM
----------------------------------- ---------- ----------------
01.04.2019 19:00 - 02.04.2019 08:00 2 01.04.2019 20:00
3 02.04.2019 01:15
02.04.2019 19:00 - 03.04.2019 08:00 5 02.04.2019 23:15
6 03.04.2019 00:10
04.04.2019 19:00 - 05.04.2019 08:00 7 04.04.2019 22:20
SQL>
This is how to filter data by days and time by one week:
With date_list as (
Select
to_date(to_char( (sysdate - level), 'yyyymmdd') || '19', 'yyyymmddhh24') begin_time,
to_date(to_char( ((sysdate - level)+1), 'yyyymmdd') || '08', 'yyyymmddhh24') end_time
From dual connect by level <= 7
)
Select begin_time, your_table.*
From
your_table t1,
date_list t2
Where
t1.your_date between t2.begin_time and t2.end_time;

In Oracle, get a count of the number of missing intervals across date ranges

In an Oracle table I have data coming in on 15 minute intervals across many different unique IDs. For example, the data might look something like this:
ObjectID Date
123 1/1/2016 00:00:00
123 1/1/2016 00:15:00
123 1/1/2016 00:30:00
123 1/1/2016 00:45:00
123 1/1/2016 01:00:00
456 1/1/2016 00:00:00
456 1/1/2016 00:15:00
456 1/1/2016 00:30:00
456 1/1/2016 00:45:00
456 1/1/2016 01:00:00
789 1/1/2016 00:00:00
789 1/1/2016 00:15:00
789 1/1/2016 00:30:00
789 1/1/2016 00:45:00
789 1/1/2016 01:00:00
This is a very simple example. In reality, the objectIDs are not sorted in a uniform order and there's a possibility of missing intervals. Thus the data really looks more like this:
ObjectID Date
456 1/1/2016 00:15:00
456 1/1/2016 00:30:00
123 1/1/2016 00:30:00
123 1/1/2016 00:45:00
123 1/1/2016 01:00:00
456 1/1/2016 00:45:00
456 1/1/2016 01:00:00
789 1/1/2016 00:45:00
789 1/1/2016 01:00:00
789 1/1/2016 00:00:00
789 1/1/2016 00:15:00
789 1/1/2016 00:30:00
I would like to be able to get a count of the missing intervals across all of the objectIDs for a specific date range. In the above, there are three missing intervals:
123 1/1/2016 00:00:00
123 1/1/2016 00:15:00
456 1/1/2016 00:00:00
I'm looking for a query that would catch these and return a count of 3 if given a date range of 1/1/2016 00:00:00 - 1/1/2016 1:00:00.
I think something like this may work. May be more efficient method.
select count(*) from your_table
where your_table.date not in
(
SELECT dateval
FROM
( WITH dates AS
(SELECT to_date('01/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS') dstart,
to_date('01/01/2016 01:00:00','MM/DD/YYYY HH24:MI:SS') dend
FROM dual
)
SELECT dstart + rownum/96.0 dateval
FROM dates
CONNECT BY rownum <=
(SELECT (dend - dstart)*96 FROM dates
)
)
)
The "connect by" counts the number of 15 minute intervals between the start/end date, then generates each of the date/times in 15 minute increments starting with the start date/time.
If you only need counting, an approach could be count the expected intervals and make the difference with the number of intervals really found:
with test(ObjectID, Date_) as
(
select 456, to_date('1/1/2016 00:15:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 456, to_date('1/1/2016 00:30:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 123, to_date('1/1/2016 00:30:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 123, to_date('1/1/2016 00:45:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 123, to_date('1/1/2016 01:00:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 456, to_date('1/1/2016 00:45:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 456, to_date('1/1/2016 01:00:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 789, to_date('1/1/2016 00:45:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 789, to_date('1/1/2016 01:00:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 789, to_date('1/1/2016 00:00:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 789, to_date('1/1/2016 00:15:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 789, to_date('1/1/2016 00:30:00', 'dd/mm/yyyy hh24:mi:ss') from dual
),
input_dates(start_date, end_date) as
( select to_date('1/1/2016 00:00:00', 'dd/mm/yyyy hh24:mi:ss'),
to_date('1/1/2016 01:00:00', 'dd/mm/yyyy hh24:mi:ss') from dual)
--
select (count( distinct objectId) * (input_dates.end_date - input_dates.start_date) * 24 * 5)
-
sum(
case when date_ between input_dates.start_date and input_dates.end_date
then 1
else 0
end
) as missing
from test, input_dates
In case of an objectID that is in your table, but does not have any interval in the input period, this will return 5, to say that it's missing all the intervals.
If you want to check only missing intervals on IDs having at least one interval in the period, you can simply remove the BETWEEN condition from the SUM and add in as WHERE clause
Let me take a shot at it.... (Aleksej i took off from your answer).
with
--
-- Test case supplied
--
test(ObjectID, Date_) as
(
select 456, to_date('1/1/2016 00:15:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 456, to_date('1/1/2016 00:30:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 123, to_date('1/1/2016 00:30:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 123, to_date('1/1/2016 00:45:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 123, to_date('1/1/2016 01:00:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 456, to_date('1/1/2016 00:45:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 456, to_date('1/1/2016 01:00:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 789, to_date('1/1/2016 00:45:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 789, to_date('1/1/2016 01:00:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 789, to_date('1/1/2016 00:00:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 789, to_date('1/1/2016 00:15:00', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 789, to_date('1/1/2016 00:30:00', 'dd/mm/yyyy hh24:mi:ss') from dual
),
--
-- Intervals generates all possible 15 minutes intervals within the given parameters
--
intervals (Date_) as
(
select to_date('1/1/2016 00:00:00', 'dd/mm/yyyy hh24:mi:ss') + ((15/1440) * (level - 1))
from dual
connect by level <= 24 * (to_date('1/1/2016 01:00:00', 'dd/mm/yyyy hh24:mi:ss') - to_date('1/1/2016 00:00:00', 'dd/mm/yyyy hh24:mi:ss')) * 4
)
--
-- Simple minus to find the missing intervals
--
select distinct test.ObjectID, to_char(intervals.Date_ ,'YYYY/MM/DD HH24:MI:SS') dt
from test,
intervals
minus
select test.ObjectID, to_char(test.Date_,'YYYY/MM/DD HH24:MI:SS') dt
from test
/
When I execute the missing intervals:
OBJECTID DT
---------- -------------------
123 2016/01/01 00:00:00
123 2016/01/01 00:15:00
456 2016/01/01 00:00:00

How to convert timestamp to yyyy-mm-dd hh24:mi:ss format in oracle

How to convert 26-Mar-15 03.42.43.601000000 pm to yyyy-mm-dd hh24:mi:ss. Can anyone help me on this ?
First convert your string/varchar2 into a timestamp, and then format it back to a string with your format:
SQL> select to_char
2 ( to_timestamp('26-Mar-15 03.42.43.601000000 pm','dd-Mon-rr hh.mi.ss.ff9 am')
3 , 'yyyy-mm-dd hh24:mi:ss'
4 )
5 from dual
6 /
TO_CHAR(TO_TIMESTAM
-------------------
2015-03-26 15:42:43
1 row selected.
Yo can convert the date string to timestamp by;
select to_timestamp('26-Mar-15 03.42.43.601000000 pm', 'dd-mon-yy hh.mi.ss.FF9 AM')
from DUAL
If you want to get the data in yyyy-mm-dd hh24:mi:ss then use to_char function
select
to_char(
to_timestamp('26-Mar-15 03.42.43.601000000 pm', 'dd-mon-yy hh.mi.ss.FF9 AM'),
'yyyy-mm-dd hh24:mi:ss'
)
from DUAL

Resources