I have a function that calculates the difference between 2 dates or timestamps which is working fine.
Is there a way to modify the function to display the fractional part of the TIMESTAMP in the difference as part of the result. I want both cases to be handled in the same function if possible.
CREATE OR REPLACE FUNCTION datediff (p_from date, p_to date)
return varchar2 is
l_years PLS_INTEGER;
l_from DATE;
l_interval interval day(3) to second(0);
begin
l_years := TRUNC(MONTHS_BETWEEN(p_to, p_from)/12);
l_from := ADD_MONTHS(p_from, l_years * 12);
l_interval := (p_to - l_from) DAY(3) TO SECOND(0);
return l_years || ' Years '
|| extract (day from l_interval) || ' Days '
|| extract (hour from l_interval) || ' Hours '
|| extract (minute from l_interval) || ' Minutes '
|| extract (second from l_interval) || ' Seconds';
end datediff;
/
SELECT
datediff( TO_DATE('1981-04-01 10:11:13','YYYY-MM-DD HH24:MI:SS'),
TO_DATE('2022-04-03 17:48:09','YYYY-MM-DD HH24:MI:SS')) as diff FROM DUAL;
DIFF
41 Years 2 Days 7 Hours 36 Minutes 56 Seconds
SELECT
datediff (TO_TIMESTAMP('1981-04-01 10:11:13.551000000', 'YYYY-MM-DD HH24:MI:SS.FF'),
TO_TIMESTAMP('2022-04-03 17:48:09.878700000', 'YYYY-MM-DD HH24:MI:SS.FF')) as diff FROM DUAL;
/* want to show fractional difference here */
DIFF
41 Years 2 Days 7 Hours 36 Minutes 56 Seconds
You want to pass TIMESTAMP arguments to the function and when you add years you also need to ensure that you propagate the fractional component of the timestamp (since ADD_MONTHS returns a DATE data-type without fractional seconds):
CREATE OR REPLACE FUNCTION datediff (p_from timestamp, p_to timestamp)
return varchar2 is
l_years PLS_INTEGER;
l_from TIMESTAMP;
l_interval interval day(3) to second(6);
begin
l_years := TRUNC(MONTHS_BETWEEN(p_to, p_from)/12);
l_from := CAST(TRUNC(ADD_MONTHS(p_from, l_years * 12), 'MI') AS TIMESTAMP)
+ NUMTODSINTERVAL( EXTRACT(SECOND FROM p_from), 'SECOND' );
l_interval := (p_to - l_from) DAY(3) TO SECOND(6);
return l_years || ' Years '
|| extract (day from l_interval) || ' Days '
|| extract (hour from l_interval) || ' Hours '
|| extract (minute from l_interval) || ' Minutes '
|| extract (second from l_interval) || ' Seconds';
end datediff;
/
Then:
SELECT datediff(
TIMESTAMP '1981-04-01 10:11:13.551000000',
TIMESTAMP '2022-04-03 17:48:09.878700000'
) as diff
FROM DUAL;
Outputs:
DIFF
41 Years 2 Days 7 Hours 36 Minutes 56.3277 Seconds
fiddle
Related
I'm trying to create a function, which returns a random TIMESTAMP between a range of timestamps.
It doesn't appear to be working ALL the time as sometimes I get a value back before the starting range and sometimes I get a value back after the ending range.
Below is my test CASE and example of a TIMESTAMP out of range.In this example the TIMESTAMP is after the ending range of TIMESTAMP '2023-01-25 12:00:00'
Can someone please explain what the problem is and show me how to fix it as I can't seem to figure this out.
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'DD-MON-YYYY HH24:MI:SS.FF';
CREATE OR REPLACE FUNCTION random_timestamp(
p_from IN TIMESTAMP,
p_to IN TIMESTAMP,
p_fraction IN VARCHAR2 DEFAULT 'Y'
) RETURN TIMESTAMP
IS
return_val_y TIMESTAMP := p_from + dbms_random.value () * (p_to - p_from + INTERVAL '1' DAY);
return_val_n TIMESTAMP (0) := return_val_y;
BEGIN
RETURN CASE
WHEN UPPER (SUBSTR (p_fraction, 1, 1)) = 'Y'
THEN return_val_y
ELSE return_val_N
END;
END random_timestamp;
/
SELECT random_timestamp(
TIMESTAMP '2023-01-25 09:00:00', TIMESTAMP '2023-01-25 12:00:00') as ts from dual
TS
26-JAN-2023 03:59:06.013730
You are adding 1 day:
p_from + dbms_random.value () * (p_to - p_from + INTERVAL '1' DAY);
It falls within the range of p_from to p_to plus 1 day and is doing exactly what you told it to do.
If you don't want the range to be 1 day extra then remove + INTERVAL '1' DAY
CREATE OR REPLACE FUNCTION random_timestamp(
p_from IN TIMESTAMP,
p_to IN TIMESTAMP,
p_fraction IN VARCHAR2 DEFAULT 'Y'
) RETURN TIMESTAMP
IS
return_val_y TIMESTAMP(9) := p_from + dbms_random.value() * (p_to - p_from);
return_val_n TIMESTAMP(0) := return_val_y;
BEGIN
RETURN CASE
WHEN p_fraction LIKE 'Y%' OR p_fraction LIKE 'y%'
THEN return_val_y
ELSE return_val_n
END;
END random_timestamp;
/
Then:
SELECT MIN(ts),
MAX(ts)
FROM (
SELECT random_timestamp(
TIMESTAMP '2023-01-25 09:00:00',
TIMESTAMP '2023-01-25 12:00:00'
) AS ts
FROM DUAL
CONNECT BY LEVEL <= 1e6
);
May randomly output:
MIN(TS)
MAX(TS)
25-JAN-23 09.00.00.017186000
25-JAN-23 11.59.59.999534000
And stays within the range.
fiddle
I have a function below, which shows the difference between 2 dates in days, hours, minutes and seconds, which is working fine.
I want to expand the function to include the number of years but I'm unsure how to DECLARE the variable in the function. I know it has to be larger than day(3) to second(0). Is there something like a year(3) to second(0)?
As you can see I get the following error when the DATE spans years. Any help would be appreciated.
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH24:MI:SS';
CREATE OR REPLACE FUNCTION datediff (p_from date, p_to date)
return varchar2 is
l_interval interval day(3) to second(0);
begin
l_interval := cast(p_to as timestamp) - cast(p_from as timestamp);
return extract (day from l_interval) || ' Days '
|| extract (hour from l_interval) || ' Hours '
|| extract (minute from l_interval) || ' Minutes '
|| extract (second from l_interval) || ' Seconds';
end datediff;
/
SELECT
datediff( TO_DATE('2022-04-01 10:11:13','YYYY-MM-DD HH24:MI:SS'),
TO_DATE('2022-04-03 17:48:09','YYYY-MM-DD HH24:MI:SS')) as diff FROM DUAL;
DIFF
2 Days 7 Hours 36 Minutes 56 Seconds
SELECT
datediff( TO_DATE('1981-04-01 10:11:13','YYYY-MM-DD HH24:MI:SS'),
TO_DATE('2022-04-03 17:48:09','YYYY-MM-DD HH24:MI:SS')) as diff FROM DUAL;
ORA-01873: the leading precision of the interval is too small
Is there something like a year(3) to second(0)?
It does not make sense to have a generic INTERVAL data type that contains years, days, hours, minutes and seconds.
If you have an interval of 365 days then if your interval starts on 2022-01-01 than that equals 1 year but if the interval starts on 2020-01-01 then is less than one year as there are 366 days in 2020 due to it being a leap year. Therefore, the number of days in a year depends on which year you are talking about and you cannot have a generic interval that combines days with either months or years as it does not make sense as you need a specific start date.
If the date spans years then you could edit your function and increase the leading precision of the INTERVAL to interval day(5) to second(0) which will let you store about 273 years and then your function will work for your sample data.
SELECT datediff( TIMESTAMP '1981-04-01 10:11:13', TIMESTAMP '2022-04-03 17:48:09')
as diff
FROM DUAL;
Outputs:
DIFF
14977 Days 7 Hours 36 Minutes 56 Seconds
However, since you have known start- and end-dates then you can find the number of full years between the two bounds and then use an INTERVAL DAY TO SECOND data type calculate the number of days, hours, minutes and seconds on the part-year remainder:
CREATE OR REPLACE FUNCTION datediff (p_from date, p_to date)
return varchar2 is
l_years PLS_INTEGER;
l_from DATE;
l_interval interval day(3) to second(0);
begin
l_years := TRUNC(MONTHS_BETWEEN(p_to, p_from)/12);
l_from := ADD_MONTHS(p_from, l_years * 12);
l_interval := (p_to - l_from) DAY(3) TO SECOND(0);
return l_years || ' Years '
|| extract (day from l_interval) || ' Days '
|| extract (hour from l_interval) || ' Hours '
|| extract (minute from l_interval) || ' Minutes '
|| extract (second from l_interval) || ' Seconds';
end datediff;
/
Then:
SELECT datediff( TIMESTAMP '2022-04-01 10:11:13', TIMESTAMP '2022-04-03 17:48:09')
as diff
FROM DUAL;
Outputs:
DIFF
0 Years 2 Days 7 Hours 36 Minutes 56 Seconds
and:
SELECT datediff( TIMESTAMP '1981-04-01 10:11:13', TIMESTAMP '2022-04-03 17:48:09')
as diff
FROM DUAL;
Outputs:
DIFF
41 Years 2 Days 7 Hours 36 Minutes 56 Seconds
db<>fiddle here
I need some assistance with the following PL/SQL code which displays the time difference in minutes between two date values. The first part before the IF-Statement returns the needed information, but the challenge comes in when i'm trying to write an IF-Statement to display the time difference for only those jobs that have exceeded 15 Minutes then send Email notification through Exchange Server.
Your assistance is appreciated in advance.
Declare
l_found boolean :=false;
Cursor C5
is
(SELECT CASE LENGTH(JCSBMTIME)
WHEN 0 THEN
round(((sysdate - to_date
(TO_DATE(TO_CHAR(to_number(JCSBMDATE)+1900000),'YYYYDDD') || ' ' ||
to_char(to_date(CONCAT('000000',JCSBMTIME),'HH24:MI:SS'),'HH24:MI:SS')
,'DD-MON-YY HH24:MI:SS')) * 1440),2)
WHEN 1 THEN
round(((sysdate - to_date
(TO_DATE(TO_CHAR(to_number(JCSBMDATE)+1900000),'YYYYDDD') || ' ' ||
to_char(to_date(CONCAT('00000',JCSBMTIME),'HH24:MI:SS'),'HH24:MI:SS')
,'DD-MON-YY HH24:MI:SS')) * 1440),2)
WHEN 2 THEN
round(((sysdate - to_date
(TO_DATE(TO_CHAR(to_number(JCSBMDATE)+1900000),'YYYYDDD') || ' ' ||
to_char(to_date(CONCAT('0000',JCSBMTIME),'HH24:MI:SS'),'HH24:MI:SS') ,'DD-
MON-YY HH24:MI:SS')) * 1440),2)
WHEN 3 THEN
round(((sysdate - to_date
(TO_DATE(TO_CHAR(to_number(JCSBMDATE)+1900000),'YYYYDDD') || ' ' ||
to_char(to_date(CONCAT('000',JCSBMTIME),'HH24:MI:SS'),'HH24:MI:SS') ,'DD-
MON-YY HH24:MI:SS')) * 1440),2)
WHEN 4 THEN
round(((sysdate - to_date
(TO_DATE(TO_CHAR(to_number(JCSBMDATE)+1900000),'YYYYDDD') || ' ' ||
to_char(to_date(CONCAT('00',JCSBMTIME),'HH24:MI:SS'),'HH24:MI:SS') ,'DD-
MON-YY HH24:MI:SS')) * 1440),2)
WHEN 5 THEN
round(((sysdate - to_date
(TO_DATE(TO_CHAR(to_number(JCSBMDATE)+1900000),'YYYYDDD') || ' ' ||
to_char(to_date(CONCAT('0',JCSBMTIME),'HH24:MI:SS'),'HH24:MI:SS') ,'DD-
MON-YY HH24:MI:SS')) * 1440),2)
ELSE
round(((sysdate - to_date
(TO_DATE(TO_CHAR(to_number(JCSBMDATE)+1900000),'YYYYDDD') || ' ' ||
to_char(to_date(CONCAT('',JCSBMTIME),'HH24:MI:SS'),'HH24:MI:SS') ,'DD-MON-
YY HH24:MI:SS')) * 1440 ),2)
END AS "DATETIME_DIFF",
JCUSER, JCJOBNBR, JCSBMTIME FROM SVM900E1.F986110
WHERE JCSBMDATE = (TO_CHAR(sysdate,'YYYYDDD')-1900000)
AND JCJOBSTS in ('P','S','W'));
Begin
For Y in C5
Loop
l_found := true;
dbms_output.put_line(Y.DATETIME_DIFF||' '||Y.JCUSER||' ' ||Y.JCJOBNBR||'
'||Y.JCSBMTIME);
End loop;
if not l_found
Then
dbms_output.put_line('No records found');
End if;
Case Y.DATETIME_DIFF when > 15
Then
dbms_output.put_line(Y.JCUSER||' ' ||Y.JCJOBNBR||' '||Y.JCSBMTIME);
Else 'No records';
End case;
End;
Pay attention to your Y iterator scope. You are trying to use it out of loop statement.
You code should be like this. I omit part of cursor declaration
--at first init to false
l_found := false;
for Y in your_cursor loop
-- if we are in loop , we are found a record , change value to true
l_found := true;
check conditions, do output etc...
end loop;
-- if cursor have no result
if not l_found then
dbms_output.put_line('No records found');
end if;
Can you please help in converting minutes to the format of ('HH24:MI').
I am getting the result in integer which is the minutes. So, how to convert them?
Thanks
Assuming, you want to convert 492 minutes:
select to_char(trunc(sysdate) + 492/24/60, 'hh24:mi') from dual;
If you want a function:
create or replace
function tq84_convert_minutes_hh24mm (p_minutes in number)
return varchar2 is
begin
return to_char (trunc(sysdate) + p_minutes / 24/60, 'hh24:mi');
end tq84_convert_minutes_hh24mm;
/
Later ...
begin
dbms_output.put_line (tq84_convert_minutes_hh24mm(492));
end;
/
Another way:
WITH c AS
(SELECT 492 AS MINUTES FROM DUAL)
SELECT TRIM(TO_CHAR(TRUNC(MINUTES / 60), '09')) || ':' ||
TRIM(TO_CHAR(TRUNC(MOD(ABS(MINUTES), 60)), '09')) AS HHMM
FROM C
This will have issues if the time exceeds 1440 minutes (24 hours) but it gives you something to start with.
Share and enjoy.
This can save you, but I had problems with time greater than 1440 minutes.
select to_char(trunc(sysdate) + [MINUTES]/24/60, 'hh24:mi') from dual;
So I did a function that verifies if minute is greater or equal 1440:
IF (QT_MINUTES_P IS NOT NULL) THEN
IF (QT_MINUTES_P >= 1440) THEN
SELECT ROUND(QT_MINUTES_P/ 60) ||':'|| MOD(QT_MINUTES_P, 60)
INTO DS_RESULT
FROM DUAL;
ELSE
SELECT TO_CHAR(TRUNC(SYSDATE) + (QT_MINUTES_P)/24/60, 'hh24:mi')
INTO DS_RESULT
FROM DUAL;
END IF;
END IF;
I need at some point to increment dynamically a timestamp plsql variable.
So, instead of doing this:
timestamp_ := timestamp_ + INTERVAL '1' DAY;
I would like to do thomething like this:
timestamp_ := timestamp_ + INTERVAL days_ DAY;
It doesn't really work.
My final goal is to dynamically create some scheduler jobs for some entities that have an variable expiration date, to avoid creating a single one which would be often executed.
It sounds like you want
timestamp_ := timestamp + numtodsinterval( days_, 'day' );
I would be somewhat cautious, however, about an architecture that involves creating thousands of scheduler jobs rather than one job that runs periodically to clear out expired rows. A single job is a heck of a lot easier to manage and oversee.
Special note:
1. INTERVAL YEAR TO MONTH and
2. INTERVAL DAY TO SECOND
are the only two valid interval datatypes;
Sample Example:
=============================
DECLARE
l_time INTERVAL YEAR TO MONTH;
l_newtime TIMESTAMP;
l_year PLS_INTEGER := 5;
l_month PLS_INTEGER := 11;
BEGIN
-- Notes :
-- 1. format is using "-" to connect year and month
-- 2. No need to mention any other keyword ; Implicit conversion takes place to set interval
l_time := l_year || '-' || l_month;
DBMS_OUTPUT.put_line ( l_time );
SELECT SYSTIMESTAMP + l_time INTO l_newtime FROM DUAL;
DBMS_OUTPUT.put_line ( 'System Timestamp :' || SYSTIMESTAMP );
DBMS_OUTPUT.put_line ( 'New Timestamp After Addition :' || l_newtime );
END;
=============================
Try this:
DECLARE
l_val NUMBER;
l_result VARCHAR2( 20 );
BEGIN
l_val := 1;
SELECT SYSDATE - INTERVAL '1' DAY * l_val INTO l_result FROM DUAL;
DBMS_OUTPUT.put_line( 'Current Date is ' || SYSDATE || ' minus ' || l_val || ' day(s) is ' || l_result );
END;
Output will be:
Current Date is 25-FEB-16 minus 1 day(s) is 24-FEB-16