Oracle SQL: Converting a flexible integer into time - sql

i can convert a 4-digit-number (2300 e.g.) into (23:00) with the following Statement:
select to_char ( to_date ( 2300, 'HH24MI'), 'HH:MI') integer_time from dual;
Result:
INTEGER_TIME
--------
11:00
But how can I help myself if the values in the database are in addition stored as three digit value, if the first value is below then 10 (e.g. '937' for 9:37).
I kinda Need a double check Statement for this, anyone got a clue?

You need to LPAD it with '0' first
SELECT LPAD('937', 4, '0') FROM dual
A call to LPAD(.., 4) with a 4 character string is a no-op
SELECT LPAD('1937', 4, '0') FROM dual

You can use TO_CHAR with the format 'fm0999' if the value is stored as a NUMBER
select to_date(to_char(937, 'fm0999'), 'HH24MI') from dual;
see format models

As an alternative, you could avoid bouncing through a nominal date and just use number and string manipulation:
select to_char(trunc(937/100), 'FM00') ||':'|| to_char(mod(937, 100), 'FM00') from dual;
TO_CHAR
-------
09:37
... though if your starting value is actually a string rather than a number there's another level of implicit conversion going on. (If it is a string you probably have bigger problems with validation, but the lpad route might be the simplest way if you assume the data is always as expected).
With a range of values:
with t (n) as (
select 0 from dual
union all select 1 from dual
union all select 59 from dual
union all select 937 from dual
union all select 2300 from dual
union all select 2359 from dual
)
select n, to_char(trunc(n/100), 'FM00') ||':'|| to_char(mod(n, 100), 'FM00')
as integer_time
from t;
N INTEGER
---------- -------
0 00:00
1 00:01
59 00:59
937 09:37
2300 23:00
2359 23:59
If you don't want the leading zero - i.e. 937 instead of 09:37 - then change the first format model to FM90.
If your data isn't constrained to be integers in the range 0-2359 then this would format 'bad' times, possibly with hashes for the hour part, while bouncing through a date would error. Neither is ideal, but hopefully is a moot point.

Related

ORA-01850: hour must be between 0 and 23

Need assistance on Converting a Military to Standard Time.
I have the code below: However, it gives me error ORA-01850
select to_char (to_date(tst_time, 'HH24MI'), 'HH:MI AM') integer_time FROM STATUS_HISTORY
where tst_id = '75344'
I'm thinking that its because the value on tst_time only has 3 characters for the first 2 entry. How can I add 0 on the beginning of the tst_time if it contains only 3 numbers?
Table Value Expected Result
TST_Time integer_time
958 09:58 AM
541 05:41 AM
1609 04:09 PM
If I understand correctly, you can use lpad():
select to_char(to_date(lpad(tst_time, 4, '0'), 'HH24MI'), 'HH:MI AM') integer_time
from (select '1609' as tst_time from dual union all
select '948' from dual
)
This adds a zero in front when necessary.
Assuming tst_time is a number - which seems likely; if it was a string it would make sense for it to already have the leading zero - then you are doing an implicit conversion to a string with the to_date() call.
If you make that an explicit conversion then you can introduce the leading zero(s) at that point:
to_char(to_date(to_char(tst_time, 'FM0000'), 'HH24MI'), 'HH:MI AM')
The to_char(tst_time, 'FM0000') converts 958 to 0958. (The FM part stops it having a leading space to allow for a +/- sign; doesn't really matter here though.)
db<>fiddle

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

Number to HH24:MM conversion - SQL, Oracle

We have number pairs like 810 1015 that mean the hour and minute. We have to calculate the minute difference of the pair. The example above would give 125 (minutes).
What solution would you give? I thought about converting to string and substringing then concatenating, but can't know if it is 3 or 4 long and using IF ELSE but would be too complicated (if no other solution exist I am left with this). Also thought about somehow converting to base 60 and subtracting, but also too complicated.
Thanks in advance.
Edit: This solution is based on Plirkee's comment to lpad numbers to get 4-character strings, and on Stefano Zanini's solution modified to allow for 0 hour, and 24-hour format.
If last two digits always represent minutes, and if hours are always in 24-hour format:
with t(time1, time2) as (
select 810, 1015 from dual union all
select 20, 1530 from dual
),
conv(time1, time2) as (
select lpad(to_char(time1), 4, '0'),
lpad(to_char(time2), 4, '0')
from t
)
select time1,
time2,
24 * 60 * (to_date(time2, 'HH24MI') - to_date(time1, 'HH24MI')) diff_minutes
from conv;
How about storing the data as a DATA datetype, using an standard date portion, such as 01-10-2000. So you data would be
01-01-2000 8:10:00
01-01-2000 10:15:00
etc
Then you can just do simple date math :)
Assuming 3 digits is the minimum length of your numbers (otherwise you'd have ambiguous cases), this following query should do the trick
select (to_date(substr(t2, 1, length(t2)-2) || ':' || substr(t2, length(t2)-1, length(t2)), 'HH:MI') -
to_date(substr(t1, 1, length(t1)-2) || ':' || substr(t1, length(t1)-1, length(t1)), 'HH:MI')) * 24 * 60 cc
from (select 810 t1, 1015 t2 from dual)
The steps are:
explode the numbers in two parts each: last two digits as the minutes and the remaining digits as the hour
concatenate the two parts with a separator (in this example ':')
convert that concatenations into dates
multiply the difference between the two dates (which is in days) by 24 to get hours and by 60 to get minutes
Just an another tweak which can be used. Hope this helps.
SELECT
TO_CHAR(TO_DATE(LPAD(LPAD('1015',4,'0') - LPAD('810',4,'0'),4,'0'),'HH24MI'),'HH24')*60
+TO_CHAR(TO_DATE(lpad(lpad('1015',4,'0') - lpad('810',4,'0'),4,'0'),'HH24MI'),'MI') MINUTES
FROM dual;

Why does RPAD() on a Date column return only the date component?

I have a column in a table with a data type as DATE. When I fetch the column via a query (I've used SYSDATE as an example, but the behavior is the same), I get the date/time - which I understand.
SELECT SYSDATE
FROM DUAL
SYSDATE
--------------------
21-Feb-11 12:24:39 PM
Now, using rpad() returns only the date part
SELECT SYSDATE, RPAD(SYSDATE, '9')
FROM DUAL
SYSDATE | RPAD(SYSDATE, '9')
----------------------|-------------------
21-Feb-11 12:27:14 PM | 21-FEB-11
Oracle documentation states:
RPAD returns expr1, right-padded to length n characters with expr2, replicated as many times as necessary. If expr1 is longer than n, then this function returns the portion of expr1 that fits in n.
Now sysdate returns characters > 9, so why doesn't, say rpad(16) return the date and the time ?
SELECT SYSDATE, RPAD(SYSDATE, '16')
FROM DUAL
SYSDATE | RPAD(SYSDATE, '16')
----------------------|-------------------
21-Feb-11 12:27:14 PM | 21-FEB-11
RPAD is a string function, so when you apply it to a DATE value Oracle first has to implicitly convert the date to a string, which it does using the session's default format mask, which usually does not include the time component. Try this instead:
SELECT SYSDATE, RPAD (TO_CHAR(SYSDATE,'DD-Mon-YY HH:MI:SS'), 16)
FROM DUAL;
Having said that, you are getting the time when you just select SYSDATE. If I try to replicate your case I see this:
SQL> alter session set nls_date_format = 'DD-MON-RR HH24:MI:SS';
SQL> select sysdate, rpad(sysdate,16) from dual;
SYSDATE RPAD(SYSDATE,16)
------------------ ----------------
21-FEB-11 11:20:20 21-FEB-11 11:20:
i.e. pretty much what you were hoping to see. Which makes me wonder: how are you setting the format so that SELECT SYSDATE FROM DUAL shows the time?

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.