SQL - extract time and offset from it - sql

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.

Related

How to format seconds as integer into HH:MM:SS in Presto

I have a table as:
id time_seconds
5 140
6 5
7 15000
I want to get it as:
id time_format
5 23:52
6 00:05
7 04:10:00
Basically format of HH:MM:SS
Now the thing is that I don't have many records with hours so the HH: should be in the output only if there are hours. Otherwise it should be just MM:SS
Presto has function that does similar thing
SELECT parse_duration('60s');
But the output isn't what I need
0 00:01:00.000
and I'm not sure this is the way to use it?
This is what I did so far:
select id, concat(cast(time_seconds as varchar(10)),'s')
from mytable
not sure how to continue
You can readily convert this to a time:
select time '00:00:00' + time_seconds * interval '1' second
I think that that is the best way to represent the value. Adding the hour conditionally seems like a bad idea -- it can lead to confusion.
I think the following will work on times:
select (case when time_seconds < 60 * 60
then date_format(time '00:00:00' + time_seconds * interval '1' second, '%i:%s')
else date_format(time '00:00:00' + time_seconds * interval '1' second, '%H:%i:%s')
end) as hhmmss
This is not a particularly elegant approach, but it'll produce the targeted output format.
Without dropping the potential '00:' hours portion:
select regexp_replace(concat(
cast(round(time_seconds/3600) as varchar),':',
cast(round((time_seconds%3600)/60) as varchar),':',
cast(round((time_seconds%3600)%60) as varchar)
),'(\d+)(:|\b)', match -> if(length(match)=1,'0'||match[1],match[1]) || match[2])
If '00:' hours must be dropped, then one could wrap the above in an additional: regexp_replace(<THE_INNER_RESULT>,'(^00:)(.*)','$2').
Note: This also doesn't satisfy the possible case of time_seconds>86400 (handling days) which #hkravitz points out in their comment.

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;

In Oracle, how do I subtract a month from timestamp retaining the millisecond part [duplicate]

This question already has answers here:
Add 2 months to current timestamp
(5 answers)
Closed 8 years ago.
In Oracle, how do I subtract a month from timestamp retaining the millisecond part.
I am using add_months function but it is truncating the milliseconds. Is there a built in function or I have to manually extract month part of timestamp and add to it?
When you use add_months() function, data type conversion takes place - the value of timestamp data type becomes a value of date data type, which does not have fractional seconds.
As #jonearles absolutely correctly pointed out in a comment to the answer, using presented above method to subtract one or more months from a given value of timestamp data type preserving milliseconds, may cause ORA-01839: date not valid for month specified.
To prevent that error from popping up, the query can be rewritten as follows:
clear screen;
with t1(col) aS(
select current_timestamp from dual union all
select timestamp '2000-03-30 12:12:12.123' from dual union all
select timestamp '2014-03-30 01:12:59.64567' from dual
)
select col as cur
, cast(add_months(col, -1) as timestamp) + (col - cast(col as date)) as prev
from t1
Result:
CUR PREV
-------------------------------------- -------------------------------
14-FEB-14 01.45.46.344187000 PM 14-JAN-14 01.45.46.344187000 PM
30-MAR-00 12.12.12.123000000 PM 29-FEB-00 12.12.12.123000000 PM
30-MAR-14 01.12.59.645670000 AM 28-FEB-14 01.12.59.645670000 AM
You can get it done by using year to month interval literal (can produce invalid dates):
clear screen;
select current_timestamp as res_1
, current_timestamp - interval '1' month as res_2
from dual
Result:
RES_1 RES_2
------------------------------ -----------------------------
05-FEB-14 02.02.32.383089000 05-JAN-14 02.02.32.383089000

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