Convert minutes to Hours:Minutes in Oracle SQL - sql

Morning Team,
I have an Oracle SQL script that is calculating from creation of an event and how many minutes old compared to the systimestamp.
I need to convert the minutes, which are coming out as 120 for 2 hour for example, into Hours:Minutes version, i.e. 2:00
I'm struggling with that part and would like to ask if someone could help? My current code for the calculation is:
(ROUND(((cast(systimestamp as date) - cast(n.createdttm as date)))*1440,0)) "Minutes old",
I'm sure it's something simple but with all my fiddling I am not able to get it.
Thank you

You can create an INTERVAL form the minutes.
select numtodsinterval(120, 'minute') from dual
Or create a datetime from the minutes and convert its time part to a string of hours and minutes.
select to_char(trunc(sysdate) + interval '1' minute * 120, 'hh24:mi') from dual

It looks like createdttm is a timestamp, so you can just subtract:
systimestamp - createdtm
... to get an interval value like +000000000 02:00:00.00000. You can't format that directly, but you can either extract the various elements and concatenate those back together, or treat it as a string and cut out the bits you want.
If you only want the time part and it will always be less than a day you can just do:
substr(systimestamp - createdtm, 12, 5)
02:00
But if it can go over 24 hours then you probably want the day part too, which you could still get just with substr (and maybe replace to change the space to another colon) if you know it can never be more than 2 days:
substr(systimestamp - createdtm, 10, 7)
0 02:00
That's unlikely to be a safe assumption though, so instead you could extract the number of days and concatenate that:
extract(day from (systimestamp - createdtm)) || ':' || substr(systimestamp - createdtm, 12, 5)
0:02:00
You could only show the number of days if it's non-zero, but that would probably be quite confusing to who/whatever is looking at the results; but if you really wanted to:
case when extract(day from (systimestamp - createdtm)) > 0
then extract(day from (systimestamp - createdtm)) || ':'
end || substr(systimestamp - createdtm, 12, 5)
02:00
db<>fiddle with a few sample values.
One thing to note is this effectively truncates the seconds off the time; your original attempt included round(), but that might not have been what you meant.

If u want to convert minutes to hours and minutes.
If x is the number of minutes (such as 350):
TO_CHAR ( FLOOR (x / 60)) || ':'
|| TO_CHAR ( MOD (x, 60)
, 'FM00'
)
If u want to convert hours and minutes to minutes.
SELECT (TRUNC (x) * 60) +
( (MOD (x, 1)
* 100
)
FROM dual;
where x is a NUMBER. If you have a sting, s, instead, use TO_NUMBER (s) in place of x.

Related

Interval Date to days [duplicate]

I have two timestamp columns: arrTime and depTime.
I need to find the number of munites the bus is late.
I tried the following:
SELECT RouteDate, round((arrTime-depTime)*1440,2) time_difference
FROM ...
I get the following error: inconsistent datatype . expected number but got interval day to second
How can i parse the nuber of minutes?
If i simply subtract: SELECT RouteDate, arrTime-depTime)*1440 time_difference
The result is correct but not well formatted:
time_difference
+00000000 00:01:00 0000000
The result of timestamp arithmetic is an INTERVAL datatype. You have an INTERVAL DAY TO SECOND there...
If you want the number of minutes one way would be to use EXTRACT(), for instance:
select extract( minute from interval_difference )
+ extract( hour from interval_difference ) * 60
+ extract( day from interval_difference ) * 60 * 24
from ( select systimestamp - (systimestamp - 1) as interval_difference
from dual )
Alternatively you can use a trick with dates:
select sysdate + (interval_difference * 1440) - sysdate
from (select systimestamp - (systimestamp - 1) as interval_difference
from dual )
The "trick" version works because of the operator order of precedence and the differences between date and timestamp arithmetic.
Initially the operation looks like this:
date + ( interval * number ) - date
As mentioned in the documentation:
Oracle evaluates expressions inside parentheses before evaluating those outside.
So, the first operation performed it to multiply the interval by 1,440. An interval, i.e. a discrete period of time, multiplied by a number is another discrete period of time, see the documentation on datetime and interval arithmetic. So, the result of this operation is an interval, leaving us with:
date + interval - date
The plus operator takes precedence over the minus here. The reason for this could be that an interval minus a date is an invalid operation, but the documentation also implies that this is the case (doesn't come out and say it). So, the first operation performed is date + interval. A date plus an interval is a date. Leaving just
date - date
As per the documentation, this results in an integer representing the number of days. However, you multiplied the original interval by 1,440, so this now represented 1,440 times the amount of days it otherwise would have. You're then left with the number of seconds.
It's worth noting that:
When interval calculations return a datetime value, the result must be an actual datetime value or the database returns an error. For example, the next two statements return errors:
The "trick" method will fail, rarely but it will still fail. As ever it's best to do it properly.
SELECT (arrTime - depTime) * 1440 time_difference
FROM Schedule
WHERE ...
That will get you the time difference in minutes. Of course, you can do any rounding that you might need to to get whole minutes....
Casting to DATE first returns the difference as a number, at least with the version of Oracle I tried.
round((cast(arrTime as date) - cast(depTime as date))*1440)
You could use TO_CHAR then convert back to a number. I have never tested the performance compared to EXTRACT, but the statement works with two dates instead of an interval which fit my needs.
Seconds:
(to_char(arrTime,'J')-to_char(depTime,'J'))*86400+(to_char(arrTime,'SSSSS')-to_char(depTime,'SSSSS'))
Minutes:
round((to_char(arrTime,'J')-to_char(depTime,'J'))*1440+(to_char(arrTime,'SSSSS')-to_char(depTime,'SSSSS'))/60)
J is julian day and SSSSS is seconds in day. Together they give an absolute time in seconds.

to_date function gives out oracle ORA 01850 : Hour should be between 0 and 23 error

I have hour and minute column in my table stored as datatype number. I'm trying to deduct 90 mins by converting them to valid date format and using to_char converting them to valid time format. I get the mentioned error.
I realized that this error is coming for data where i have hours entered as single number. for example 9 instead of 09. I tried LPAD but did not work as int or number doesn't take a 0 when padding.
to_char(to_date ( "hour_column" || "minute_column", 'hh24mi' ) - 90 / (24 * 60), 'hh24:mi') AS "Max_TIME"
Ora 08150: hour should be between 0 and 23.
You can apply a FORMAT adding leading zeroes, e.g.
to_char(to_date ( to_char("hour_column" * 100 + "minute_column", '0000'), 'hh24mi' ) - 90 / (24 * 60), 'hh24:mi') AS "Max_TIME"
The correct way to convert a one- or two-digit number to a two-digit string (with leading zeros, if necessary) is with the TO_CHAR() function, with the proper format model. The format model '00' is what you need; but that model will generate a three character string, leaving a space for the algebraic sign (plus is omitted by default, space is used as placeholder; if the number were negative, you would see the minus sign). Add the fm format model modifier to get just the two-digit number without a leading space.
Try to read the solution below step by step; with some luck, you will understand it all in a single reading. The WITH clause is there to generate some test inputs (it's not part of the solution!)
Final note - get in the habit of NOT using case-sensitive column names, which require double-quotes. Name your columns whatever you like, without double-quotes; then the names are not case sensitive, and you can write them in lower case, upper case, whatever, in your queries that need to reference them. If you name them with double-quotes, then you must always reference them in double quotes AND remember the exact capitalization you used when you created the table. Good luck remembering that "Max_TIME" was written in that capitalization!
with
test_data("hour_column", "minute_column") as (
select 3, 45 from dual union all
select 23, 50 from dual union all
select 1, 15 from dual union all
select 1, 30 from dual union all
select 0, 0 from dual
)
select "hour_column", "minute_column",
to_char( to_date( to_char("hour_column" , 'fm00') ||
to_char("minute_column", 'fm00') , 'hh24mi')
- interval '90' minute
, 'hh24:mi') as "Max_TIME"
from test_data
;
hour_column minute_column Max_TIME
----------- ------------- --------
3 45 02:15
23 50 22:20
1 15 23:45
1 30 00:00
0 0 22:30
If you like hacks, here's a hack - do an arithmetic computation with minutes (add one full day and then take modulo 24 * 60, to get the correct result when the input time is before 01:30) and then apply substr() to an interval data type. WITH clause and output not shown (they are the same as above).
select "hour_column", "minute_column",
substr( numtodsinterval(
mod((24 + "hour_column") * 60 + "minute_column" - 90, 24 * 60)
, 'minute') , 12, 5) as "Max_TIME"
from test_data
;
I would recommend to use the INTERVAL DAY TO SECOND Data Type rather than separate columns for hour and minute. If you cannot change the data type in your table then the solution could be
"hour_column" * INTERVAL '1' HOUR + "minute_column" * INTERVAL '1' MINUTE
or
NUMTODSINTERVAL("hour_column", 'hour') + NUMTODSINTERVAL("minute_column", 'minute')
Then you can run your arithmetic, for example
("hour_column" * INTERVAL '1' HOUR + "minute_column" * INTERVAL '1' MINUTE) - INTERVAL '90' MINUTE AS "Max_TIME"
This solution works also for Hours > 23 or Minutes > 59
Is this what you want?
SELECT
to_date(to_char(case when
hour_column<10
then '0'||hour_column else
hour_column end ||
"minute_column", 'hh24mi' ) - 90 /
(24 * 60), 'hh24:mi') AS "Max_TIME"
from table

Formatting INTERVAL DAY(0) TO SECOND(0)

I have a few time fields that are INTERVAL DAY(0) TO SECOND(0) Datatypes to store various times of the day.
Currently on normal retrieval, my fields display as such:
+00 19:00:00.000000
How am I able to format this such that it displays as 19:00?
You want hours and minutes from an interval. If that interval cannot exceed 24 hours, then you can add the interval to a date without time part and then use TO_CHAR to get the formatted hours and minutes:
select to_char(trunc(sysdate) + myinterval, 'hh24:mi') from mytable
If, however, the interval can be larger (e.g. 100 hours and 15 minutes: 100:15), then you'll need another approach:
select
to_char(extract (day from myinterval) * 24 + extract (hour from myinterval)) ||
':' ||
to_char(extract (minute from myinterval), 'fm00')
from mytable
In contradiction to documentation you cannot use function TO_CHAR() for intervals. It allows to use INTERVAL values but the result is always the same. On another place the documentation says:
Interval data types do not have format models.
You can use either EXTRACT() function or you can work with Regular Expressions:
WITH t AS
(SELECT INTERVAL '19' HOUR + INTERVAL '30' MINUTE AS i FROM dual)
SELECT
LPAD(EXTRACT(HOUR FROM i), 2, '0')||':'||LPAD(EXTRACT(MINUTE FROM i), 2, '0'),
REGEXP_SUBSTR(i, '\d{2}:\d{2}')
FROM t;
Usually it is not very smart to use Regular Expression on date/time values. On the other hand, since format of INTERVAL does not depend on any NLS-Settings it is a feasible option.
Disadvantage of EXTRACT is when you have negative intervals. Such an interval would be written like -19:-30 because every component is returned with minus sign (-). Also be aware that EXTRACT returns NUMBER values, so you have to pad them with '0' manually.
Use
substr(to_char(interval_value), 5, 5)
where interval_value is the column name (incidentally, tables in a relational database don't have fields; they do have columns).

ORA-01873: the leading precision of the interval is too smalll

I am getting an ORA-01873 "leading precision of the interval is too small" error from this statement and can't figure out why:
The v_not_auto_bl_num is declared as VARCHAR2(1000).
What is causing the error?
In the code you originally posted you are doing:
ABS( EXTRACT(DAY FROM (TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF')
- TO_TIMESTAMP(DHS.COMPLETED_IODT,'YYYYMMDDHH24MISS.FF')) *86400*1000) / 1000)
The relevant part is this:
(TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF')
- TO_TIMESTAMP(DHS.COMPLETED_IODT,'YYYYMMDDHH24MISS.FF')) *86400*1000
If you subtract two timestamps you get an interval data type, not a number; e.g. if your table columns were, say, '20170419065416' and '20170419000000' then subtracting them would generate:
(TO_TIMESTAMP(DHS.A
-------------------
+00 06:54:16.000000
If you multiply that by 86400*1000 you exceed the precision of the interval data type. I chose that value because one second less is OK:
with dhs (assignment_iodt, completed_iodt) as (
select '20170419065415', '20170419000000' from dual
)
select (TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF')
- TO_TIMESTAMP(DHS.COMPLETED_IODT,'YYYYMMDDHH24MISS.FF')) as original,
(TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF')
- TO_TIMESTAMP(DHS.COMPLETED_IODT,'YYYYMMDDHH24MISS.FF')) *86400*1000 as multiplied
from dhs;
ORIGINAL MULTIPLIED
------------------- -------------------------
+00 06:54:15.000000 +24855000 00:00:00.000000
Once second more (or, in fact, anything beyond 20170419065415.134814814, or any pair of values with the actual interval above 06:54:15.134814814) will error as the multiplied interval is out of range for the data type.
What's actually happening under the hood is unclear; using a smaller multiplier also causes the issues once you cross that raw interval size limit.
Anyway, you seem to be trying to get the number of while seconds, which you can do by extracting each time element and multiplying them individually:
select abs(
(extract(day from diff) * 86400)
+ (extract (hour from diff) * 3600)
+ (extract (minute from diff) * 60)
+ trunc(extract (second from diff))
) as c_f_previous_time
from (
select to_timestamp(dhs.assignment_iodt,'YYYYMMDDHH24MISS.FF')
- to_timestamp(dhs.completed_iodt,'YYYYMMDDHH24MISS.FF') as diff
from dhs
);
I've put the timestamp subtraction in an inline view just so it doesn't have to be repeated within each extract call. You can put the rest of your original query inside that inline view (or a CTE) too.
Incidentally, the abs() implies you can have rows in your table where the completed date is earlier than the assignment; or just that you didn't notice you're doing the subtraction the wrong way round. If you data cannot have completed before assigned then you can swap the terms over and lose the abs(); I'd probably swap the terms anyway just to make it look more logical.
first try this:
create table test_table as
SELECT ACT_BL.BL_NUM,
ABS( EXTRACT(DAY FROM (TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF') - TO_TIMESTAMP(DHS.COMPLETED_IODT,'YYYYMMDDHH24MISS.FF')) *86400*1000) / 1000) AS C_F_PREVIOUS_TIME
FROM DOCI_ACTIVITY ACT ,
DOCI_ACTIVITY_RELATED_BL ACT_BL ,
DSH_ACTIVITY DHS
WHERE ACTIVITY_TYPE IN ('BlCodingAndFormatting','BlCreationFromESI')
AND ACT.ACTIVITY_ID =ACT_BL.ACTIVITY_ID
AND ACT_BL.ACTIVITY_ID = DHS.ACTIVITY_ID
AND ACT_BL.BL_NUM = v_not_auto_bl_num;
then check the test_table columns type(BL_NUM and C_F_PREVIOUS_TIME)
after that you apply that column types to your table
In your case, the exception is raised when you multiply an interval by 86400.
As I've posted here you could use the following shorter method to convert interval to milliseconds.
SELECT ROUND((EXTRACT(DAY FROM (
TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF') - TO_TIMESTAMP(DHS.COMPLETED_IODT ,'YYYYMMDDHH24MISS.FF')
) * 24 * 60) * 60 + EXTRACT(SECOND FROM (
TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF') - TO_TIMESTAMP(DHS.COMPLETED_IODT ,'YYYYMMDDHH24MISS.FF')
))) * 1000) AS MILLIS FROM DUAL;
Your numeric number appears to be too large for the ABS function to handle. The biggest value you can pass to ABS() as the number is 2^31-1:

Oracle: subtract millisecond from a datetime

I thought it was really simple but it isn't.
SELECT TO_TIMESTAMP('10/08/2012','DD/MM/YYYY')
- 1/(24*50*60*1000) data
FROM dual;
It simply doesn't work.
Other details:
SELECT TO_TIMESTAMP('10/08/2012','DD/MM/YYYY') -
NUMTODSINTERVAL(1/(24*50*60*1000),'HOUR') data
FROM dual;
doesn't work..
The right seems to be
SELECT TO_TIMESTAMP('10/08/2012','DD/MM/YYYY') -
NUMTODSINTERVAL(1/(24*25*60*1000),'HOUR') data
FROM dual;
Why? How does it work?
For adding or subtracting an amount of time expressed as a literal you can use INTERVAL.
SELECT TO_TIMESTAMP('10/08/2012','DD/MM/YYYY')
- INTERVAL '0.001' SECOND
FROM dual;
As well there are now standard ways to express date and time literals and avoid the use of various database specific conversion functions.
SELECT TIMESTAMP '2012-10-08 00:00:00'
- INTERVAL '0.001' SECOND DATA
FROM dual;
For your original question the time part of a day is stored in fractional days. So one second is:
1 / (hours in day * minutes in hour * seconds in a minute)
Divide by 1000 to get milliseconds.
1 / (24 * 60 * 60 * 1000)
SELECT TO_TIMESTAMP('10/08/2012','DD/MM/YYYY') - NUMTODSINTERVAL(1/(24*50*60*1000),'HOUR') data
FROM dual;
OUTPUT
DATA
---------------------------------
09/AUG/12 11:59:59.999950000 PM
1 row selected.
The answer posted above subtracts a tenth of a millisecond from the date. I think what you want is the following:
SELECT TO_TIMESTAMP('10/08/2012','DD/MM/YYYY')-NUMTODSINTERVAL(1/1000,'SECOND')
FROM dual;
Output:
DATA
---------------------------------------------------------------------------
09-AUG-12 11.59.59.999000000 PM
^^^
|||
tenths|thousandths
|
hundredths
The following NUMTODSINTERVAL(1/(24*25*60*1000),'HOUR') seems to work only because 24*25 = 600. But that number is wrong because 1/(600*60*1000) of an hour is a tenth of a millisecond, not a millisecond. If you want to use 'HOUR' in NUMTODSINTERVAL() you should use 1/(60*60*1000) (sixty minutes in an hour, sixty seconds in a minute, 1000 ms in a second).
This is correct (with a millisecond being 1000th of a second):-
SELECT TO_TIMESTAMP('10/08/2012','DD/MM/YYYY') - NUMTODSINTERVAL(1/1000,'SECOND') data FROM dual;
DATA
-----------------------------
09-AUG-12 23.59.59.999000000
As to why the other code isn't working it's because you aren't calculating a millisecond correctly. An hour must be divided by 60 to give minutes and again by 60 to given seconds then by 1000 to give a millisecond, thus if you must use HOUR as the interval then it is:-
SELECT TO_TIMESTAMP('10/08/2012','DD/MM/YYYY') - NUMTODSINTERVAL(1/(60*60*1000),'HOUR') as data FROM dual;
DATA
---------------------------------------------------------------------------
09-AUG-12 23.59.59.999000000
select TO_CHAR(TO_TIMESTAMP('10.05.2012', 'DD.MM.YYYY') -
NUMTODSINTERVAL(1/1000, 'SECOND'), 'DD.MM.YYYY HH24:MI:SS:FF3') Res
from dual;
RES
-----------------------------
09.05.2012 23:59:59.999