Number to HH24:MM conversion - SQL, Oracle - sql

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;

Related

Create a datetime value from separate date time columns

I have individual columns for year, month, day, hour, minute, second and millisecond. I need to combine them all together into a date timestamp. I am able to make a date value, but can't seem to make a valid time one. I am working in Oracle and I have sample below. I'd like my value to look like the createtime column.
This did the trick:
to_timestamp(XT.MONTH1||'-'||XT.DAY1||'-'||XT.YEAR||' '|| xt.hour24||'.'||xt.minute||'.'||xt.second||'.'||xt.milliseconds,'MM-DD-YYYY HH24:MI:SS.FF')
That is quite easy but not pretty. You convert the columns from datatype NUMBER to VARCHAR2 padded with leading zeros, using fm to suppress any spaces for + or -:
SELECT TO_CHAR(month,'fm00') FROM mytable;
05
Next, you glue the converted columns together with the || operator to get a single, long, string:
SELECT TO_CHAR(year,'fm0000')||TO_CHAR(month,'fm00')|| ...
20200519...
This long string can now be converted to datatype DATE, or, in your case TIMESTAMP as you have milliseconds. You need to specify the date format you used, f.i. 'YYYYMMDD...'
SELECT TO_TIMESTAMP(TO_CHAR(year,'fm0000')|| ... , 'YYYYMM')
A complete example looks like:
SELECT TO_TIMESTAMP(
TO_CHAR(year,'fm0000')||TO_CHAR(month,'fm00')||
TO_CHAR(day,'fm00')||TO_CHAR(hour24,'fm00')||
TO_CHAR(minute,'fm00')||TO_CHAR(second,'fm00')||
TO_CHAR(ms,'fm000')
, 'YYYYMMDDHH24MISSFF3')
FROM (-- your table, as a mockup, I'll use DUAL
SELECT 2020 as year, 5 as month, 19 as day,
13 as hour24, 7 as minute, 10 as second,
300 as ms
FROM DUAL);
2020-05-19 13:07:10,300000000
EDIT:
The fill mode modifier fm supresses a leading space for positive numbers (to make room for the - sign for negative numbers). All parts of a date are positive, so you get a lot of spaces in your string.
SELECT TO_CHAR(x,'99'), TO_CHAR(x,'fm99')
FROM (SELECT -10 as x FROM DUAL UNION ALL SELECT 10 FROM DUAL);
| 10|10|
|-10|-10|
The documentation is a bit hidden under Format Model Modifiers.
Come to think of it, you might as well keep the spaces and adjust your format model:
SELECT TO_TIMESTAMP(
TO_CHAR(year,'0000')||TO_CHAR(month,'00')||
TO_CHAR(day,'00')||TO_CHAR(hour24,'00')||
TO_CHAR(minute,'00')||TO_CHAR(second,'00')||
TO_CHAR(ms,'000')
,' YYYY MM DD HH24 MI SS FF3')
FROm (SELECT 2020 as year, 5 as month, 19 as day,
13 as hour24, 7 as minute, 10 as second,
300 as ms FROM DUAL);

How to convert decimal to time in Oracle SQL?

In a certain table I'm querying, time is stored like this:
09:30 is stored as 9,3
14:25 is stored as 14,25
How can I, using SQL, convert 9,3 to 09:30 (hh:mm format)?
So far, the only thing I could find in other questions are people who need to convert 9,5 to 09:30 (in my case, 9,5 means 09:50).
Right now, I'm using PHP to convert this. But my DB server is better than my application server. Since I need to get a lot of data, it would be better to solve this using SQL.
Sorry if this is a stupid question, but I'm new to SQL. Feel free to ask for more questions if you need to.
Assuming those are numeric values, you can get the hour part with trunc(your_column) and the minutes part with 100 * mod(your_column, 1). That gives you two numbers; you can format those and concatenate them, again assuming you want a string result, e.g.:
to_char(trunc(your_column), 'FM00') ||':' || to_char(100 * mod(your_column, 1), 'FM00')
Or more simply, format the whole number as a single string in one step, by telling it to use a colon as the decimal separator (with anything as the group separator - that isn't used):
to_char(your_column, 'FM00D00', 'NLS_NUMERIC_CHARACTERS=:,')
Demo with sample data in a CTE:
-- CTE for sample data
with your_table (time) as (
select 9.3 from dual
union all select 9.03 from dual
union all select 14.25 from dual
)
-- actual query
select time,
to_char(trunc(time), 'FM00') ||':' || to_char(100 * mod(time, 1), 'FM00') as new_time1,
to_char(time, 'FM00D00', 'NLS_NUMERIC_CHARACTERS=:,') as new_time2
from your_table;
TIME NEW_TIME1 NEW_TIME2
---------- --------- ---------
9.3 09:30 09:30
9.03 09:03 09:03
14.25 14:25 14:25
If you actually want an interval data type result then you can use the same split with trunc/mod but handle the two numbers differently:
select time,
numtodsinterval(trunc(time), 'HOUR')
+ numtodsinterval(100 * mod(time, 1), 'MINUTE') as new_time
from your_table;
TIME NEW_TIME
---------- -------------------
9.3 +00 09:30:00.000000
9.03 +00 09:03:00.000000
14.25 +00 14:25:00.000000
Or you can convert to that time on a nominal date if you prefer, e.g.:
date '2000-01-01'
+ trunc(your_column), 'HOUR')
+ numtodsinterval(100 * mod(your_column, 1), 'MINUTE')
At some point you'll run into problems if you have values that don't really represent valid times - for example, 9.60 or 25.3. Either of the queries above will handle that to some extent - at least, up to 99.99 for the first one - but won't necessarily give useful results. The string would give you '09:60' and '25:30', while the interval would give you '0 10:00:00' and '1 01:30:00', which is slightly more sensible perhaps. That's the danger of using an inappropriate data type though.
Preserving a comment from #BobJarvis, if you actually want a date data type result then you can convert the number to a string without any separator and then convert from that to a date:
to_date(to_char(your_column, '00V00'), 'HH24MI')
which will give you that time on the first day of the current month. However, that will error with 9.60 or 25.3.

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

Subtracting Dates in Oracle - Number or Interval Datatype?

I have a question about some of the internal workings for the Oracle DATE and INTERVAL datatypes. According to the Oracle 11.2 SQL Reference, when you subtract 2 DATE datatypes, the result will be a NUMBER datatype.
On cursory testing, this appears to be true:
CREATE TABLE test (start_date DATE);
INSERT INTO test (start_date) VALUES (date'2004-08-08');
SELECT (SYSDATE - start_date) from test;
will return a NUMBER datatype.
But now if you do:
SELECT (SYSDATE - start_date) DAY(5) TO SECOND from test;
you get an INTERVAL datatype. In other words, Oracle can convert the NUMBER from the DATE subtraction into an INTERVAL type.
So now I figured I could try putting in a NUMBER datatype directly in the brackets (instead of doing 'SYSDATE - start_date' which results in a NUMBER anyways):
SELECT (1242.12423) DAY(5) TO SECOND from test;
But this results in the error:
ORA-30083: syntax error was found in interval value expression
So my question is: what's going on here? It seems like subtracting dates should lead to a NUMBER (as demonstrated in SELECT statement #1), which CANNOT be automatically cast to INTERVAL type (as demonstrated in SELECT statement #3). But Oracle seems to be able to do that somehow if you use the DATE subtraction expression instead of putting in a raw NUMBER (SELECT statement #2).
Thanks
Ok, I don't normally answer my own questions but after a bit of tinkering, I have figured out definitively how Oracle stores the result of a DATE subtraction.
When you subtract 2 dates, the value is not a NUMBER datatype (as the Oracle 11.2 SQL Reference manual would have you believe). The internal datatype number of a DATE subtraction is 14, which is a non-documented internal datatype (NUMBER is internal datatype number 2). However, it is actually stored as 2 separate two's complement signed numbers, with the first 4 bytes used to represent the number of days and the last 4 bytes used to represent the number of seconds.
An example of a DATE subtraction resulting in a positive integer difference:
select date '2009-08-07' - date '2008-08-08' from dual;
Results in:
DATE'2009-08-07'-DATE'2008-08-08'
---------------------------------
364
select dump(date '2009-08-07' - date '2008-08-08') from dual;
DUMP(DATE'2009-08-07'-DATE'2008
-------------------------------
Typ=14 Len=8: 108,1,0,0,0,0,0,0
Recall that the result is represented as a 2 seperate two's complement signed 4 byte numbers. Since there are no decimals in this case (364 days and 0 hours exactly), the last 4 bytes are all 0s and can be ignored. For the first 4 bytes, because my CPU has a little-endian architecture, the bytes are reversed and should be read as 1,108 or 0x16c, which is decimal 364.
An example of a DATE subtraction resulting in a negative integer difference:
select date '1000-08-07' - date '2008-08-08' from dual;
Results in:
DATE'1000-08-07'-DATE'2008-08-08'
---------------------------------
-368160
select dump(date '1000-08-07' - date '2008-08-08') from dual;
DUMP(DATE'1000-08-07'-DATE'2008-08-0
------------------------------------
Typ=14 Len=8: 224,97,250,255,0,0,0,0
Again, since I am using a little-endian machine, the bytes are reversed and should be read as 255,250,97,224 which corresponds to 11111111 11111010 01100001 11011111. Now since this is in two's complement signed binary numeral encoding, we know that the number is negative because the leftmost binary digit is a 1. To convert this into a decimal number we would have to reverse the 2's complement (subtract 1 then do the one's complement) resulting in: 00000000 00000101 10011110 00100000 which equals -368160 as suspected.
An example of a DATE subtraction resulting in a decimal difference:
select to_date('08/AUG/2004 14:00:00', 'DD/MON/YYYY HH24:MI:SS'
- to_date('08/AUG/2004 8:00:00', 'DD/MON/YYYY HH24:MI:SS') from dual;
TO_DATE('08/AUG/200414:00:00','DD/MON/YYYYHH24:MI:SS')-TO_DATE('08/AUG/20048:00:
--------------------------------------------------------------------------------
.25
The difference between those 2 dates is 0.25 days or 6 hours.
select dump(to_date('08/AUG/2004 14:00:00', 'DD/MON/YYYY HH24:MI:SS')
- to_date('08/AUG/2004 8:00:00', 'DD/MON/YYYY HH24:MI:SS')) from dual;
DUMP(TO_DATE('08/AUG/200414:00:
-------------------------------
Typ=14 Len=8: 0,0,0,0,96,84,0,0
Now this time, since the difference is 0 days and 6 hours, it is expected that the first 4 bytes are 0. For the last 4 bytes, we can reverse them (because CPU is little-endian) and get 84,96 = 01010100 01100000 base 2 = 21600 in decimal. Converting 21600 seconds to hours gives you 6 hours which is the difference which we expected.
Hope this helps anyone who was wondering how a DATE subtraction is actually stored.
You get the syntax error because the date math does not return a NUMBER, but it returns an INTERVAL:
SQL> SELECT DUMP(SYSDATE - start_date) from test;
DUMP(SYSDATE-START_DATE)
--------------------------------------
Typ=14 Len=8: 188,10,0,0,223,65,1,0
You need to convert the number in your example into an INTERVAL first using the NUMTODSINTERVAL Function
For example:
SQL> SELECT (SYSDATE - start_date) DAY(5) TO SECOND from test;
(SYSDATE-START_DATE)DAY(5)TOSECOND
----------------------------------
+02748 22:50:04.000000
SQL> SELECT (SYSDATE - start_date) from test;
(SYSDATE-START_DATE)
--------------------
2748.9515
SQL> select NUMTODSINTERVAL(2748.9515, 'day') from dual;
NUMTODSINTERVAL(2748.9515,'DAY')
--------------------------------
+000002748 22:50:09.600000000
SQL>
Based on the reverse cast with the NUMTODSINTERVAL() function, it appears some rounding is lost in translation.
A few points:
Subtracting one date from another results in a number; subtracting one timestamp from another results in an interval.
Oracle converts timestamps to dates internally when performing timestamp arithmetic.
Interval constants cannot be used in either date or timestamp arithmetic.
Oracle 11gR2 SQL Reference Datetime Matrix
Use extract() function to retrieve hour / minute / seconds from interval value. See below example, how to get hours from two timestamp columns. Hope this helps!
select INS_TS, MAIL_SENT_TS, extract( hour from (INS_TS - MAIL_SENT_TS) ) hourDiff from MAIL_NTFCTN;
select TIMEDIFF (STR_TO_DATE('07:15 PM', '%h:%i %p') , STR_TO_DATE('9:58 AM', '%h:%i %p'))

SQL - extract time and offset from it

If I have a DateTime field within a table. How can I extract just the time from the row and then return an offset value.
For example, I want to offset against midnight. So if the time is 00:45, I want the value 45. If the time is 23:30 I want the value -30.
Thanks.
Try:
(datecol - round(datecol))*24*60
For example:
with times as
( select trunc(sysdate) t from dual
union
select trunc(sysdate)+0.25 t from dual
union
select trunc(sysdate)+0.5 t from dual
union
select trunc(sysdate)+0.75 t from dual
)
select (t-round(t))*24*60 from times;
0
360
-720
-360
Note that midday is treated as 720 minutes before midnight.
Ok..assuming time from 12 to 23 hrs as before midnight and 0 to 11 hrs as after midnight, in other words as #Tony Andrews said midday is treated as 720 minutes before midnight
Here is another solution -
SELECT (
CASE
WHEN TO_CHAR(datetimefield,'HH24') BETWEEN 12 AND 23
THEN -1 * (60 * (12 - TO_CHAR(datetimefield,'HH')) - TO_CHAR(datetimefield,'MI'))
WHEN TO_CHAR(datetimefield,'HH24') BETWEEN 0 AND 11
THEN 60 * (TO_CHAR(datetimefield,'HH')) + TO_CHAR(datetimefield,'MI')
END) diff
FROM (select systimestamp datetimefield from dual);
I used my systemtimestamp for testing, You need to replace the the query select systimestamp datetimefield from dual to fetch your own datetime field from your table to make it work for you.
A list of Oracle-supported datetime formats can be found here: http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/sql_elements004.htm
You can use these formats with the to_char function - for example:
select to_char(sysdate, 'mi') from dual
You seem to have picked a way of representing times that isn't among those supported, but Tony's answer should meet your requirements.