How to round up time interval to next day in Oracle SQL?
select apppackage
, numtodsinterval(
sum( trunc(extract (day from (periods)) * 86400
+ extract (hour from (periods)) *3600
+ extract (minute from (periods))*60
+ extract (second from (periods)))
), 'SECOND') as retention_period
, count(apppackage) as users
from retentions
where apppackage = 'com.Freesoul.Rotter'
group by apppackage;
The output of this is
'com.Freesoul.Rotter' '+2969 04:32:47.000000' '3'
and desired output is
'com.Freesoul.Rotter' '2970' '3'
but if the output of query is
'com.Freesoul.Rotter' '+2969 00:00:00.000000' '3'
then desired output is
'com.Freesoul.Rotter' '2969' '3'
column period is of INTERVAL DAY(9) TO SECOND(6) type
and i won't mind if the retention_period is changed to number datatype.
I'll be grateful if anyone can suggest change in my query to attain the desired output.
The result of your sum is in seconds, so you don't really need to convert it to an interval at all. Just divide by 60*60*24 to get the answer in days, and round it up with ceil():
select apppackage
, ceil(sum( trunc(extract (day from (periods)) * 86400
+ extract (hour from (periods)) *3600
+ extract (minute from (periods))*60
+ extract (second from (periods)))
) / 86400) as retention_period
, count(apppackage) as users
from retentions
where apppackage = 'com.Freesoul.Rotter'
group by apppackage;
Demo with artificial data in a CTE just to mimic your expected results, for both scenarios:
-- CTE for sample data
with retentions (apppackage, periods) as (
select 'com.Freesoul.Rotter', interval '+2967 04:32:47.000000' day(9) to second(6) from dual
union all
select 'com.Freesoul.Rotter', interval '1' day from dual
union all
select 'com.Freesoul.Rotter', interval '1' day from dual
union all
select 'com.Freesoul.XYZ', interval '+2967 00:00:00.000000' day(9) to second(6) from dual
union all
select 'com.Freesoul.XYZ', interval '1' day from dual
union all
select 'com.Freesoul.XYZ', interval '1' day from dual
)
-- actual query
select apppackage
, ceil(sum( trunc(extract (day from (periods)) * 86400
+ extract (hour from (periods)) *3600
+ extract (minute from (periods))*60
+ extract (second from (periods)))
) / 86400) as retention_period
, count(apppackage) as users
from retentions
where apppackage = 'com.Freesoul.Rotter'
-- extra clause for dummy data
or apppackage = 'com.Freesoul.XYZ'
group by apppackage;
APPPACKAGE RETENTION_PERIOD USERS
------------------- ---------------- ----------
com.Freesoul.XYZ 2969 3
com.Freesoul.Rotter 2970 3
Your expected output shows a plain number. If you actually want it as an interval, but as the whole number of days, just pass ceil'd number into numtodsinterval or more simply (and usually faster for some reason) multiply by interval '1' day.
With the same dummy data:
select apppackage
, ceil(sum( trunc(extract (day from (periods)) * 86400
+ extract (hour from (periods)) *3600
+ extract (minute from (periods))*60
+ extract (second from (periods)))
) / 86400) * interval '1' day as retention_period
, count(apppackage) as users
...
APPPACKAGE RETENTION_PERIOD USERS
------------------- --------------------- ----------
com.Freesoul.XYZ +2969 00:00:00.000000 3
com.Freesoul.Rotter +2970 00:00:00.000000 3
As #mathguy pointed out, you probably don't need or want the trunc() call in there; that is removing the fractional seconds from each period before they are summed, which sounds insignificant but could easily affect the result you get.
How about this?
CASE WHEN numtodsinterval(extract (day from periods), 'DAY') = periods THEN
extract (day from periods)
ELSE
extract (day from periods) + 1
END
Related
I have a table with two columns, Start_time and SLA.
Start time updates for each day and is in a date format e.g., 01-Jun-2021 19:15:38
SLA column is having fixed HH24MI as 2010
I want 1915 - 2010 to be -00:55 (as in HH24MI format)
SELECT TO_CHAR((TO_CHAR(START_TIME,'HH24')||TO_CHAR(START_TIME,'MI'))-2010,'0000')
FROM DUAL;
Above will give the result as -0095 but I want it to be -00:55
Store the start_time as a DATE data type and the sla as an INTERVAL DAY TO SECOND data type:
CREATE TABLE table_name (
start_time DATE,
sla INTERVAL DAY TO SECOND
);
Then your data would be:
INSERT INTO table_name ( start_time, sla ) VALUES (
TO_DATE('01-Jun-2021 19:15:38', 'DD-MON-YYYY HH24:MI:SS', 'NLS_DATE_LANGUAGE=American'),
INTERVAL '20:10:00' HOUR TO SECOND
);
And, to find the difference, you can use:
SELECT start_time,
sla,
(start_time - TRUNC(start_time)) DAY TO SECOND - sla AS difference
FROM table_name
Which outputs:
START_TIME
SLA
DIFFERENCE
2021-06-01 19:15:38
+00 20:10:00.000000
-000000000 00:54:22.000000000
If you want the output as a formatted string, rather than as an interval, then:
SELECT start_time,
sla,
CASE WHEN difference < INTERVAL '0' HOUR THEN '-' END
|| TO_CHAR( ABS( EXTRACT( HOUR FROM difference ) ), 'FM00' )
|| TO_CHAR( ABS( EXTRACT( MINUTE FROM difference ) ), 'FM00' )
AS difference
FROM (
SELECT start_time,
sla,
(start_time - TRUNC(start_time)) DAY TO SECOND - sla AS difference
FROM table_name
)
Which outputs:
START_TIME
SLA
DIFFERENCE
2021-06-01 19:15:38
+00 20:10:00.000000
-0054
db<>fiddle here
This query: select EXTRACT(HOUR FROM 1435047532) as hour from TABLENAME;
Returns this error: "invalid extract field for extract source".
I'm trying to extract the hour from a given timestamp. Maybe the problem is the format of timestamp field is NUMBER and not TIMESTAMP?
You can convert your numeric seconds-since-epoch time to a TIMESTAMP type using:
TIMESTAMP '1970-01-01 00:00:00' + NUMTODSINTERVAL( your_time_since_epoch, 'SECOND' )
So to get the hours:
SELECT EXTRACT( HOUR FROM TIMESTAMP '1970-01-01 00:00:00'
+ NUMTODSINTERVAL( 1435047532, 'SECOND' ) )
FROM DUAL;
If you need to handle leap seconds then you will need to create a function/package to handle this.
try this
select extract(hour from (select to_date('19700101', 'YYYYMMDD')
+ ( 1 / 24 / 60 / 60 ) * 1435047532 from dual)) from dual
I am just trying to add 1 hour to a value, it is kind of complicated on where and why i am doing this but basically i just need to query something like this
select DATE_ADD(hh,1,'2014-10-15 03:30:00 pm') from dual
I keep reading old articles that say to use dateAdd or date_add but I keep getting invalid identifier errors.
select sysdate + 1/24 from dual;
sysdate is a function without arguments which returns DATE type
+ 1/24 adds 1 hour to a date
select to_char(to_date('2014-10-15 03:30:00 pm', 'YYYY-MM-DD HH:MI:SS pm') + 1/24, 'YYYY-MM-DD HH:MI:SS pm') from dual;
Use an interval:
select some_date_column + interval '1' hour
from your_table;
You can use INTERVAL type or just add calculated number value - "1" is equal "1 day".
first way:
select date_column + INTERVAL '0 01:00:00' DAY TO SECOND from dual;
second way:
select date_column + 1/24 from dual;
First way is more convenient when you need to add a complicated value - for example, "1 day 3 hours 25 minutes 49 seconds".
See also: http://www.oracle-base.com/articles/misc/oracle-dates-timestamps-and-intervals.php
Also you have to remember that oracle have two interval types - DAY TO SECOND and YEAR TO MONTH.
As for me, one interval type would be better, but I hope people in oracle knows, what they do ;)
Old way:
SELECT DATE_COLUMN + 1 is adding a day
SELECT DATE_COLUMN + N /24 to add hour(s) - N being number of hours
SELECT DATE_COLUMN + N /1440 to add minute(s) - N being number of minutes
SELECT DATE_COLUMN + N /86400 to add second(s) - N being number of seconds
Using INTERVAL:
SELECT DATE_COLUMN + INTERVAL 'N' HOUR or MINUTE or SECOND - N being a number of hours or minutes or seconds.
To add/subtract from a DATE, you have 2 options :
Method #1 :
The easiest way is to use + and - to add/subtract days, hours, minutes, seconds, etc.. from a DATE, and ADD_MONTHS() function to add/subtract months and years from a DATE. Why ? That's because from days, you can get hours and any smaller unit (1 hour = 1/24 days), (1 minute = 1/1440 days), etc... But you cannot get months and years, as that depends on the month and year themselves, hence ADD_MONTHS() and no add_years(), because from months, you can get years (1 year = 12 months).
Let's try them :
SELECT TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS') FROM dual; -- prints current date: 19-OCT-2019 20:42:02
SELECT TO_CHAR((SYSDATE + 1/24), 'DD-MON-YYYY HH24:MI:SS') FROM dual; -- prints date + 1 hour: 19-OCT-2019 21:42:02
SELECT TO_CHAR((SYSDATE + 1/1440), 'DD-MON-YYYY HH24:MI:SS') FROM dual; -- prints date + 1 minute: 19-OCT-2019 20:43:02
SELECT TO_CHAR((SYSDATE + 1/86400), 'DD-MON-YYYY HH24:MI:SS') FROM dual; -- prints date + 1 second: 19-OCT-2019 20:42:03
-- Same goes for subtraction.
SELECT SYSDATE FROM dual; -- prints current date: 19-OCT-19
SELECT ADD_MONTHS(SYSDATE, 1) FROM dual; -- prints date + 1 month: 19-NOV-19
SELECT ADD_MONTHS(SYSDATE, 12) FROM dual; -- prints date + 1 year: 19-OCT-20
SELECT ADD_MONTHS(SYSDATE, -3) FROM dual; -- prints date - 3 months: 19-JUL-19
Method #2 : Using INTERVALs, you can or subtract an interval (duration) from a date easily. More than that, you can combine to add or subtract multiple units at once (e.g 5 hours and 6 minutes, etc..)
Examples :
SELECT TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS') FROM dual; -- prints current date: 19-OCT-2019 21:34:15
SELECT TO_CHAR((SYSDATE + INTERVAL '1' HOUR), 'DD-MON-YYYY HH24:MI:SS') FROM dual; -- prints date + 1 hour: 19-OCT-2019 22:34:15
SELECT TO_CHAR((SYSDATE + INTERVAL '1' MINUTE), 'DD-MON-YYYY HH24:MI:SS') FROM dual; -- prints date + 1 minute: 19-OCT-2019 21:35:15
SELECT TO_CHAR((SYSDATE + INTERVAL '1' SECOND), 'DD-MON-YYYY HH24:MI:SS') FROM dual; -- prints date + 1 second: 19-OCT-2019 21:34:16
SELECT TO_CHAR((SYSDATE + INTERVAL '01:05:00' HOUR TO SECOND), 'DD-MON-YYYY HH24:MI:SS') FROM dual; -- prints date + 1 hour and 5 minutes: 19-OCT-2019 22:39:15
SELECT TO_CHAR((SYSDATE + INTERVAL '3 01' DAY TO HOUR), 'DD-MON-YYYY HH24:MI:SS') FROM dual; -- prints date + 3 days and 1 hour: 22-OCT-2019 22:34:15
SELECT TO_CHAR((SYSDATE - INTERVAL '10-3' YEAR TO MONTH), 'DD-MON-YYYY HH24:MI:SS') FROM dual; -- prints date - 10 years and 3 months: 19-JUL-2009 21:34:15
The calculation is simple
if you want to add 1 hour in the date .
every day have 24 hour , you can add .
select sysdate + 1/24 from dual;
if you want 1 day to add
select sysdate + 24/24 from dual;
or
select sysdate + 1 from dual;
same as for 2, 3 , 4 day
For static date you have the answer below.
From my view, I am getting a column with value like '0 0:0:0.343009' which shows the difference between two timestamps, to millisecond precision.
I would like to show them as a string like 343 milliseconds or other value but should be a millisecond conversion.
I had similar post in the past but that time column was a DATE datatype, and this time it is a TIMESTAMP. I am using the FLOOR function to change the output to a numeric value to show a more user friendly result.
I used sample query to find a difference of dates. Here created_time is a TIMESTAMP datatype:
select msg_guid,
(max(case when payload_type = 1 then created_time end) -
(case when max(case when payload_type = 2 then created_time end) <>
trunc(max(case when payload_type = 2 then created_time end))
then max(case when payload_type = 2 then created_time end)
when max(case when payload_type = 3 then created_time end) <>
trunc(max(case when payload_type = 3 then created_time end))
then max(case when payload_type = 3 then created_time end)
end)
) as diff
from table t
group by msg_guid;
When you add or subtract timestamps, the result is an interval, not another timestamp. You can use the extract function to pull the components out of that. If you value is always going to be sub-second you can just extract the seconds, and multiply by a thousand to get the milliseconds:
with t as (
select 1 as msg_guid,
interval '0 0:0:0.343009' day to second as diff
from dual
)
select trunc(extract (second from diff) * 1000)
from t;
TRUNC(EXTRACT(SECONDFROMDIFF)*1000)
-----------------------------------
343
Here your real query would take the place of the dummy CTE I used with an interval literal.
If the interval might be more than a second then you would presumably want to get the entire value in milliseconds, so you'd need to extract all the elements and add them together, multiplying each based on what they represent - so a full day would be 86400000 milliseconds etc.; the plain elements would come out like:
column diff format a25
with t as (
select 1 as msg_guid,
systimestamp - trunc(systimestamp) as diff
from dual
)
select diff,
extract (day from diff) as dd,
extract (hour from diff) as hh,
extract (minute from diff) as mi,
extract (second from diff) as ss
from t;
DIFF DD HH MI SS
---------------------- ---------- ---------- ---------- ----------
0 9:13:26.150627 0 9 13 26.150627
And you'd combine them like:
with t as (
select 1 as msg_guid,
systimestamp - trunc(systimestamp) as diff
from dual
)
select diff,
trunc(1000 * (
extract (day from diff) * (60*60*24)
+ extract (hour from diff) * (60*60)
+ extract (minute from diff) * 60
+ extract (second from diff)
)) as milliseconds
from t;
DIFF MILLISECONDS
---------------------- ------------
0 9:13:27.650365 33207650
But based on your previous question, maybe you want it as a string, as the separate components:
with t as (
select 1 as msg_guid,
systimestamp - trunc(systimestamp) as diff
from dual
)
select diff,
extract (day from diff) || ' DAYS '
|| extract (hour from diff) || ' HOURS '
|| extract (minute from diff) || ' MINUTES '
|| trunc(extract (second from diff)) || ' SECONDS '
|| (trunc(extract (second from diff) * 1000)
- (trunc(extract (second from diff)) * 1000)) || ' MILLISECONDS'
as text
from t;
DIFF TEXT
---------------------- -------------------------------------------------------
0 9:43:38.896007 0 DAYS 9 HOURS 43 MINUTES 38 SECONDS 896 MILLISECONDS
SQL Fiddle based on your sample data, sort of, and with the time calculation reversed so the value is positive.
Alex Poole posted a brilliant solution to another user's issue at Oracle formatting date intervals but
Didn't make it clear earlier that while I have questions on what parts of Alex' original query do, the deisred goal is a result set showing roster entries by individuals in 14 day intervals. Understanding exactly what all parts of the query do is key to always to getting a good result. So much emphasis was placed on certain parts of the query and not enough on where I wanted to arrive. :)
In this part of the query, what does the divisor of 48 have to do with 30 minute intervals? Jokingly, it could be the new Secret of the Universe (Hitchhiker's Guide to the Galaxy) -- it's not 42 anymore but 48. :)
A co worker and I figured it might be for a 6-day work week (48 hours -- 6 8 hour days). Here is Alex' query, note that the object tbl_stat is located at the top of the post, not part of Alex' query per se, it was part of the poster's original question:
with tmp_tab as (
select start_time + (level - 1)/48 as period_start,
start_time + level/48 - interval '1' second as period_end
from (
select to_date(:start_time, 'DD/MM/YYYY HH24:MI:SS') start_time,
to_date(:end_time, 'DD/MM/YYYY HH24:MI:SS') end_time
from dual
)
connect by start_time + (level - 1)/48 < end_time
)
select to_char(tt.period_start, 'DD/MM/YYYY HH24:MI') dt,
count(ts.track_datetime)
from tmp_tab tt
left join tbl_stat ts
on ts.track_datetime between tt.period_start and tt.period_end
group by tt.period_start
order by tt.period_start;
Tha'ts the whole query, but what does the interval " / 48" pertain to as far as setting up 30 minute intervals, etc:
select start_time + (level - 1)/48 as period_start,
start_time + level/48 - interval '1' second as period_end
Thanks, hope that's not too inane a question but I really don't see what's what with it.
The documentation on datetime/interval arithmetic explains:
You can use NUMBER constants in arithmetic operations on date and timestamp values, but not interval values. Oracle internally converts timestamp values to date values and interprets NUMBER constants in arithmetic datetime and interval expressions as numbers of days. For example, SYSDATE + 1 is tomorrow. SYSDATE - 7 is one week ago. SYSDATE + (10/1440) is ten minutes from now.
(That's perhaps slightly misleading; SYSDATE + 1 is the same time tomorrow...)
It's common to see 1/24 used to represent an hour in calculations, 1/(24*60) to represent a minute, and 1/(24*60*60) to represent a second. Some people prefer that format - or 1/86400 - while some prefer interval '1' second or numtodsinterval(1, 'SECOND'), but they all mean the same in the end.
As Peter Lang said, 1/48 represents half an hour, as a fraction of one day. 1/24 is an hour, so 1/(2*24) is half an hour; it's the same as (1/2)*(1/24) if that helps.
You can compare two dates to see the fractional difference:
select 1/48, to_date('12:30', 'HH24:MI') - to_date('12:00', 'HH24:MI') as diff
from dual
1/48 DIFF
---------- ----------
.020833333 .020833333
The query I need to get the intervals within the pay periods of an overtime roster is done and I'll share it.
Here is my Frankenstein, which now HAS life. A curious thing, though ... I took a post literally and originally had my WHERE predicate as:
WHERE TRIM(UPPER(TO_CHAR(TO_DATE(TT.PERIOD_END, 'DD/MM/YYYY' ), 'DAY'))) = 'SATURDAY'
AND MOD(TT.PAY_PERIOD,2) <> 0
This curiously picked MONDAY PERIOD_END dates. I had to set the = to 'THURSDAY' to get Saturday dates. Well, taking out the un-needed TO_DATE() resolved this but I found it strange that it wigged out the way it did, offsetting what I was looking for by 2 days.
WITH TMP_TAB AS (
SELECT
START_TIME + (LEVEL - 1) AS PERIOD_START
,START_TIME + LEVEL + INTERVAL '13' DAY AS PERIOD_END
,LEVEL AS PAY_PERIOD
FROM (
SELECT
TO_DATE
(
TO_CHAR(
(
SELECT
CASE WHEN MOD(TO_NUMBER(TO_CHAR((SELECT MIN(ENTERED_DT) FROM OVTR_LOG) + DT1,'J')),2) = 0
THEN TRUNC((SELECT MIN(ENTERED_DT) FROM OVTR_LOG) + DT1 + 7)
ELSE TRUNC((SELECT MIN(ENTERED_DT) FROM OVTR_LOG) + DT1) END AS FIRST_PPE_DT
FROM (SELECT 7 - TO_NUMBER(TO_CHAR(MIN(ENTERED_DT),'D')) AS DT1 FROM OVTR_LOG)
),'DD/MM/YYYY'
), 'DD/MM/YYYY'
) START_TIME
,TO_DATE
(
TO_CHAR(
(
SELECT
CASE WHEN MOD(TO_NUMBER(TO_CHAR((SELECT MAX(ENTERED_DT) FROM OVTR_LOG) + DT1,'J')),2) = 0
THEN TRUNC((SELECT MAX(ENTERED_DT) FROM OVTR_LOG) + DT1 + 7)
ELSE TRUNC((SELECT MAX(ENTERED_DT) FROM OVTR_LOG) + DT1) END AS MOST_RECENT_PPE_DT
FROM (SELECT 7 - TO_NUMBER(TO_CHAR(MAX(ENTERED_DT),'D')) AS DT1 FROM OVTR_LOG)
),'DD/MM/YYYY'
), 'DD/MM/YYYY'
) END_TIME
FROM DUAL
)
CONNECT BY START_TIME + (LEVEL -1) < END_TIME
)
SELECT
TO_CHAR(TT.PERIOD_START, 'DD/MM/YYYY') PERIOD_START
,TO_CHAR(TT.PERIOD_END, 'DD/MM/YYYY') PERIOD_END
FROM TMP_TAB TT
--LEFT JOIN TO THE OTR
--ON WHATEVER
WHERE TRIM(UPPER(TO_CHAR(TT.PERIOD_END, 'DAY'))) = 'SATURDAY'
AND MOD(TT.PAY_PERIOD,2) <> 0
GROUP BY
TT.PERIOD_START
,TT.PERIOD_END
ORDER BY TT.PERIOD_START
This gives the following result set. My apologies for not being "in complete form" here -- as Alex suggested the help centre should be reviewed and it will be.
The original question included I had was what does / 48 do? It divides LEVEL according to the context LEVEL is used in, straight and simple.
Here is what I originally wanted as a result set and with the great help of Alex' query (thanks to Peter's original question) I got there. This should explain where I was trying to arrive.
PERIOD_START PERIOD_END
23/04/2011 07/05/2011
07/05/2011 21/05/2011
21/05/2011 04/06/2011
04/06/2011 18/06/2011
18/06/2011 02/07/2011
02/07/2011 16/07/2011
16/07/2011 30/07/2011
30/07/2011 13/08/2011
13/08/2011 27/08/2011
27/08/2011 10/09/2011
10/09/2011 24/09/2011
24/09/2011 08/10/2011
08/10/2011 22/10/2011
22/10/2011 05/11/2011
05/11/2011 19/11/2011
19/11/2011 03/12/2011
03/12/2011 17/12/2011
17/12/2011 31/12/2011
31/12/2011 14/01/2012
14/01/2012 28/01/2012
28/01/2012 11/02/2012
11/02/2012 25/02/2012
25/02/2012 10/03/2012
10/03/2012 24/03/2012
24/03/2012 07/04/2012
07/04/2012 21/04/2012
21/04/2012 05/05/2012
05/05/2012 19/05/2012
19/05/2012 02/06/2012
02/06/2012 16/06/2012