I'm trying to make a function on psql. It will be triggered on insert on table. I want to inject an variable on my select. Can't get working ...
CREATE OR REPLACE FUNCTION updateHistoricLongTime()
RETURNS void AS $$
DECLARE
hour_nb int;
index_hour int;
saved_hours int;
tmp_counter int;
BEGIN
hour_nb := 0;
index_hour := 1;
saved_hours := 2160;
tmp_counter := 0;
SELECT COUNT(*) FROM locationhistoric WHERE type='hour' AND idLocation=6 INTO hour_nb;
IF (hour_nb<saved_hours) THEN
FOR i IN 1 .. saved_hours LOOP
SELECT COUNT(*) FROM visits
WHERE stend < (timestamp '2017-11-29 15:00' - interval **>> index_hour<<<** - 1 ' hour') AND stend > (timestamp '017-11-29 15:00' - interval **>>index_hour <<<**' hour') AND location_id=6 AND duration>0 INTO tmp_counter;
index_hour := index_hour + 1;
END LOOP;
END IF;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE;
How can I inject variable index_hour in my SELECT COUNT(*) FROM Visits ...
EDIT: It's just syntax issue, but I can't manage to find the right way !
The result in command line:
ERROR: syntax error at or near "index_hour"
LINE 16: ... stend < (timestamp '2017-11-29 15:00' - interval index_hour...
Thanks a lot,
The solution
CREATE OR REPLACE FUNCTION updateHistoricLongTime()
RETURNS void AS $$
DECLARE
hour_nb int;
index_hour int;
saved_hours int;
tmp_counter int;
index_hour_minor int;
BEGIN
hour_nb := 0;
index_hour := 1;
index_hour_minor := 0;
saved_hours := 2160;
SELECT COUNT(*)
INTO hour_nb
FROM locationhistoric
WHERE type='hour'
AND idLocation=6;
IF (hour_nb<saved_hours) THEN
FOR i IN 1 .. saved_hours LOOP
SELECT COUNT(*)
INTO tmp_counter
FROM visits
WHERE start > timestamp '2017-11-29 15:00' - ( interval '1 hour' * index_hour )
AND start < timestamp '2017-11-29 15:00' - ( interval '1 hour' * index_hour_minor)
AND location_id=6
AND duration>0;
INSERT INTO locationhistoric
(type, date, counter, idLocation)
VALUES( 'hour',
timestamp '2017-11-29 15:00' - ( interval '1 hour' * index_hour_minor),
tmp_counter,
6);
index_hour_minor := index_hour_minor + 1;
index_hour := index_hour + 1;
END LOOP;
END IF;
END;
$$
LANGUAGE plpgsql;
The value specified for an interval can't be passed as a variable. However if the base unit is always an hour you can multiply a one our interval with the desired number of ours, e.g.
interval '1' hour * 5
will return 5 hours. The 5 can be a parameter. So your query should be:
SELECT COUNT(*)
INTO tmp_counter
FROM visits
WHERE stend < (timestamp '2017-11-29 15:00' - (interval '1' hour * index_hour))
AND stend > (timestamp '2017-11-29 15:00' - (interval '1' hour * index_hour))
AND location_id=6
AND duration > 0;
The syntax you want to get for your query (where index_hour = 8, for example) is:
select count(*)
from visits
where
stend < (timestamp '2017-11-29 15:00' - interval '7 hour') and
stend > (timestamp '2017-11-29 15:00' - interval '8 hour') and
location_id = 6 and
duration > 0;
Note where the quotes are. This means that your variable has to be inside quotes in pl/pgsql and that means it will be treated as a literal.
The solution is:
execute
'select count(*) ' ||
'from visits ' ||
'where ' ||
'stend < (timestamp ''2017-11-29 15:00'' - interval ''' || (index_hour - 1) || ' hour'') and ' ||
'stend > (timestamp ''2017-11-29 15:00'' - interval ''' || index_hour || ' hour'') and ' ||
'location_id = 6 and ' ||
'duration > 0'
To save me setting up your data I've written a simpler example using a table that I have (driver) so that I could test. Note that you have to use 2 single quotes to get one single quote into a string and that means counting quotes carefully.
create function a47768241() returns integer
as $body$
declare
index_hour int;
id integer;
begin
index_hour = 8;
execute
'select id ' ||
'from driver ' ||
'where ' ||
'from_date_time < (timestamp ''2013-04-22 16:00:00'' - interval ''' || (index_hour - 1) || ' hour'') '
into id;
return id;
end;
$body$
language 'plpgsql';
Simple test:
# select a47768241();
a47768241
-----------
158
(1 row)
Using the result value to check the date:
# select * from driver where id = a47768241();
id | vehicle_id | person_id | from_date_time | to_date_time | created_at | updated_at
-----+------------+-----------+---------------------------+---------------------------+----------------------------+------------
158 | 6784 | 15430 | 2012-09-13 17:00:41.39778 | 2012-09-14 01:54:46.39778 | 2016-06-03 16:43:11.456063 |
(1 row)
just concat the interval value, like
interval concat(index_hour - 1 , ' hour')
Related
I am reviewing the functions but I cannot solve this one;
Write a function that returns every Friday 13th during a specific year.
Example:
SELECT * FROM martes13(2020);
13/01/2020
13/03/2020
13/08/2020
My unfortunate attempt, do not pay much attention.
DECLARE
diaInicial date;
diaFinal date;
anio1 date;
anio2 date;
auxData date;
dates date[];
BEGIN
diaInicial := ('01/' || '01/' || anio ) :: date;
diaFinal := diaInicial + '1 YEAR' :: interval;
anio2:= date_part('year',diaFinal);
FOR i IN 1..12 BY 1 LOOP
FOR j IN 1..30 BY 1 LOOP
diaInicial := anio || '-' || i || '-' || j;
if(date_part('dom',auxData)==13 and date_part('dow',auxData)==5)then
dates[j] := diaInicial;
end if;
end loop;
end loop;
return dates;
END;
There's no way to solve it no matter how hard I try, I understand that I have to use dates, years intervals and counters but it does not work out. Any help or information could be of use to me.
Thanks in advance.
That can be solved with a simple SQL statement:
SELECT CAST(d AS date)
FROM generate_series(
TIMESTAMP '2020-01-13',
TIMESTAMP '2020-12-13',
INTERVAL '1 month'
) AS thirteen(d)
WHERE EXTRACT (dow FROM d) = 5;
You could wrap that in an SQL function.
Here I want to print dates in a loop.
DO $$
DECLARE counter INTEGER := 0 ;
DECLARE interval_days TIMESTAMP;
BEGIN
WHILE counter <= 5 LOOP
counter := counter + 1 ;
interval_days := NOW() - INTERVAL '2 DAY';
RAISE NOTICE '%', interval_days;
END LOOP ;
END; $$
In the above code, I want to print something like this.
2020-11-16 12:11:43.741087
2020-11-15 12:11:43.741087
2020-11-14 12:11:43.741087
Date in different days interval. 2 days is not fixed.
You'll have to modify your code slightly:
DO $$
DECLARE
counter INTEGER := 0;
interval_days TIMESTAMP := current_timestamp;
BEGIN
WHILE counter <= 5 LOOP
counter := counter + 1;
interval_days := interval_days - INTERVAL '1 DAY';
RAISE NOTICE '%', interval_days;
END LOOP;
END;$$;
But you can get the same with
SELECT *
FROM generate_series(
current_timestamp - INTERVAL '1 DAY',
current_timestamp - INTERVAL '6 DAY',
INTERVAL '-1 DAY'
);
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'
);
DECLARE
start_date VARCHAR2(12);
end_date VARCHAR2(12);
start_epochtime VARCHAR2(15);
end_epochtime VARCHAR2(15);
v_sql VARCHAR2(1024);
BEGIN
SELECT to_char(current_date,'YYYY-MM-DD') into start_date from dual;
SELECT to_char(current_date - 30,'YYYY-MM-DD') into end_date from dual;
dbms_output.put_line(start_date);
dbms_output.put_line(end_date);
/* Below section will convert date to epochtime with hard code date value */
SELECT CAST((TO_DATE('2016-01-01','YYYY-MM-DD') - TO_DATE('1970-01- 01','YYYY-MM-DD') ) * 24 * 60 * 60 * 1000 AS VARCHAR(15)) into start_epochtime FROM DUAL;
SELECT CAST((TO_DATE('2016-01-01','YYYY-MM-DD') - TO_DATE('1970-01-01','YYYY-MM-DD') - 30) * 24 * 60 * 60 * 1000 AS VARCHAR(15)) into end_epochtime FROM DUAL;
dbms_output.put_line(start_epochtime);
dbms_output.put_line(end_epochtime);
/* Below section will convert date to epochtime with a variable */
SELECT CAST((TO_DATE(start_date,'YYYY-MM-DD') - TO_DATE('1970-01-01','YYYY-MM-DD') ) * 24 * 60 * 60 * 1000 AS VARCHAR(15)) into start_epochtime FROM DUAL;
SELECT CAST((TO_DATE(end_date,'YYYY-MM-DD') - TO_DATE('1970-01-01','YYYY-MM-DD') - 30) * 24 * 60 * 60 * 1000 AS VARCHAR(15)) into end_epochtime FROM DUAL;
dbms_output.put_line(start_epochtime);
dbms_output.put_line(end_epochtime);
EXECUTE IMMEDIATE q'[select to_char((TO_DATE('1970-01-01','yyyy-mm-dd') + (m.CREATIONDATE/1000/24/60/60)),'YYYY-MM-DD'),count(1) from jivemessage_us m where m.CREATIONDATE >= :start_epochtime and m.CREATIONDATE <= :end_epochtime group by to_char((TO_DATE('1970-01-01','yyyy-mm-dd') + (m.CREATIONDATE/1000/24/60/60)),'YYYY-MM-DD') order by 1]';
END;
/
I got this error ORA-01008: not all variables bound when i am running this pl sql. And, All statements are running fine except EXECUTE IMMEDIATE q'';
There doesn't appear to be a reason to use EXECUTE IMMEDIATE here. You're not building a dynamic query, nor are you executing a DDL statement. I suggest replacing the EXECUTE IMMEDIATE with
select to_char((TO_DATE('1970-01-01','yyyy-mm-dd') +
(m.CREATIONDATE/1000/24/60/60)),'YYYY-MM-DD'),
count(1)
from jivemessage_us m
where m.CREATIONDATE >= start_epochtime and
m.CREATIONDATE <= end_epochtime
group by to_char((TO_DATE('1970-01-01','yyyy-mm-dd') +
(m.CREATIONDATE/1000/24/60/60)),'YYYY-MM-DD')
order by 1
Best of luck.
If you really want to do it all in PL/SQL then you can do:
VARIABLE cur REFCURSOR;
DECLARE
start_date VARCHAR2(12);
end_date VARCHAR2(12);
start_epochtime VARCHAR2(15);
end_epochtime VARCHAR2(15);
v_sql VARCHAR2(1024);
BEGIN
start_date := TO_CHAR(current_date, 'YYYY-MM-DD');
end_date := TO_CHAR(current_date - 30, 'YYYY-MM-DD');
dbms_output.put_line(start_date);
dbms_output.put_line(end_date);
/* Below section will convert date to epochtime with hard code date value */
start_epochtime := ( DATE '2016-01-01' - DATE '1970-01-01' ) * 24 * 60 * 60 * 1000;
end_epochtime := ( DATE '2016-01-01' - DATE '1970-01-01' - 30 ) * 24 * 60 * 60 * 1000;
dbms_output.put_line(start_epochtime);
dbms_output.put_line(end_epochtime);
/* Below section will convert date to epochtime with a variable */
start_epochtime := ( CURRENT_DATE - DATE '1970-01-01' ) * 24 * 60 * 60 * 1000;
end_epochtime := ( CURRENT_DATE - 30 - DATE '1970-01-01' ) * 24 * 60 * 60 * 1000;
dbms_output.put_line(start_epochtime);
dbms_output.put_line(end_epochtime);
OPEN :cur FOR
select to_char(DATE '1970-01-01' + CREATIONDATE/1000/24/60/60,'YYYY-MM-DD'),
count(1)
from jivemessage_us
where CREATIONDATE BETWEEN start_epochtime and end_epochtime
group by CREATIONDATE
order by 1;
END;
/
PRINT cur;
But it would be simpler to do it in SQL:
select to_char(DATE '1970-01-01' + CREATIONDATE/1000/24/60/60,'YYYY-MM-DD'),
count(1)
from jivemessage_us
where CREATIONDATE BETWEEN ( CURRENT_DATE - DATE '1970-01-01' )*24*60*60*1000
AND ( CURRENT_DATE - 30 - DATE '1970-01-01' )*24*60*60*1000
group by CREATIONDATE
order by 1;
(Note: I've left your logic as-is but moved it from continually context switching from PL/SQL to SQL to just use PL/SQL as much as possible and ANSI Date literals; however, I do think that you have the -30 in the wrong places as it ought to be for the start_epochtime and not the end_epochtime.)
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!