Oracle SQL To_Char(interval) is not using my format string? - sql

In the following bit of SQL, I want to print an interval (actually a number of seconds) in HH:MM:SS format. I convert the seconds to an Interval, then attempt to use TO_CHAR date formatting. The Oracle server ignores my pattern and prints a whole timestamp with zero chars in dates and microseconds. What am I doing wrong?
CASE WHEN (TR.RUN_LENGTH > 0) THEN TO_CHAR(NUMTODSINTERVAL(TR.RUN_LENGTH,'second'), 'HH24:MI:SS')
ELSE '0' END AS RUN_LENGTH,
And I get: +000000000 00:03:22.000000000

As you have observed, to_char() doesn't work on intervals. So, don't think in terms of intervals. Instead, you have a number of seconds and you want to convert to a string in the format of HH:MM:SS.
Use arithmetic and string operations:
select lpad(floor(tr.run_length / 3600), 2, '0') || ':' || lpad(mod(floor(tr.run_length / 60), 60), 2, '0') || ':' || lpad(mod(tr.run_length, 60), 2, '0') as hhmmss
from (select 1 as run_length from dual union all
select 10 as run_length from dual union all
select 100 as run_length from dual union all
select 1000 as run_length from dual union all
select 10000 as run_length from dual union all
select 100000 as run_length from dual
) tr;
Here is a db<>fiddle.

I searched through the other Related responses; It's clear that Oracle doesn't actually implement a user specified format for TO_CHAR(DSINTERVAL). As some have suggested, you can add a date then use the date formatting. But that fails if the interval is more than 24 hours.
I ended up using REGEXP_SUBSTRING() but then ran into the problem that Oracle evidently doesn't support non-capturing groups. I was able to piece together this:
REGEXP_SUBSTR(TO_CHAR(NUMTODSINTERVAL(TR.RUN_LENGTH,'second')), '^([\+0:\ ]*)(.*)(\.)',1,1,NULL,2 )
which seems to work quite well. It suppresses any leading zeros and fractional seconds.

If your interval is always less than 24 hours, then you can use one of these:
TO_CHAR(TRUNC(SYSDATE) + NUMTODSINTERVAL(TR.RUN_LENGTH, 'second'), 'hh24:mi:ss')
REGEXP_SUBSTR(NUMTODSINTERVAL(TR.RUN_LENGTH,'second'), '\d{2}:\d{2}:\d{2}')
Otherwise you need to use the EXTRACT function:
SELECT
TO_CHAR(
EXTRACT(DAY FROM NUMTODSINTERVAL(TR.RUN_LENGTH,'second')) * 24
+ EXTRACT(HOUR FROM NUMTODSINTERVAL(TR.RUN_LENGTH,'second')) )
|| ':' EXTRACT(MINUTE FROM NUMTODSINTERVAL(TR.RUN_LENGTH,'second')
|| ':' TRUNC(EXTRACT(SECOND FROM NUMTODSINTERVAL(TR.RUN_LENGTH,'second'))
Optionally you have to add LPAD(..., 2, '0') around the parts, if required for your output.

Less code:
to_number(
substr(to_char((LATER_timestamp - EARLIER_timestamp),'HH24:MI:SS'), 1,10) /* this is the DAY string */
) || 'd ' ||
substr(to_char((LATER_timestamp - EARLIER_timestamp),'HH24:MI:SS'),12,11) /* this is the TIME string. Milliseconds are "FLOORED". */
Example output is: "2d 02:59:57.31".

Try this:
TO_CHAR (TRUNC (SYSDATE) + NUMTODSINTERVAL ((TR.RUN_LENGTH, 'second'),
'hh24:mi:ss' )
The doc for NUMTODSINTERVAL says it returns a string literal, so to_char has no effect. This hack above just takes the current date/time (sysdate), truncates the HH:mm:ss component, then adds back the #seconds you specify, then format that to just show the HH:MM:SS value

Related

Convert minutes to Hours:Minutes in Oracle 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.

How can i pass variable with in single quote oracle sql

select
to_char(sysdate + interval '2' hour,'hh12:mi AM') as Time
from dual
i have a query that will add 2 hours from current system time , but it may add aur subtract the time and also hour's value will also be dynamic
so i have to use operator value it may + or - and similarly hour value it may 2 ,3 ,4 or 5 so my query will be
select
to_char(sysdate :operator interval ':hourvalue' hour,'hh12:mi AM') as Time
from dual
it gives me error
ORA-00907: missing right parenthesis
please help me out i am using oracle 11g
You cannot have an INTERVAL literal of a variable amount; however, you can have a fixed INTERVAL literal and then multiply it by a bind variable:
SELECT TO_CHAR(
SYSDATE + :hourvalue * INTERVAL '1' HOUR,
'hh12:mi AM'
) AS TIME
FROM DUAL
If you want a negative amount then just specify a negative :hourvalue rather than using a separate :operator bind variable.
You can use NUMTODSINTERVAL(n, 'hour') where n can has a negative value as well.
For example
SELECT NUMTODSINTERVAL((-1)*10, 'hour') h FROM DUAL;
You can also create an interval -(1 hour 37 min 41 sec):
SELECT NUMTODSINTERVAL((-1) * (1*3600 + 37*60 + 41), 'second') hms FROM DUAL;
Note, n is a number (decimal), so this will work too:
SELECT NUMTODSINTERVAL((-1) * (1 + 37/60 + 41/3600), 'hour') hms FROM DUAL;
It's only possible to use bind variables for arguments, you cannot use operators as bind variables. A workaround is to multiply the interval by -1 for negative and 1 for positive.
SELECT
TO_CHAR(
SYSDATE + numtodsinterval((CASE WHEN :operator = '-' THEN -1 ELSE 1 END * :interval),'hour'),
'hh12:mi AM'
) as time
FROM DUAL;

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

Oracle Data Conversion: ORA-01722: invalid number

Error while converting varchar2 column value to Number
i am unable to compare varchar2 value field with another value
like this '10:54' with '11.00', i have to compare minutes with minutes field.
How is it possible?
Query is:
select
adm_emp_num,adm_emp_nam,adm_ace_dte,asm_sft,asm_ed_tme,asm_st_tme,adm_ace_tme from Adm,asm
where adm_ace_dte =ass_sft_dte
and to_char(adm_ace_dte,'YYYYMM')=201409
--having min(to_char(adm_ace_tme,'HH24:MI')) < asm_st_tme
having min(to_char(adm_ace_tme,'HH24:MI')) < to_char(to_number(asm_st_tme,'99999
9.99'),'999999.99')
group by adm_emp_num,adm_emp_nam,adm_ace_dte,asm_sft,asm_ed_tme,asm_
st_tme,adm_ace_tme
order by 1,2
refer the table structures in the link:
https://stackoverflow.com/review/suggested-edits/5861972
Any help will be appreciated
Thanks
Use EXTRATC function to get minute part. Examples:
from TIMESTAMP type:
SELECT EXTRACT(MINUTE FROM SYSTIMESTAMP) FROM DUAL;
from DATE type:
SELECT EXTRACT(MINUTE FROM CAST(SYSDATE AS TIMESTAMP)) FROM DUAL;
from VARCHAR2 type (for example: 11:30):
SELECT EXTRACT(MINUTE FROM TO_TIMESTAMP('11:30', 'HH24:MI')) FROM DUAL;
You can do it like this:
select to_number(to_char(to_date('12:56', 'hh24:mi'), 'mi')) from dual;
i.e. first convert your string to a valid date, then extract the minute part (by specifying just 'mi' in the format), then casting that to a number.
You can then perform comparisons:
...where to_number(to_char(to_date('12:56', 'hh24:mi'), 'mi')) = ...
You could of course just parse out the substring, but you would then be vulnerable to invalid minutes, like 79.
You can substring your value and convert it to number, for example:
...
WHERE TO_NUMBER(SUBSTR('10:54',4,2)) >= TO_NUMBER(SUBSTR('11:00',4,2));
--for minutes
...
WHERE TO_NUMBER(SUBSTR('10:54',1,2)) >= TO_NUMBER(SUBSTR('11:00',1,2));
--for hours

format interval with to_char

Following SQL command
select TO_CHAR(NVL(arg1 - arg2, TO_DSINTERVAL('0 00:00:00'))) from table1
produces a result of the format: +000000000 00:03:01.954000.
Is it possible to enter a special format in the to_char function in order to get
a result of format: +00 00:00:00.000?
you could cast the result if you want less precision:
SQL> SELECT TO_DSINTERVAL('10 10:00:00') t_interval FROM dual;
T_INTERVAL
-----------------------------------------------------------
+000000010 10:00:00.000000000
SQL> SELECT CAST(TO_DSINTERVAL('10 10:00:00')
2 AS INTERVAL DAY(2) TO SECOND(3)) t_interval
3 FROM dual;
T_INTERVAL
-----------------------------------------------------------
+10 10:00:00.000
Edit following OP comment:
From The Oracle Documentation (11gr1):
Interval datatypes do not have format models. Therefore, to adjust their presentation, you must combine character functions such as EXTRACT and concatenate the components.
It seems you will have to manually use EXTRACT to achieve the desired output:
SQL> SELECT to_char(extract(DAY FROM t_interval), 'fmS99999') || ' ' ||
2 to_char(extract(HOUR FROM t_interval), 'fm00') || ':' ||
3 to_char(extract(MINUTE FROM t_interval), 'fm00') || ':' ||
4 to_char(extract(SECOND FROM t_interval), 'fm00.000')
5 FROM (SELECT TO_DSINTERVAL('10 01:02:55.895') t_interval FROM dual)
6 ;
TO_CHAR(EXTRACT(DAYFROMT_INTER
------------------------------
+10 01:02:55.895
This is not very elegant but it seems it is the only way to deal with microseconds precision.
to_char() seems to have fixed format :( so regexp_substr may be an option, e.g.:
SELECT regexp_substr (TO_DSINTERVAL ('10 10:00:00'), '\d{2} \d{2}:\d{2}:\d{2}\.\d{3}') t_interval FROM dual
I realize it's not clever at all, nor is it the special format string you're looking for, but this answer does work, given that the output is fixed length:
SELECT SUBSTR(TO_CHAR(NVL(arg1 - arg2, TO_DSINTERVAL('0 00:00:00'))), 1, 1)
|| SUBSTR(TO_CHAR(NVL(arg1 - arg2, TO_DSINTERVAL('0 00:00:00'))), 9, 2)
|| ' '
|| SUBSTR(TO_CHAR(NVL(arg1 - arg2, TO_DSINTERVAL('0 00:00:00'))), 12, 12)
FROM table1;
It also just truncs the fractional seconds instead of rounding, but I assume from your example they're all just zeros anyway.
This is an even greater embarrassment, but I couldn't resist:
SELECT SUBSTR(REPLACE(TO_CHAR(NVL(arg1 - arg2, TO_DSINTERVAL('0 00:00:00')))
, '0000000', '')
, 1, 16)
FROM table1;
Slight case of thread necromancy, however I came across this question while searching for how to format an interval, so I thought it was worth adding this comment.
From the Oracle documentation, adding a timestamp to an interval results in a timestamp, so by adding a constant timestamp with zero time elements you can then use the standard to_char format elements for datetime ...
SELECT TO_CHAR( TIMESTAMP'1969-12-31 00:00:00' + TO_DSINTERVAL('0 00:03:01.954321'),
'HH24:MI:SS.FF3' ) FROM dual;
However, there is an issue if you intervals could be greater than a day. There is no format element for days that will yield 0. "DDD" is day of the year, so would be 365 in the example above, or 1 or more if the interval was greater then a day. This is fine as long as your intervals are less than 24 hours though.
Should add this is on 11g so may well not have be applicable to the OP.
SELECT W.SHIFT_NUMB || ' c: ' ||
TO_CHAR(TO_DATE('01.01.2012', 'dd.mm.yyyy') + W.TIMEFROM, 'HH24:MI') ||
' по: ' ||
TO_CHAR(TO_DATE('01.01.2012', 'dd.mm.yyyy') + W.TIMETO, 'HH24:MI'),
w.ID
FROM AC_WORK_SHIFT W
WHERE W.CLIENT_ID = GC
Just add date and use to_char ('HH24:MI') !
You can strip out the last part (or any part) with regular expression
Oracle REGEXP_REPLACE does just that.
select REGEXP_REPLACE( TO_CHAR(NVL(arg1 - arg2, TO_DSINTERVAL('0 00:00:00'))), '..*') from table1
SQL> SELECT
2 TO_CHAR(TIMESTAMP '1969-12-31 00:00:00' + to_dsinterval('0 00:03:01.954321'),
3 '"T minus" HH24 "hours" MI "minutes" SS.FF3 "seconds."'
4 ) AS elapsed_time
5 FROM
6 dual;
ELAPSED_TIME
-------------------------------------------
T minus 00 hours 03 minutes 01.954 seconds.