Related
I am trying to work on a query where there is date selection in the where clause i.e. if sysdate is Monday I have to get the dates from Monday to Saturday and Hours Between Morning 08:00:00 AM to Next Day Morning 07:00:00 AM. I am hardcoding the dates and Hours in the where clause, When I run the query data does not show.
Query:
SELECT TO_CHAR(sysdate, 'HH24:MI:SS'), REPLACE(TO_CHAR(sysdate, 'DAY'), ' ')
FROM dual
WHERE TO_CHAR(sysdate, 'HH24:MI:SS') BETWEEN '08:01:00' AND '08:00:00'
AND TO_CHAR(sysdate, 'DAY') >= 'MONDAY'
AND TO_CHAR(sysdate, 'DAY') <= 'SATURDAY';
You need to filter the wider range first (from 8 AM at Monday till the end of a Saturday, if I understood correctly) and then exclude time from 7 AM till 8 AM.
In the below code iw format element stands for ISO week, that starts on the Monday.
with a as (
select
date '2022-04-17'
+ interval '01:30:00' hour to second
+ interval '2' hour * level
as dt
from dual
connect by level < 90
)
select
to_char(dt, 'yyyymmdd') as day_
, listagg(to_char(dt, 'hh24:mi'), ',')
within group (order by dt asc) as hours
from a
where 1 = 1
/*From Mon 08 AM*/
and dt > trunc(dt, 'iw') +
interval '8' hour
/*Till Sat end of the day*/
and dt < trunc(dt, 'iw') + 6
/*and except minutes between 7 and 8 AM*/
and not (
to_char(dt, 'hh24mi') < '0800'
and to_char(dt, 'hh24mi') > '0700'
)
group by to_char(dt, 'yyyymmdd')
DAY_ | HOURS
:------- | :----------------------------------------------------------------
20220418 | 09:30,11:30,13:30,15:30,17:30,19:30,21:30,23:30
20220419 | 01:30,03:30,05:30,09:30,11:30,13:30,15:30,17:30,19:30,21:30,23:30
20220420 | 01:30,03:30,05:30,09:30,11:30,13:30,15:30,17:30,19:30,21:30,23:30
20220421 | 01:30,03:30,05:30,09:30,11:30,13:30,15:30,17:30,19:30,21:30,23:30
20220422 | 01:30,03:30,05:30,09:30,11:30,13:30,15:30,17:30,19:30,21:30,23:30
20220423 | 01:30,03:30,05:30,09:30,11:30,13:30,15:30,17:30,19:30,21:30,23:30
db<>fiddle here
(And what if sysdate isn't Monday?)
Therefore, could you explain a little bit better what is the input (dates? One date? SYSDATE?) and what is desired output (related to that input).
Basically, I don't understand what you want. Meanwhile, errors you made (if it'll help).
Format model is wrong; this is what you did:
SQL> select to_char(sysdate, 'DAY') day, length(to_char(sysdate, 'DAY')) len from dual;
DAY LEN
--------- ----------
FRIDAY 9
"FRIDAY" doesn't have 9 characters; it has 6 of them --> use the fm format modifier (it'll truncate trailing spaces):
SQL> select to_char(sysdate, 'fmDAY') day, length(to_char(sysdate, 'fmDAY')) len from dual;
DAY LEN
--------- ----------
FRIDAY 6
SQL>
Today (22.04.2022) is Friday. Your query searches for data whose day is between "MONDAY" and "SATURDAY". As you're comparing strings and alphabet goes as [A, B, ..., F, G, ..., M, N, ..., S, T], "F(riday)" is NEVER between M(onday) and S(aturday) so there's zero chance that it'll work.
As of hours: which time exactly is between 08:01 and 08:00? Time doesn't go backwards (unless you meant "08:01 today and 08:00 tomorrow").
if sysdate is Monday I have to get the dates from Monday to Saturday and Hours Between Morning 08:00:00 AM to Next Day Morning 07:00:00 AM.
You can find whether SYSDATE is Monday by comparing the day to the start of the ISO week (which will always be midnight on Monday):
SELECT *
FROM DUAL
WHERE SYSDATE - TRUNC(SYSDATE, 'IW') < 1
You can find out whether the hours are between 08:00 and 07:00 the next day by subtracting 8 hours and finding out whether the time is between 00:00 and 23:00:
SELECT *
FROM DUAL
WHERE (SYSDATE - INTERVAL '8' HOUR) - TRUNC(SYSDATE - INTERVAL '8' HOUR)) DAY TO SECOND
<= INTERVAL '23' HOUR;
You can combine the two to find out if the day is between Monday and Saturday and the time is between 08:00 and 07:00 on the next day (so for Saturday, it would include 7 hours of Sunday) using:
SELECT *
FROM DUAL
WHERE (SYSDATE - INTERVAL '8' HOUR) - TRUNC(SYSDATE - INTERVAL '8' HOUR), 'IW') < 6
AND (SYSDATE - INTERVAL '8' HOUR) - TRUNC(SYSDATE - INTERVAL '8' HOUR)) DAY TO SECOND
<= INTERVAL '23' HOUR;
Note: This does not use TO_CHAR so it is unaffected by any changes to the NLS_TERRITORY or NLS_DATE_LANGUAGE session parameters so it will always give the same answer (independent of the settings of the user who runs the query).
You can use such a combination
SELECT TO_CHAR(dt,'HH24:MI:SS','NLS_DATE_LANGUAGE=English') AS Hour,
TO_CHAR(dt,'Day','NLS_DATE_LANGUAGE=English') AS day
FROM t
WHERE TO_CHAR(dt,'Dy','NLS_DATE_LANGUAGE=English') IN ('Tue','Wed','Thu','Fri')
AND TO_CHAR(dt, 'HH24:MI:SS') NOT BETWEEN '07:00:01' AND '08:00:00'
OR TO_CHAR(dt,'Dy','NLS_DATE_LANGUAGE=English') = 'Mon'
AND TO_CHAR(dt, 'HH24:MI:SS')>= '07:00:00'
OR TO_CHAR(dt,'Dy','NLS_DATE_LANGUAGE=English') = 'Sat'
AND TO_CHAR(dt, 'HH24:MI:SS')<= '08:00:00'
where needs to consider restricting the periods for the bound dates individually
Demo
I would like to select and return a number of rows depending on 'today's' date.
Something like...
SELECT * FROM my_table
WHERE
//some conditions AND
my_DATE BETWEEN trunc (sysdate, 'mm')/*current month*/ AND SYSDATE
However I would like to return:
Only rows for the last two months (excluding this month) if today's date 'dd' is less than 15
Return rows for the last two months (including this month) if today's date is >= 15
I am thinking of a case. Something like
WHERE
(CASE
when trunc (sysdate, 'dd') < 15 THEN
TO_CHAR(my_DATE, 'MMYYY') BETWEEN TO_CHAR((add_months(sysdate,-3)) AND TO_CHAR((add_months(sysdate,-1))
Any pointers are highly appreciated as I get acquainted with this arena. Thank you
I would do all the computation on sysdate, to help the optimizer user indexes:
where datecol >= add_months(trunc(sysdate, 'MON'),
(case when extract(day from sysdate) < 15 then -2 else -1 end)
) and
datecol < add_months(trunc(sysdate, 'MON'),
(case when extract(day from sysdate) < 15 then 0 else 1 end)
)
EDIT:
Based on the comment:
where datecol >= add_months(trunc(sysdate, 'MON'), -2) and
datecol < add_months(trunc(sysdate, 'MON'),
(case when extract(day from sysdate) < 15 then 0 else 1 end)
)
If this represents sample data:
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> select * from test order by id;
ID DATUM
---------- ----------
1 20.12.2020
2 07.01.2021
3 15.02.2021
4 25.02.2021
5 10.03.2021
then see whether the following code makes sense. The whole date '&&par_sysdate' shuld be replaced by sysdate in real life; for testing purposes, I used a parameter as sysdate returns today's date.
First example: par_sysdate = 10.03.2021 (day is less than 15):
SQL> select t.id, t.datum
2 from test t
3 where t.datum >=
4 trunc(add_months(date '&&par_sysdate',
5 -case when to_number(to_char(date '&&par_sysdate', 'dd')) < 15 then 2
6 else 1
7 end
8 ), 'mm')
9 and t.datum < case when to_number(to_char(date '&&par_sysdate', 'dd')) < 15 then
10 trunc(date '&&par_sysdate', 'mm')
11 else date '&&par_sysdate'
12 end;
Enter value for par_sysdate: 2021-03-10
ID DATUM
---------- ----------
2 07.01.2021
3 15.02.2021
4 25.02.2021
Second example: using today's date (20.03.2021) where day is greater than 15:
SQL> undefine par_sysdate
SQL> /
Enter value for par_sysdate: 2021-03-20
ID DATUM
---------- ----------
3 15.02.2021
4 25.02.2021
5 10.03.2021
SQL>
Or, as I said, using sysdate:
SQL> select t.id, t.datum
2 from test t
3 where t.datum >=
4 trunc(add_months(sysdate,
5 -case when to_number(to_char(sysdate, 'dd')) < 15 then 2
6 else 1
7 end
8 ), 'mm')
9 and t.datum < case when to_number(to_char(sysdate, 'dd')) < 15 then
10 trunc(sysdate, 'mm')
11 else sysdate
12 end;
ID DATUM
---------- ----------
3 15.02.2021
4 25.02.2021
5 10.03.2021
SQL>
Your requirements are not wholly clear, but I think you want something like this:
select t.*
from mytable t
where (to_number(to_char(sysdate, 'dd')) < 15
and t.dt >= add_months(trunc(sysdate, 'mm'),-3)
and t.dt < trunc(sysdate, 'mm')
)
or (to_number(to_char(sysdate, 'dd')) >= 15
and t.dt >= add_months(trunc(sysdate, 'mm'),-2)
and t.dt <= last_day(sysdate)
)
I have put a demo version of this code on db<>fiddle with an affordance for changing the date of today instead of using sysdate.
the previous months should be FULL not previous months based on today's date.
To get full months, truncate the date using the 'mm' mask, which returns the first of the month.
So, my aim is to be able to count time spent on certain activities in hour ranges.
My data contains: start of the certain activity and end of that activity,
for example I know that someone had break from '2019-01-09 17:04:34' to '2019-01-09 19:55:03'.
My aim is to calculate that this person spent 55 minutes on break in interval '17-18', 60 minutes on '18-19' and 55 minutes on '19-20'.
My idea was to always split the source so for the row containing start and and of the activity I would receive as many rows as my time range split in the hour ranges (for this sample data I would receive 3: rows with '2019-01-09 17:04:34' to '2019-01-09 17:59:59', '2019-01-09 18:00:00' to '2019-01-09 18:59:59' and '2019-01-09 19:00:00' to '2019-01-09 19:55:03')
If I could obtain something like that I could manage to count all things I need to. I predict that to obtain this result I should use CTE (as we don't know in how many ranges we need to split time interval), but I have no experience in it.
Hopefully I managed to explain my problem clearly. I work on oracle sql developer.
I'd be very grateful for your help on at least some tips.
Since you mentioned recursion, this uses recursive subquery factoring:
-- CTE for sample data
with your_table (id, start_time, end_time) as (
select 1, timestamp '2019-01-09 17:04:34', timestamp '2019-01-09 19:55:03' from dual
union all
select 2, timestamp '2019-01-09 23:47:01', timestamp '2019-01-10 02:05:03' from dual
union all
select 3, timestamp '2019-01-09 18:01:01', timestamp '2019-01-09 18:02:07' from dual
union all
select 4, timestamp '2019-01-09 13:00:00', timestamp '2019-01-09 14:00:01' from dual
),
-- recursive CTE
rcte (id, hour_period, minutes, period_start_time, end_time, hour_num) as (
select id,
-- first period is the original start hour
extract(hour from start_time),
-- minutes in first period, which can end at the end of that hour, or at original
-- end time if earlier
case when extract(minute from end_time) = 0
and end_time >= cast(trunc(start_time, 'HH') as timestamp) + interval '1' hour
then 60
else extract(minute from
least(cast(trunc(start_time, 'HH') as timestamp) + interval '1' hour, end_time)
- start_time
)
end,
-- calculate next period start
cast(trunc(start_time, 'HH') as timestamp) + interval '1' hour,
-- original end time
end_time,
-- first hour period (for later ordering)
1
from your_table
union all
select id,
-- this period's hour value
extract(hour from period_start_time),
-- minutes in this period - either 60 if we haven't reach the end time yet;
-- or if we have then the number of minutes from the end time
case when end_time < period_start_time + interval '1' hour
then extract(minute from end_time)
else 60
end,
-- calculate next period start
period_start_time + interval '1' hour,
-- original end time
end_time,
-- increment hour period (for later ordering)
hour_num + 1
from rcte
where period_start_time < end_time
)
select id, hour_period, minutes
from rcte
order by id, hour_num;
ID HOUR_PERIOD MINUTES
---------- ----------- ----------
1 17 55
1 18 60
1 19 55
2 23 12
2 0 60
2 1 60
2 2 5
3 18 1
4 13 60
4 14 0
It find finds the amount of time spent in the first hour of the period in the anchor member, then recursively looks at subsequent hours until the end time is reached, increasing the passed-on period end time each time; and in the recursive member it checks whether to use a fixed 60 minutes (if it knows the end time hasn't been reached) or use the actual minutes from the end time.
My example periods include ones that span midnight, cover less than an hour, and that start in the first minute of an hour - and which end in the first minute of an hour, which (in my calculation anyway) ends up with a row for that hour anyway and the number of minutes as zero. You can easily filter that out if you don't want to see it.
It is not entirely clear from your post how you want to handle non-zero seconds components (what combination of rounding and/or truncation). In any case, that can be coded easily, once a complete set of non-contradictory rules is agreed upon.
Other than that, your question consists of two parts: identify the proper hours for each id (each activity or event), and the duration of the part of that event during that hour. In the query below, using the CONNECT BY hierarchical technique, I generate the hours and the duration as an interval day to second. As I said, that can be converted to minutes (between 0 and 60) once you clarify the rounding rules.
with
your_table (id, start_time, end_time) as (
select 1, timestamp '2019-01-09 17:04:34', timestamp '2019-01-09 19:55:03'
from dual union all
select 2, timestamp '2019-01-09 23:47:01', timestamp '2019-01-10 02:05:03'
from dual union all
select 3, timestamp '2019-01-09 18:01:01', timestamp '2019-01-09 18:02:07'
from dual union all
select 4, timestamp '2019-01-09 13:00:00', timestamp '2019-01-09 14:00:01'
from dual
)
select id,
trunc(start_time, 'hh') + interval '1' hour * (level - 1) as hr,
case when level = 1 and connect_by_isleaf = 1
then end_time - start_time
when level = 1
then trunc(start_time, 'hh') + interval '1' hour - start_time
when connect_by_isleaf = 1
then end_time - trunc(end_time, 'hh')
else interval '1' hour
end as duration
from your_table
connect by trunc(start_time, 'hh') + interval '1' hour * (level - 1) < end_time
and prior id = id
and prior sys_guid() is not null
;
Output:
ID HR DURATION
---------- ------------------- -------------------
1 2019-01-09 17:00:00 +00 00:55:26.000000
1 2019-01-09 18:00:00 +00 01:00:00.000000
1 2019-01-09 19:00:00 +00 00:55:03.000000
2 2019-01-09 23:00:00 +00 00:12:59.000000
2 2019-01-10 00:00:00 +00 01:00:00.000000
2 2019-01-10 01:00:00 +00 01:00:00.000000
2 2019-01-10 02:00:00 +00 00:05:03.000000
3 2019-01-09 18:00:00 +00 00:01:06.000000
4 2019-01-09 13:00:00 +00 01:00:00.000000
4 2019-01-09 14:00:00 +00 00:00:01.000000
I have to get data from Oracle Table in which I have one datefield called periodstarttime and I want to get only the data aggregated(aggregation must be done for 1hr data) at minutes 00th,15th,30th,45th in a day with input data in 5 minute intervals
For example, if in my table I have periodstarttime as
periodstarttime data
05/04/2017 1:00:00 10
05/04/2017 1:05:00 1
05/04/2017 1:10:00 2
05/04/2017 1:15:00 3
05/04/2017 1:20:00 4
05/04/2017 1:25:00 5
05/04/2017 1:30:00
and so on....
then I want my result to look like:
periodstarttime data with 1hr aggregation
05/04/2017 1:00:00 data with 1hr aggregation from 1.00 to 2.00
05/04/2017 1:15:00 data with 1hr aggregation from 1.15 to 2.15
05/04/2017 1:30:00 data with 1hr aggregation from 1.30 to 2.30
05/04/2017 1:45:00 data with 1hr aggregation from 1.45 to 2.45
You can use TRUNC() and CASE to truncate to the nearest 15 minute interval and then use an analytic function with a windowing clause to get the sum over a rolling window:
SELECT DISTINCT
periodstarttime,
SUM( data ) OVER ( ORDER BY periodstarttime
RANGE BETWEEN INTERVAL '0' MINUTE PRECEDING
AND INTERVAL '45' MINUTE FOLLOWING
)
AS hour_total,
'data with 1hr aggregation from '
|| TO_CHAR( periodstarttime, 'HH24:MI' )
|| ' to '
|| TO_CHAR( periodstarttime + INTERVAL '1' HOUR, 'HH24:MI' )
AS range
FROM (
SELECT CASE
WHEN periodstarttime < TRUNC( periodstarttime, 'HH24' ) + INTERVAL '15' MINUTE
THEN TRUNC( periodstarttime, 'HH24' )
WHEN periodstarttime < TRUNC( periodstarttime, 'HH24' ) + INTERVAL '30' MINUTE
THEN TRUNC( periodstarttime, 'HH24' ) + INTERVAL '15' MINUTE
WHEN periodstarttime < TRUNC( periodstarttime, 'HH24' ) + INTERVAL '45' MINUTE
THEN TRUNC( periodstarttime, 'HH24' ) + INTERVAL '30' MINUTE
ELSE TRUNC( periodstarttime, 'HH24' ) + INTERVAL '45' MINUTE
END AS periodstarttime,
data
FROM table_name
)
ORDER BY periodstarttime;
Here is one way of doing it:
WITH your_table AS (SELECT to_date('05/04/2017 01:00:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 10 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 01:05:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 20 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 01:10:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 30 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 01:15:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 40 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 01:20:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 50 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 01:25:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 60 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 01:30:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 70 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 01:40:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 80 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 01:50:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 90 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 02:00:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 100 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 02:10:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 110 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 02:20:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 120 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 02:30:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 130 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 02:40:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 140 DATA FROM dual UNION ALL
SELECT to_date('05/04/2017 03:00:00', 'dd/mm/yyyy hh24:mi:ss') periodstarttime, 150 DATA FROM dual)
-- End of setting up data in your_table; you would not need the above since you already have a table with data in it
-- See the below SQL for the main statement:
SELECT DISTINCT TRUNC(periodstarttime, 'hh') + FLOOR((TRUNC(periodstarttime, 'mi') - TRUNC(periodstarttime, 'hh'))/(15/1440))*15/1440 periodstarttime,
sum(DATA) OVER (ORDER BY TRUNC(periodstarttime, 'hh') + FLOOR((TRUNC(periodstarttime, 'mi') - TRUNC(periodstarttime, 'hh'))/(15/1440))*15/1440
RANGE BETWEEN 0 PRECEDING AND 60/1440 FOLLOWING) sum_over_next_hour
FROM your_table
ORDER BY periodstarttime;
PERIODSTARTTIME SUM_OVER_NEXT_HOUR
---------------- ------------------
05/04/2017 01:00 660
05/04/2017 01:15 850
05/04/2017 01:30 770
05/04/2017 01:45 690
05/04/2017 02:00 750
05/04/2017 02:15 540
05/04/2017 02:30 290
05/04/2017 03:00 150
This works by:
finding the period start times, which we do by finding the minutes of each periodstarttime, dividing it by 15 minutes, finding the floor of that and then multiplying that new number by 15 minutes. That separates the times into 15 minute groups.
Now we know the groups, we can do a cumulative sum. Because we want values across the hour, we need to do a range between the current period and the current period + 1 hour. N.B. This is inclusive, so that the 02:00 values will be included in the cumulative sum for the 01:00 period. If you don't want that, change the range so that it goes up to 59 minutes (which is fine, since our periods are now 0, 15, 30 or 45 minutes past the hour).
Have a look at Windowing_clause of Analytic Functions.
Would be like this
select periodstarttime,
SUM(DATA) OVER (RANGE BETWEEN CURRENT ROW AND INTERVAL '1' HOUR FOLLOWING ORDER BY periodstarttime),
'data with 1hr aggregation from '||FIRST_VALUE(periodstarttime) OVER (RANGE BETWEEN CURRENT ROW AND INTERVAL '1' HOUR FOLLOWING ORDER BY periodstarttime)
||' to '||LAST_VALUE(periodstarttime) OVER (RANGE BETWEEN CURRENT ROW AND INTERVAL '1' HOUR FOLLOWING ORDER BY periodstarttime)
FROM ...
WHERE EXTRACT(MINUTE FROM periodstarttime) IN (0,15,30,45)
How can I add months to a timestamp value in Oracle? In my query, it's getting converted to date value instead:
SELECT add_months(current_timestamp,2)
FROM dual;
The actual output is:
ADD_MONTH
11-MAR-13
The expected output is:
2013-01-01 00:00:00.000000000+00:00
This will give you the date and the time as a TIMESTAMP data type:
select TO_TIMESTAMP(TO_CHAR(ADD_MONTHS(SYSDATE, 2), 'YYYYMMDD HH24:MI'),
'YYYYMMDD HH24:MI') from dual;
If you need more or less precision (E.G. rounding) than what is above, adjust the date formats (both need to be the same format). For example, this will return 2 months down to the seconds level of precision:
select TO_TIMESTAMP(TO_CHAR(ADD_MONTHS(SYSTIMESTAMP, 2),
'YYYYMMDD HH24:MI:SS'), 'YYYYMMDD HH24:MI:SS') from dual;
This is the closest I can get (as a character) to the format you need:
select TO_CHAR(
TO_TIMESTAMP(TO_CHAR(ADD_MONTHS(SYSTIMESTAMP, 2),
'YYYYMMDD HH24:MI:SS'), 'YYYY-MM-DD HH24:MI:SS'),
'YYYY-MM-DD HH24:MI:SS.FF TZR') from dual;
I think this will about give you what you're looking for:
SELECT TO_CHAR(TO_TIMESTAMP(ADD_MONTHS(CURRENT_TIMESTAMP,2))
+ (CURRENT_TIMESTAMP - TRUNC(CURRENT_TIMESTAMP)),
'YYYY-MM-DD HH:MI:SSxFFTZR') FROM DUAL;
The problem with using the interval methods is that you can get an unexpected error depending on the date you run the query. E.g.
SELECT TO_TIMESTAMP('31-JAN-2012') + NUMTOYMINTERVAL(1,'MONTH') FROM DUAL;
That query returns:
ORA-01839: date not valid for month specified
This is because it attempts to return February 31, which is not a valid date.
ADD_MONTHS is a "safer" way to date math, in that where the interval query would throw an error, ADD_MONTHS will return the last date of the month (Feb 28 or 29 depending on the year) in the above example.
For Oracle:
SELECT
TIMESTAMP'2014-01-30 08:16:32', -- TS we want to increase by 1 month
--TIMESTAMP'2014-01-30 08:16:32' + NUMTOYMINTERVAL(1, 'MONTH'), -- raises ORA-01839: date not valid for month specified
--TIMESTAMP'2014-01-30 08:16:32' + INTERVAL '1' MONTH, -- raises ORA-01839: date not valid for month specified
ADD_MONTHS(TIMESTAMP'2014-01-30 08:16:32', 1), -- works but is a date :(
CAST(ADD_MONTHS(TIMESTAMP'2014-01-30 08:16:32', 1) AS TIMESTAMP) -- works
FROM DUAL
SELECT current_timestamp + INTERVAL '2' MONTH from dual;
To display this in your desired format, use TO_CHAR:
SELECT TO_CHAR(current_timestamp + INTERVAL '2' MONTH,
'YYYY-MM-DD HH24:MI:SS.FF9TZH:TZM') from dual;
2013-03-11 23:58:14.789501000+01:00
For Oracle:
select TO_TIMESTAMP(Sysdate,'DD-Mon-YYYY HH24-MI-SS') + 60
from dual;
select sysdate + interval '2' month from dual;
select TO_TIMESTAMP (Sysdate + interval '2' month, 'DD-Mon-YYYY HH24-MI-SS')
from dual
;
Result1:
| TO_TIMESTAMP(SYSDATE,'DD-MON-YYYYHH24-MI-SS')+60 |
----------------------------------------------------
| March, 12 0013 00:00:00+0000 |
Result2:
| SYSDATE+INTERVAL'2'MONTH |
--------------------------------
| March, 11 2013 21:41:10+0000 |
Result3:
| TO_TIMESTAMP(SYSDATE+INTERVAL'2'MONTH,'DD-MON-YYYYHH24-MI-SS') |
------------------------------------------------------------------
| March, 11 0013 00:00:00+0000 |
sqlffidle demo.