Oracle SQL Time Difference in hours - sql

I'm trying to find a way to get the holiday hours while comparing two dates.
the query below at the moment excludes the holidays and returns the working hours. I'm struggling to get it the other way. Just to return number of hours of holidays which falls between start and end date.
If the start date is of 15-04-22 16:00 and its a holiday then query should only return hours b/w 16:00 - 18:00. (work hours would be between 7:00 - 18:00)
create table holidays_tb(
holiday_date date
);
insert into holidays_tb values (TO_DATE('15/04/2022', 'DD/MM/YYYY'));
insert into holidays_tb values (TO_DATE('18/04/2022', 'DD/MM/YYYY'));
declare
v_st_date date;
v_end_date date;
return_val number;
begin
v_st_date := TO_DATE('13/04/2022 09:55:52', 'DD/MM/YYYY HH24:MI:SS');
v_end_date := TO_DATE('19/04/2022 16:30:00', 'DD/MM/YYYY HH24:MI:SS');
with all_days as
(select trunc(v_st_date) + level - 1 as a_dt
from dual
connect by level <= 1 + (v_end_date - v_st_date)
minus
select holiday_date from holidays_tb
)
select sum (11)
into return_val
from all_days
where TO_CHAR ( a_dt , 'Dy') NOT IN ('Sat', 'Sun');
dbms_output.put_line( return_val );
end;
Have been stuck at it for more than couple of hours now :|

You can get the number of holiday hours using:
DECLARE
v_st_date date := DATE '2022-04-13' + INTERVAL '0 09:55:52' DAY TO SECOND;
v_end_date date := DATE '2022-04-19' + INTERVAL '0 16:30:00' DAY TO SECOND;
v_work_day_start INTERVAL DAY(0) TO SECOND(0) := INTERVAL '0 07:00:00' DAY TO SECOND;
v_work_day_end INTERVAL DAY(0) TO SECOND(0) := INTERVAL '0 18:00:00' DAY TO SECOND;
v_hours NUMBER := EXTRACT(HOUR FROM v_work_day_end - v_work_day_start)
+ EXTRACT(MINUTE FROM v_work_day_end - v_work_day_start)/60
+ EXTRACT(MINUTE FROM v_work_day_end - v_work_day_start)/3600;
v_holiday NUMBER;
return_val number;
BEGIN
SELECT SUM(
LEAST(holiday_date + v_work_day_end, v_end_date)
- GREATEST(holiday_date + v_work_day_start, v_st_date)
) * 24
INTO v_holiday
FROM holidays_tb
WHERE holiday_date BETWEEN TRUNC(v_st_date) AND v_end_date
AND holiday_date - TRUNC(holiday_date, 'IW') < 5;
DBMS_OUTPUT.PUT_LINE( v_holiday );
END;
/
Which, for the sample data, outputs 22.
Additionally, you do not need to use a recursive query to get the number of days and can directly calculate it using:
DECLARE
v_st_date date := DATE '2022-04-13' + INTERVAL '0 09:55:52' DAY TO SECOND;
v_end_date date := DATE '2022-04-19' + INTERVAL '0 16:30:00' DAY TO SECOND;
v_work_day_start INTERVAL DAY(0) TO SECOND(0) := INTERVAL '0 07:00:00' DAY TO SECOND;
v_work_day_end INTERVAL DAY(0) TO SECOND(0) := INTERVAL '0 18:00:00' DAY TO SECOND;
v_hours NUMBER := EXTRACT(HOUR FROM v_work_day_end - v_work_day_start)
+ EXTRACT(MINUTE FROM v_work_day_end - v_work_day_start)/60
+ EXTRACT(MINUTE FROM v_work_day_end - v_work_day_start)/3600;
v_holiday NUMBER;
return_val number;
BEGIN
SELECT SUM(
LEAST(holiday_date + v_work_day_end, v_end_date)
- GREATEST(holiday_date + v_work_day_start, v_st_date)
) * 24
INTO v_holiday
FROM holidays_tb
WHERE holiday_date BETWEEN TRUNC(v_st_date) AND v_end_date
AND holiday_date - TRUNC(holiday_date, 'IW') < 5;
return_val :=
-- Full weeks
(TRUNC(v_end_date, 'IW') - TRUNC(v_st_date, 'IW')) * 5 / 7 * v_hours
-- Full days before in start week
- LEAST(TRUNC(v_st_date) - TRUNC(v_st_date, 'IW'), 5) * v_hours
-- Part days before in start week
- CASE
WHEN v_st_date - TRUNC(v_st_date, 'IW') < 5
THEN LEAST(
GREATEST(
(v_st_date - (TRUNC(v_st_date) + v_work_day_start))* 24,
0
),
v_hours
)
ELSE 0
END
-- End full days
+ LEAST(TRUNC(v_end_date) - TRUNC(v_end_date, 'IW'), 5) * v_hours
-- End part days
+ CASE
WHEN v_end_date - TRUNC(v_end_date, 'IW') < 5
THEN LEAST(
GREATEST(
(v_end_date - (TRUNC(v_end_date) + v_work_day_start))* 24,
0
),
v_hours
)
ELSE 0
END
-- Holiday hours
- v_holiday;
DBMS_OUTPUT.PUT_LINE( return_val );
END;
/
Which outputs: 28.5689 (33 hours for 3 full days less 1.5 hours at the end and almost 3 hours at the start).

Related

Add days/hours/minutes and months to time stamp

Is there a way to add days hours months and minutes into a timestamp that i want to insert the value of into a variable
I have separate variables for minutes,hours,days and months that I want to add to the timestamp
the timestamp format goes like this '04-FEB-21 10.25.12.013000 AM'
I tried using SELECT TO_TIMESTAMP(datecreated,'dd-mon-yyyy hh.mi.ss AM') + daysvar into duedate FROM dual; but it returns the error AM/A.M. or PM/P.M. required
daysvar contains the amount of days to be added to the timestamp
Thank you!
I have separate variables for minutes,hours,days and months that I want to add to the timestamp
Use NUMTODSINTERVAL:
DECLARE
datecreated VARCHAR2(30) := '04-FEB-21 10.25.12.013000 AM';
days INT := 1;
hours INT := 2;
minutes INT := 42;
seconds INT := 3;
duedate VARCHAR2(30);
BEGIN
duedate := TO_CHAR(
TO_TIMESTAMP(
datecreated,
'DD-MON-RR HH12.MI.SS.FF6 AM',
'NLS_DATE_LANGUAGE=American'
)
+ NUMTODSINTERVAL(days, 'DAY')
+ NUMTODSINTERVAL(hours, 'HOUR')
+ NUMTODSINTERVAL(minutes, 'MINUTE')
+ NUMTODSINTERVAL(seconds, 'SECOND'),
'DD-MON-RR HH12.MI.SS.FF6 AM',
'NLS_DATE_LANGUAGE=American'
);
DBMS_OUTPUT.PUT_LINE(duedate);
END;
/
db<>fiddle here
If you want to add months then use ADD_MONTHS; however, it returns a DATE (and not a TIMESTAMP) so you would lose the fractional seconds. You can add those back though:
DECLARE
datecreated VARCHAR2(30) := '04-FEB-21 10.25.12.013000 AM';
months INT := 4;
days INT := 1;
hours INT := 2;
minutes INT := 42;
seconds INT := 3;
created_date TIMESTAMP;
duedate VARCHAR2(30);
BEGIN
created_date := TO_TIMESTAMP(
datecreated,
'DD-MON-RR HH12.MI.SS.FF6 AM',
'NLS_DATE_LANGUAGE=American'
);
duedate := TO_CHAR(
CAST(ADD_MONTHS(created_date, months) AS TIMESTAMP)
+ NUMTODSINTERVAL(days, 'DAY')
+ NUMTODSINTERVAL(hours, 'HOUR')
+ NUMTODSINTERVAL(minutes, 'MINUTE')
+ NUMTODSINTERVAL(seconds, 'SECOND')
+ (created_date - CAST(created_date AS DATE)), -- Fractional seconds
'DD-MON-RR HH12.MI.SS.FF6 AM',
'NLS_DATE_LANGUAGE=American'
);
DBMS_OUTPUT.PUT_LINE(duedate);
END;
/
db<>fiddle here

How can we get the 15 minutes time interval

I am trying to fetch every 15min data in such a way that, if the current time is 23-10-19 11:11:30 then I need to get the data from 23-10-19 10:30:59 to 23-10-19 10:45:59 in the same way if the time is 23-10-19 11:15:30 then I need to get the data from 23-10-19 10:45:59 to 23-10-19 11:00:59.
I have tried forgetting the 15min delay but not the way I want to approach. Please suggest me how can we approach the scenario
select concat(to_char(current_timestamp - numtodsinterval(30,'MINUTE'),'yyyy-mm-dd hh24:mi'),':59') A,
concat(to_char(current_timestamp - numtodsinterval(15,'MINUTE'),'yyyy-mm-dd hh24:mi'),':59') B,
to_char(current_timestamp,'yyyy-mm-dd hh24:mi:ss') C from dual
below is the output that I was getting.
A B C
------------------- ------------------- -------------------
2019-10-23 13:03:59 2019-10-23 13:18:59 2019-10-23 13:33:22
You can truncate to the nearest minute to zero the seconds and then subtract the number of minutes to get back to the nearest 15 minute interval past the hour and then apply your offsets:
SELECT TRUNC( current_timestamp, 'MI' )
- MOD( EXTRACT( MINUTE FROM current_timestamp ), 15 ) * INTERVAL '1' MINUTE
- INTERVAL '30' MINUTE
+ INTERVAL '59' SECOND AS start_time,
TRUNC( current_timestamp, 'MI' )
- MOD( EXTRACT( MINUTE FROM current_timestamp ), 15 ) * INTERVAL '1' MINUTE
- INTERVAL '15' MINUTE
+ INTERVAL '59' SECOND AS end_time,
current_timestamp
FROM DUAL
Outputs:
START_TIME | END_TIME | CURRENT_TIMESTAMP
:------------------ | :------------------ | :----------------------------
2019-10-23 09:00:59 | 2019-10-23 09:15:59 | 2019-10-23 09:42:53.742684000
db<>fiddle here
Another solution is this one:
WITH t AS
(SELECT TRUNC(CURRENT_TIMESTAMP , 'hh') + TRUNC(EXTRACT(MINUTE FROM CURRENT_TIMESTAMP ) / 15) * INTERVAL '15' MINUTE - INTERVAL '30' MINUTE AS Base
FROM dual)
SELECT Base + INTERVAL '59' SECOND AS begin_time,
Base + INTERVAL '15:59' minute to SECOND AS end_time
FROM t;
It is based on my generic Interval function:
CREATE OR REPLACE FUNCTION MakeInterval(ts IN TIMESTAMP, roundInterval IN INTERVAL DAY TO SECOND) RETURN TIMESTAMP DETERMINISTIC IS
denom INTEGER;
BEGIN
IF roundInterval >= INTERVAL '1' HOUR THEN
denom := EXTRACT(HOUR FROM roundInterval);
IF MOD(24, denom) <> 0 THEN
RAISE VALUE_ERROR;
END IF;
RETURN TRUNC(ts) + TRUNC(EXTRACT(HOUR FROM ts) / denom) * denom * INTERVAL '1' HOUR;
ELSIF roundInterval >= INTERVAL '1' MINUTE THEN
denom := EXTRACT(MINUTE FROM roundInterval);
IF MOD(60, denom) <> 0 THEN
RAISE VALUE_ERROR;
END IF;
RETURN TRUNC(ts, 'hh') + TRUNC(EXTRACT(MINUTE FROM ts) / denom) * denom * INTERVAL '1' MINUTE;
ELSE
denom := EXTRACT(SECOND FROM roundInterval);
IF MOD(60, denom) <> 0 THEN
RAISE VALUE_ERROR;
END IF;
RETURN TRUNC(ts, 'mi') + TRUNC(EXTRACT(SECOND FROM ts) / denom) * denom * INTERVAL '1' SECOND;
END IF;
END MakeInterval;
You would invoke it as
SELECT MakeInterval(CURRENT_TIMESTAMP, INTERVAL '15' MINUTE) from dual;
plus/minus your constant offsets.
select :start_date + (1/96)*(level-1) from dual connect by level < :intervals

How to convert timestamp with milliseconds to date in Oracle

I have MSSTAMP as "timestamp with milliseconds" in Oracle, format: 1483228800000. How can I cast that milliseconds timestamp into a date format "YYYY-MM", in order to get count of FINISHED rows per month for previous years.
I have tried different variations of TO_DATE, CAST, TO_CHAR - but I'm unable to get this working.
select
count(*) "EVENTS",
TO_DATE(MSSTAMP, 'YYYY-MM') "FINISHED_MONTH"
from
DB_TABLE
where
MSSTAMP < '1483228800000'
and
STATUS in ('FINISHED')
group by
FINISHED_MONTH ASC
Unix Time
If you just need to convert from milliseconds since epoch to a timestamp in the UTC timezone, then:
SELECT TIMESTAMP '1970-01-01 00:00:00.000 UTC'
+ NUMTODSINTERVAL( 1483228800000 / 1000, 'SECOND' )
AS TIME
FROM DUAL
Which outputs:
TIME
2017-01-01 00:00:00.000 +00:00
It you just want the year-month then use TRUNC( timestamp, 'MM' ) or TO_CHAR( timestamp, 'YYYY-MM' ).
Real Time (with leap seconds)
If you need to handle leap seconds then you can create a utility package that will adjust the epoch time to account for this:
CREATE OR REPLACE PACKAGE time_utils
IS
FUNCTION milliseconds_since_epoch(
in_datetime IN TIMESTAMP,
in_epoch IN TIMESTAMP DEFAULT TIMESTAMP '1970-01-01 00:00:00'
) RETURN NUMBER;
FUNCTION milliseconds_epoch_to_ts (
in_milliseconds IN NUMBER,
in_epoch IN TIMESTAMP DEFAULT TIMESTAMP '1970-01-01 00:00:00'
) RETURN TIMESTAMP;
END;
/
CREATE OR REPLACE PACKAGE BODY time_utils
IS
-- List of the seconds immediately following leap seconds:
leap_seconds CONSTANT SYS.ODCIDATELIST := SYS.ODCIDATELIST(
DATE '1972-07-01',
DATE '1973-01-01',
DATE '1974-01-01',
DATE '1975-01-01',
DATE '1976-01-01',
DATE '1977-01-01',
DATE '1978-01-01',
DATE '1979-01-01',
DATE '1980-01-01',
DATE '1981-07-01',
DATE '1982-07-01',
DATE '1983-07-01',
DATE '1985-07-01',
DATE '1988-01-01',
DATE '1990-01-01',
DATE '1991-01-01',
DATE '1992-07-01',
DATE '1993-07-01',
DATE '1994-07-01',
DATE '1996-01-01',
DATE '1997-07-01',
DATE '1999-01-01',
DATE '2006-01-01',
DATE '2009-01-01',
DATE '2012-07-01',
DATE '2015-07-01',
DATE '2016-01-01'
);
HOURS_PER_DAY CONSTANT BINARY_INTEGER := 24;
MINUTES_PER_HOUR CONSTANT BINARY_INTEGER := 60;
SECONDS_PER_MINUTE CONSTANT BINARY_INTEGER := 60;
MILLISECONDS_PER_SECOND CONSTANT BINARY_INTEGER := 1000;
MINUTES_PER_DAY CONSTANT BINARY_INTEGER := HOURS_PER_DAY * MINUTES_PER_HOUR;
SECONDS_PER_DAY CONSTANT BINARY_INTEGER := MINUTES_PER_DAY * SECONDS_PER_MINUTE;
MILLISECONDS_PER_MINUTE CONSTANT BINARY_INTEGER := SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND;
MILLISECONDS_PER_HOUR CONSTANT BINARY_INTEGER := MINUTES_PER_HOUR * MILLISECONDS_PER_MINUTE;
MILLISECONDS_PER_DAY CONSTANT BINARY_INTEGER := HOURS_PER_DAY * MILLISECONDS_PER_HOUR;
FUNCTION milliseconds_since_epoch(
in_datetime IN TIMESTAMP,
in_epoch IN TIMESTAMP DEFAULT TIMESTAMP '1970-01-01 00:00:00'
) RETURN NUMBER
IS
p_leap_milliseconds BINARY_INTEGER := 0;
p_diff INTERVAL DAY(9) TO SECOND(3);
BEGIN
IF in_datetime IS NULL OR in_epoch IS NULL THEN
RETURN NULL;
END IF;
p_diff := in_datetime - in_epoch;
IF in_datetime >= in_epoch THEN
FOR i IN 1 .. leap_seconds.COUNT LOOP
EXIT WHEN in_datetime < leap_seconds(i);
IF in_epoch < leap_seconds(i) THEN
p_leap_milliseconds := p_leap_milliseconds + MILLISECONDS_PER_SECOND;
END IF;
END LOOP;
ELSE
FOR i IN REVERSE 1 .. leap_seconds.COUNT LOOP
EXIT WHEN in_datetime > leap_seconds(i);
IF in_epoch > leap_seconds(i) THEN
p_leap_milliseconds := p_leap_milliseconds - MILLISECONDS_PER_SECOND;
END IF;
END LOOP;
END IF;
RETURN MILLISECONDS_PER_SECOND * EXTRACT( SECOND FROM p_diff )
+ MILLISECONDS_PER_MINUTE * EXTRACT( MINUTE FROM p_diff )
+ MILLISECONDS_PER_HOUR * EXTRACT( HOUR FROM p_diff )
+ MILLISECONDS_PER_DAY * EXTRACT( DAY FROM p_diff )
+ p_leap_milliseconds;
END milliseconds_since_epoch;
FUNCTION milliseconds_epoch_to_ts(
in_milliseconds IN NUMBER,
in_epoch IN TIMESTAMP DEFAULT TIMESTAMP '1970-01-01 00:00:00'
) RETURN TIMESTAMP
IS
p_datetime TIMESTAMP;
BEGIN
IF in_milliseconds IS NULL OR in_epoch IS NULL THEN
RETURN NULL;
END IF;
p_datetime := in_epoch
+ NUMTODSINTERVAL( in_milliseconds / MILLISECONDS_PER_SECOND, 'SECOND' );
IF p_datetime >= in_epoch THEN
FOR i IN 1 .. leap_seconds.COUNT LOOP
EXIT WHEN p_datetime < leap_seconds(i);
IF in_epoch < leap_seconds(i) THEN
p_datetime := p_datetime - INTERVAL '1' SECOND;
END IF;
END LOOP;
ELSE
FOR i IN REVERSE 1 .. leap_seconds.COUNT LOOP
EXIT WHEN p_datetime > leap_seconds(i);
IF in_epoch > leap_seconds(i) THEN
p_datetime := p_datetime + INTERVAL '1' SECOND;
END IF;
END LOOP;
END IF;
RETURN p_datetime;
END milliseconds_epoch_to_ts;
END;
/
Then you can do:
SELECT TIME_UTILS.milliseconds_epoch_to_ts(
in_milliseconds => 1483228800000,
in_epoch => TIMESTAMP '1970-01-01 00:00:00.000'
) AS time
FROM DUAL;
And get the output:
TIME
2016-12-31 23:59:33.000
Note: you will need to keep the package up-to-date when new leap-seconds are proposed.
db<>fiddle here
Update:
Your query would then be:
SELECT COUNT(*) "EVENTS",
TRUNC(
TIMESTAMP '1970-01-01 00:00:00.000'
+ NUMTODSINTERVAL( MSSTAMP / 1000, 'SECOND' ),
'MM'
) AS FINISHED_MONTH
FROM DB_TABLE
WHERE MSSTAMP < 1483228800000
AND STATUS = 'FINISHED'
GROUP BY
TRUNC(
TIMESTAMP '1970-01-01 00:00:00.000'
+ NUMTODSINTERVAL( MSSTAMP / 1000, 'SECOND' ),
'MM'
);

Rounding to Previous 2 hour Window Oracle SQL

I'm trying to round a date (date datatype which includes timestamp too) to the previous 2 hour block. e.g 13:23 -> 12:00, 18:12 -> 18:00
I had it working in MySQL using a MOD function as:
DATE_ADD(DATE(DATE_ADD(created_at, INTERVAL - 7 HOUR)), INTERVAL HOUR(DATE_ADD(created_at, INTERVAL - 6 HOUR)) - MOD(HOUR(DATE_ADD(created_at, INTERVAL - 6 HOUR)), 2) HOUR) AS Window_Start
**Complexity added as I'm also shifting the time a) to correct for a 7 hr time zone difference and b) because I need to offset the time by 1 hr before grouping it. But that's not where the issue is.
But I can't get it to work on an Oracle platform. Specifically, I can't seem to extract the hour of the time as a number which to feed into the MOD(). I've been trying with CAST, and TO_TIMESTAMP and TO_CHAR but nothing seems to work. The usual error is "inconsistent datatypes".
EXTRACT only works with with timestamp type, not date. And TO_TIMESTAMP only works on strings. But EXTRACT(TO_TIMESTAMP(TO_CHAR( doesn't work either.
I'm sure there's an easier way to do this...
In Oracle, one method is using date arithmetic. For example:
select date '2000-01-01' + floor((sysdate - date '2000-01-01') * (24 / 2)) / (24 / 2)
from dual;
The "24" is for hours in a day. The "2" is for the two hour period you want to truncate to.
I'm not sure that this is the answer but it'll give you a rabbit hole to chase:
select sysdate as curr_dt, to_date(to_char(sysdate - interval '2' hour, 'yyyy-mm-dd HH24')) as two_hours_ago from dual;
Hope it helps!
To get the hour in a number format this will work.
select TO_NUMBER(TO_CHAR(SYSDATE,'HH')) from dual
You can select each piece individually using the time stamp portion of this date format DD/MM/YYYY HH:MI:SS.
Some time ago I wrote this generic function:
CREATE FUNCTION MakeInterval(ts IN TIMESTAMP, roundInterval IN INTERVAL DAY TO SECOND) RETURN TIMESTAMP DETERMINISTIC IS
denom INTEGER;
BEGIN
IF roundInterval >= INTERVAL '1' HOUR THEN
denom := EXTRACT(HOUR FROM roundInterval);
IF MOD(24, denom) <> 0 THEN
RAISE VALUE_ERROR;
END IF;
RETURN TRUNC(ts) + TRUNC(EXTRACT(HOUR FROM ts) / denom) * denom * INTERVAL '1' HOUR;
ELSIF roundInterval >= INTERVAL '1' MINUTE THEN
denom := EXTRACT(MINUTE FROM roundInterval);
IF MOD(60, denom) <> 0 THEN
RAISE VALUE_ERROR;
END IF;
RETURN TRUNC(ts, 'hh') + TRUNC(EXTRACT(MINUTE FROM ts) / denom) * denom * INTERVAL '1' MINUTE;
ELSE
denom := EXTRACT(SECOND FROM roundInterval);
IF MOD(60, denom) <> 0 THEN
RAISE VALUE_ERROR;
END IF;
RETURN TRUNC(ts, 'mi') + TRUNC(EXTRACT(SECOND FROM ts) / denom) * denom * INTERVAL '1' SECOND;
END IF;
END MakeInterval;
In your case the usage would be
SELECT MakeInterval(created_at, INTERVAL '2' HOUR)
FROM ...
But perhaps this would be an overkill in your situation, usage without the function would be:
SELECT TRUNC(created_at) + TRUNC(EXTRACT(HOUR FROM created_at) / 2) * 2 * INTERVAL '1' HOUR
FROM ...

Calculating difference between two timestamps in Oracle in milliseconds

How do I calculate the time difference in milliseconds between two timestamps in Oracle?
When you subtract two variables of type TIMESTAMP, you get an INTERVAL DAY TO SECOND which includes a number of milliseconds and/or microseconds depending on the platform. If the database is running on Windows, systimestamp will generally have milliseconds. If the database is running on Unix, systimestamp will generally have microseconds.
1 select systimestamp - to_timestamp( '2012-07-23', 'yyyy-mm-dd' )
2* from dual
SQL> /
SYSTIMESTAMP-TO_TIMESTAMP('2012-07-23','YYYY-MM-DD')
---------------------------------------------------------------------------
+000000000 14:51:04.339000000
You can use the EXTRACT function to extract the individual elements of an INTERVAL DAY TO SECOND
SQL> ed
Wrote file afiedt.buf
1 select extract( day from diff ) days,
2 extract( hour from diff ) hours,
3 extract( minute from diff ) minutes,
4 extract( second from diff ) seconds
5 from (select systimestamp - to_timestamp( '2012-07-23', 'yyyy-mm-dd' ) diff
6* from dual)
SQL> /
DAYS HOURS MINUTES SECONDS
---------- ---------- ---------- ----------
0 14 55 37.936
You can then convert each of those components into milliseconds and add them up
SQL> ed
Wrote file afiedt.buf
1 select extract( day from diff )*24*60*60*1000 +
2 extract( hour from diff )*60*60*1000 +
3 extract( minute from diff )*60*1000 +
4 round(extract( second from diff )*1000) total_milliseconds
5 from (select systimestamp - to_timestamp( '2012-07-23', 'yyyy-mm-dd' ) diff
6* from dual)
SQL> /
TOTAL_MILLISECONDS
------------------
53831842
Normally, however, it is more useful to have either the INTERVAL DAY TO SECOND representation or to have separate columns for hours, minutes, seconds, etc. rather than computing the total number of milliseconds between two TIMESTAMP values.
Here's a stored proc to do it:
CREATE OR REPLACE function timestamp_diff(a timestamp, b timestamp) return number is
begin
return extract (day from (a-b))*24*60*60 +
extract (hour from (a-b))*60*60+
extract (minute from (a-b))*60+
extract (second from (a-b));
end;
/
Up Vote if you also wanted to beat the crap out of the Oracle developer who negated to his job!
BECAUSE comparing timestamps for the first time should take everyone an hour or so...
Easier solution:
SELECT numtodsinterval(date1-date2,'day') time_difference from dates;
For timestamps:
SELECT (extract(DAY FROM time2-time1)*24*60*60)+
(extract(HOUR FROM time2-time1)*60*60)+
(extract(MINUTE FROM time2-time1)*60)+
extract(SECOND FROM time2-time1)
into diff FROM dual;
RETURN diff;
Select date1 - (date2 - 1) * 24 * 60 *60 * 1000 from Table;
I know that this has been exhaustively answered, but I wanted to share my FUNCTION with everyone. It gives you the option to choose if you want your answer to be in days, hours, minutes, seconds, or milliseconds. You can modify it to fit your needs.
CREATE OR REPLACE FUNCTION Return_Elapsed_Time (start_ IN TIMESTAMP, end_ IN TIMESTAMP DEFAULT SYSTIMESTAMP, syntax_ IN NUMBER DEFAULT NULL) RETURN VARCHAR2 IS
FUNCTION Core (start_ IN TIMESTAMP, end_ IN TIMESTAMP DEFAULT SYSTIMESTAMP, syntax_ IN NUMBER DEFAULT NULL) RETURN VARCHAR2 IS
day_ VARCHAR2(7); /* This means this FUNCTION only supports up to 99 days */
hour_ VARCHAR2(9); /* This means this FUNCTION only supports up to 999 hours, which is over 41 days */
minute_ VARCHAR2(12); /* This means this FUNCTION only supports up to 9999 minutes, which is over 17 days */
second_ VARCHAR2(18); /* This means this FUNCTION only supports up to 999999 seconds, which is over 11 days */
msecond_ VARCHAR2(22); /* This means this FUNCTION only supports up to 999999999 milliseconds, which is over 11 days */
d1_ NUMBER;
h1_ NUMBER;
m1_ NUMBER;
s1_ NUMBER;
ms_ NUMBER;
/* If you choose 1, you only get seconds. If you choose 2, you get minutes and seconds etc. */
precision_ NUMBER; /* 0 => milliseconds; 1 => seconds; 2 => minutes; 3 => hours; 4 => days */
format_ VARCHAR2(2) := ', ';
return_ VARCHAR2(50);
BEGIN
IF (syntax_ IS NULL) THEN
precision_ := 0;
ELSE
IF (syntax_ = 0) THEN
precision_ := 0;
ELSIF (syntax_ = 1) THEN
precision_ := 1;
ELSIF (syntax_ = 2) THEN
precision_ := 2;
ELSIF (syntax_ = 3) THEN
precision_ := 3;
ELSIF (syntax_ = 4) THEN
precision_ := 4;
ELSE
precision_ := 0;
END IF;
END IF;
SELECT EXTRACT(DAY FROM (end_ - start_)) INTO d1_ FROM DUAL;
SELECT EXTRACT(HOUR FROM (end_ - start_)) INTO h1_ FROM DUAL;
SELECT EXTRACT(MINUTE FROM (end_ - start_)) INTO m1_ FROM DUAL;
SELECT EXTRACT(SECOND FROM (end_ - start_)) INTO s1_ FROM DUAL;
IF (precision_ = 4) THEN
IF (d1_ = 1) THEN
day_ := ' day';
ELSE
day_ := ' days';
END IF;
IF (h1_ = 1) THEN
hour_ := ' hour';
ELSE
hour_ := ' hours';
END IF;
IF (m1_ = 1) THEN
minute_ := ' minute';
ELSE
minute_ := ' minutes';
END IF;
IF (s1_ = 1) THEN
second_ := ' second';
ELSE
second_ := ' seconds';
END IF;
return_ := d1_ || day_ || format_ || h1_ || hour_ || format_ || m1_ || minute_ || format_ || s1_ || second_;
RETURN return_;
ELSIF (precision_ = 3) THEN
h1_ := (d1_ * 24) + h1_;
IF (h1_ = 1) THEN
hour_ := ' hour';
ELSE
hour_ := ' hours';
END IF;
IF (m1_ = 1) THEN
minute_ := ' minute';
ELSE
minute_ := ' minutes';
END IF;
IF (s1_ = 1) THEN
second_ := ' second';
ELSE
second_ := ' seconds';
END IF;
return_ := h1_ || hour_ || format_ || m1_ || minute_ || format_ || s1_ || second_;
RETURN return_;
ELSIF (precision_ = 2) THEN
m1_ := (((d1_ * 24) + h1_) * 60) + m1_;
IF (m1_ = 1) THEN
minute_ := ' minute';
ELSE
minute_ := ' minutes';
END IF;
IF (s1_ = 1) THEN
second_ := ' second';
ELSE
second_ := ' seconds';
END IF;
return_ := m1_ || minute_ || format_ || s1_ || second_;
RETURN return_;
ELSIF (precision_ = 1) THEN
s1_ := (((((d1_ * 24) + h1_) * 60) + m1_) * 60) + s1_;
IF (s1_ = 1) THEN
second_ := ' second';
ELSE
second_ := ' seconds';
END IF;
return_ := s1_ || second_;
RETURN return_;
ELSE
ms_ := ((((((d1_ * 24) + h1_) * 60) + m1_) * 60) + s1_) * 1000;
IF (ms_ = 1) THEN
msecond_ := ' millisecond';
ELSE
msecond_ := ' milliseconds';
END IF;
return_ := ms_ || msecond_;
RETURN return_;
END IF;
END Core;
BEGIN
RETURN(Core(start_, end_, syntax_));
END Return_Elapsed_Time;
For example, if I called this function right now (12.10.2018 11:17:00.00) using Return_Elapsed_Time(TO_TIMESTAMP('12.04.2017 12:00:00.00', 'DD.MM.YYYY HH24:MI:SS.FF'),SYSTIMESTAMP), it should return something like:
47344620000 milliseconds
Better to use procedure like that:
CREATE OR REPLACE FUNCTION timestamp_diff
(
start_time_in TIMESTAMP
, end_time_in TIMESTAMP
)
RETURN NUMBER
AS
l_days NUMBER;
l_hours NUMBER;
l_minutes NUMBER;
l_seconds NUMBER;
l_milliseconds NUMBER;
BEGIN
SELECT extract(DAY FROM end_time_in-start_time_in)
, extract(HOUR FROM end_time_in-start_time_in)
, extract(MINUTE FROM end_time_in-start_time_in)
, extract(SECOND FROM end_time_in-start_time_in)
INTO l_days, l_hours, l_minutes, l_seconds
FROM dual;
l_milliseconds := l_seconds*1000 + l_minutes*60*1000 + l_hours*60*60*1000 + l_days*24*60*60*1000;
RETURN l_milliseconds;
END;
You can check it by calling:
SELECT timestamp_diff (TO_TIMESTAMP('12.04.2017 12:00:00.00', 'DD.MM.YYYY HH24:MI:SS.FF'),
TO_TIMESTAMP('12.04.2017 12:00:01.111', 'DD.MM.YYYY HH24:MI:SS.FF'))
as milliseconds
FROM DUAL;
The timestamp casted correctly between formats else there is a chance the fields would be misinterpreted.
Here is a working sample that is correct when two different dates (Date2, Date1) are considered from table TableXYZ.
SELECT ROUND (totalSeconds / (24 * 60 * 60), 1) TotalTimeSpendIn_DAYS,
ROUND (totalSeconds / (60 * 60), 0) TotalTimeSpendIn_HOURS,
ROUND (totalSeconds / 60) TotalTimeSpendIn_MINUTES,
ROUND (totalSeconds) TotalTimeSpendIn_SECONDS
FROM (SELECT ROUND (
EXTRACT (DAY FROM timeDiff) * 24 * 60 * 60
+ EXTRACT (HOUR FROM timeDiff) * 60 * 60
+ EXTRACT (MINUTE FROM timeDiff) * 60
+ EXTRACT (SECOND FROM timeDiff))
totalSeconds,
FROM (SELECT TO_TIMESTAMP (
TO_CHAR (Date2,
'yyyy-mm-dd HH24:mi:ss')
- 'yyyy-mm-dd HH24:mi:ss'),
TO_TIMESTAMP (
TO_CHAR (Date1,
'yyyy-mm-dd HH24:mi:ss'),
'yyyy-mm-dd HH24:mi:ss')
timeDiff
FROM TableXYZ))
Above one has some syntax error, Please use following on oracle:
SELECT ROUND (totalSeconds / (24 * 60 * 60), 1) TotalTimeSpendIn_DAYS,
ROUND (totalSeconds / (60 * 60), 0) TotalTimeSpendIn_HOURS,
ROUND (totalSeconds / 60) TotalTimeSpendIn_MINUTES,
ROUND (totalSeconds) TotalTimeSpendIn_SECONDS
FROM
(SELECT ROUND ( EXTRACT (DAY FROM timeDiff) * 24 * 60 * 60 + EXTRACT (HOUR FROM timeDiff) * 60 * 60 + EXTRACT (MINUTE FROM timeDiff) * 60 + EXTRACT (SECOND FROM timeDiff)) totalSeconds
FROM
(SELECT TO_TIMESTAMP(TO_CHAR( date2 , 'yyyy-mm-dd HH24:mi:ss'), 'yyyy-mm-dd HH24:mi:ss') - TO_TIMESTAMP(TO_CHAR(date1, 'yyyy-mm-dd HH24:mi:ss'),'yyyy-mm-dd HH24:mi:ss') timeDiff
FROM TABLENAME
)
);
I've posted here some methods to convert interval to nanoseconds and nanoseconds to interval. These methods have a nanosecond precision.
You just need to adjust it to get milliseconds instead of nanoseconds.
A shorter method to convert interval to nanoseconds.
SELECT (EXTRACT(DAY FROM (
INTERVAL '+18500 09:33:47.263027' DAY(5) TO SECOND --Replace line with desired interval --Maximum value: INTERVAL '+694444 10:39:59.999999999' DAY(6) TO SECOND(9) or up to 3871 year
) * 24 * 60) * 60 + EXTRACT(SECOND FROM (
INTERVAL '+18500 09:33:47.263027' DAY(5) TO SECOND --Replace line with desired interval
))) * 100 AS MILLIS FROM DUAL;
MILLIS
1598434427263.027
I) if you need to calculate the elapsed time in seconds between two timestamp columns try this:
SELECT
extract ( day from (end_timestamp - start_timestamp) )*86400
+ extract ( hour from (end_timestamp - start_timestamp) )*3600
+ extract ( minute from (end_timestamp - start_timestamp) )*60
+ extract ( second from (end_timestamp - start_timestamp) )
FROM table_name
II) if you want to just show the time difference in character format try this:
SELECT to_char (end_timestamp - start_timestamp) FROM table_name
I know that many people finding this solution simple and clear:
create table diff_timestamp (
f1 timestamp
, f2 timestamp);
insert into diff_timestamp values(systimestamp-1, systimestamp+2);
commit;
select cast(f2 as date) - cast(f1 as date) from diff_timestamp;
bingo!