Generate range of dates using CTE Oracle - oracle

I want to generate a range of days between two different dates using recursive WITH clause in Oracle.
WITH CTE_Dates (cte_date) AS
( SELECT CAST(TO_DATE('10-02-2017', 'DD-MM-YYYY') AS DATE) cte_date FROM dual
UNION ALL
SELECT CAST( (cte_date + 1) AS DATE) cte_date
FROM CTE_Dates
WHERE TRUNC(cte_date) + 1 <= TO_DATE('20-02-2017', 'DD-MM-YYYY')
)
SELECT * FROM CTE_Dates
The returned results are completely other than expected:
10-02-2017
09-02-2017
08-02-2017
07-02-2017
06-02-2017
... (unlimited)
The expected results:
10-02-2017
11-02-2017
...
19-02-2017
20-02-2017
Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production.
Edit:
As I understood, this is a known Bug in Oracle, the bug exists through Oracle 11.2.0.2 and it was fixed in 11.2.0.3.
Altarnative solution:
SELECT TRUNC (TO_DATE('10-02-2017', 'DD-MM-YYYY') + ROWNUM -1) dt
FROM DUAL
CONNECT BY ROWNUM <= (TO_DATE('20-02-2017', 'DD-MM-YYYY') - (TO_DATE('10-02-2017', 'DD-MM-YYYY')))

This was a known bug in recursive CTE's in Oracle 11 (specifically with regard to date arithmetic). Fixed in Oracle 12. Exactly that behavior: whether you add or subtract in your code, the engine always subtracts, it never adds.
EDIT: Actually, as Alex Poole pointed out in a Comment to the original post, the bug exists through Oracle 11.2.0.2 and it was fixed in 11.2.0.3. End edit
Alas I am not a paying customer, so I can't quote chapter and verse, but with a little bit of Googling you will find links to this (including on OTN where I was involved in a few threads discussing this and other bugs in recursive CTEs - some were fixed, some are still bugs in Oracle 12.1).
Added - here is one of those discussions: https://community.oracle.com/thread/3974408

Unless you really need to use the WITH clause there is another solution to get the expected result by using the CONNECT BY clause.
SELECT TO_DATE('10-02-2017', 'DD-MM-YYYY') AS date_range
FROM dual
UNION ALL
SELECT TO_DATE('10-02-2017', 'DD-MM-YYYY') + LEVEL
FROM dual
CONNECT BY LEVEL <= (TO_DATE('20-02-2017', 'DD-MM-YYYY') - TO_DATE('10-02-2017', 'DD-MM-YYYY'));

Maybe reverse order of the recursion solves the problem:
with cte_dates (cte_date) as (
select cast(to_date('20-02-2017', 'DD-MM-YYYY') as date) cte_date from dual
union all
select cast((cte_date - 1) as date) cte_date
from cte_dates
where cast(cte_date as date) > to_date('10-02-2017', 'DD-MM-YYYY')
)
select * from cte_dates
order by cte_date
;
2017-02-10
2017-02-11
2017-02-12
...
2017-02-18
2017-02-19
2017-02-20
Note: cast date as date needed due to yet another bug by RCTE in 11gR2
Old discussion here

You don't need a recursive WITH clause, you just need a table where number of rows > number of dates you want to generate :
WITH
dates
AS
(SELECT
TO_DATE('10-02-2017', 'DD-MM-YYYY') + (rownum - 1)
FROM
all_tables
WHERE 1=1
AND rownum < (TO_DATE('20-02-2017', 'DD-MM-YYYY') - TO_DATE('10-02-2017', 'DD-MM-YYYY')) + 2
)
SELECT
*
FROM
dates

Related

How to use defined variable in Where clause

Defining and selecting variable works just fine in Oracle SQL Developer.
ALTER SESSION SET NLS_LANGUAGE=english; -- First day of week
--DEFINE SUMMER_START_DT = TO_CHAR(TO_DATE('03-24-2022', 'MM-DD-YYYY'),'yyyymmdd')
DEFINE SUMMER_START_DT = TO_CHAR(NEXT_DAY(LAST_DAY(TO_DATE(TO_CHAR('01/03/' || (EXTRACT(YEAR FROM SYSDATE)-1 + level) || '02:00:00'),'DD/MM/YYYY HH24:MI:SS')) - INTERVAL '7' DAY, 'SUNDAY'),'yyyymmdd') FROM DUAL CONNECT BY level <=1
SELECT &SUMMER_START_DT;
But I get an error when trying to use the variable in Select statement using it as filter in the Where clause.
SELECT a.* FROM TRADE a WHERE TO_CHAR(a.TRADE_DATE_TIME,'yyyymmdd') = &SUMMER_START_DT;
I get the error "SQL command not properly ended"
Hope someone can help me. Thanks
Kind regards
Soren Sig Mikkelsen
You substitution variable includes from dual, which is OK when you just prepend select in your first example; but in the second you end up with two from clauses:
SELECT a.*
FROM TRADE a
WHERE TO_CHAR(a.TRADE_DATE_TIME,'yyyymmdd') =
TO_CHAR(NEXT_DAY(LAST_DAY(TO_DATE(TO_CHAR('01/03/' || (EXTRACT(YEAR FROM SYSDATE)-1 + level) || '02:00:00'),'DD/MM/YYYY HH24:MI:SS')) - INTERVAL '7' DAY, 'SUNDAY'),'yyyymmdd')
FROM DUAL CONNECT BY level <=1
(You can see that in the generated column name/alias in the output grid; or set verify on and run as a script.)
If you really wanted to use that as the right-hand side of the filter then you could enclose it in parentheses:
SELECT a.* FROM TRADE a WHERE TO_CHAR(a.TRADE_DATE_TIME,'yyyymmdd') = (SELECT &SUMMER_START_DT);
which would become:
SELECT a.*
FROM TRADE a
WHERE TO_CHAR(a.TRADE_DATE_TIME,'yyyymmdd') =
(
SELECT TO_CHAR(NEXT_DAY(LAST_DAY(TO_DATE(TO_CHAR('01/03/' || (EXTRACT(YEAR FROM SYSDATE)-1 + level) || '02:00:00'),'DD/MM/YYYY HH24:MI:SS')) - INTERVAL '7' DAY, 'SUNDAY'),'yyyymmdd')
FROM DUAL CONNECT BY level <=1
)
But the connect by isn't doing anything here, so you can remove that; and if you remove from dual as well then you can run your first statement as:
SELECT &SUMMER_START_DT FROM DUAL;
and the second as it is.
You could simplify the calculation though. For a start you aren't using the time element, so you don't need to make it 2am; and you can truncate to the start of the year and add two months to get March 1st; as a string if that's really what you want:
to_char(next_day(last_day(add_months(trunc(sysdate, 'YYYY'), 2)) - 7, 'SUNDAY'), 'YYYYMMDD')
db<>fiddle
But you can keep it as a date; if you:
DEFINE SUMMER_START_DT = next_day(last_day(add_months(trunc(sysdate, 'YYYY'), 2)) - 7, 'SUNDAY')
then again you can do:
SELECT &SUMMER_START_DT FROM DUAL;
and your second query can be:
SELECT a.*
FROM TRADE a
WHERE a.TRADE_DATE_TIME >= &SUMMER_START_DT
AND a.TRADE_DATE_TIME < &SUMMER_START_DT + 1
which avoids converting every TRADE_DATE_TIME date value to a string to compare it, and allows an index on that date column to be used.

Get date of the previous day in Oracle

I need to bring the day immediately preceding date in Oracle using a truncate but not how. He was using the following line but bring me some records for the current day of execution and should not be. Neceisto only the previous day; investigation found the truncate with dates in Oracle but not how to use it.
and fnxs.FECHA_INGRESO BETWEEN (TO_CHAR (SYSDATE-1, 'DD-MON-YY')) AND (TO_CHAR (SYSDATE, 'DD-MON-YY'));
I appreciate your help
Using BETWEEN with dates in Oracle is generally a bad idea. I see it all the time, and most of the time people get it wrong (like in the accepted answer above). Even when they fully understand that the two dates are included, they still make logical errors because they forget about timestamps.
The OP is asking for yesterday dates. The following sql shows that today falls within "BETWEEN TRUNC( SYSDATE ) - 1 AND TRUNC( SYSDATE )"
with adate as (
select trunc(sysdate) today from dual
) select today from adate where today between trunc(sysdate) -1
and trunc(sysdate);
16-Apr-15 00:00:00
[returns the record for today]
I find it easier to be correct with dates when you're more explicit about the end points:
SELECT * from your_table
WHERE fnxs.FECHA_INGRESO >= TRUMC(SYSDATE) - 1
AND fnxs.FECHA_INGRESO < TRUNC(SYSDATE);
Upon looking closer, the OP's date-like column might be a VARCHAR2 (could still be a date that was implicitly cast in the comparison he gave). If it is a VARCHAR, then it needs to be converted first (using an appropriate format string):
SELECT * FROM your_table
WHERE TO_DATE(fnxs.FECHA_INGRESO, 'DD-MON-YY') >= TRUMC(SYSDATE) - 1
AND TO_DATE(fnxs.FECHA_INGRESO, 'DD-MON-YY') < TRUNC(SYSDATE);
Assuming your column is of type DATE
SELECT *
FROM TABLE_NAME
WHERE FECHA_INGRESO BETWEEN TRUNC( SYSDATE ) - 1
AND TRUNC( SYSDATE );
If it is a character string then:
SELECT *
FROM TABLE_NAME
WHERE TO_DATE( FECHA_INGRESO, 'DD-MON-YY' )
BETWEEN TRUNC( SYSDATE ) - 1
AND TRUNC( SYSDATE );

Oracle is not working with Japanese OS issue

I have a query which returns second and fourth saturdays of given year,
WITH ALL_SATURDAYS AS
(SELECT TO_CHAR(TO_DATE('01012014','DDMMYYYY'),'WW') * (level) AS WEEK_NO,
NEXT_DAY(TO_DATE('01012014','DDMMYYYY') + (TO_CHAR(TO_DATE('01012014','DDMMYYYY'),'WW' ) * (level-1) * 7),'土') AS SATURDAY_DATE,
row_number() OVER (PARTITION BY TO_CHAR(NEXT_DAY(TO_DATE('01012014','DDMMYYYY') + (TO_CHAR(TO_DATE('01012014','DDMMYYYY'),'WW' ) * (level-1) * 7),'土'),'月') ORDER BY level) AS Pos
FROM DUAL
CONNECT BY level<= 52
ORDER BY 1
)
SELECT SATURDAY_DATE,POS FROM ALL_SATURDAYS WHERE POS IN (2,4) ORDER BY 1,2
It's working in other systems, but mine is a japanese os when i execute this query it returns ORA-01821: date format not recognized error.
How can i fix the error?
Replace 月 with MONTH. The Datetime Format Elements are always in English, even though the results may be in another language.
alter session set nls_date_language=japanese;
select to_char(date '2014-01-01', 'MONTH') month from dual;
Month
-----
1月

oracle datetime group by

I want to group data based on date time in oracle
eg:-
03-DEC-13 06:12:03:23,
03-DEC-13 06:12:03:25,
04-DEC-13 08:12:03:23,
04-DEC-13 08:12:03:25
expected result :- 03-DEC-13 06:12:03, 04-DEC-13 08:12:03
neglect seconds.
SELECT DISTINCT trunc(some_date, 'MI') FROM some_table;
Question is not completely clear, but I think something like:
select to_char(datefield, 'DD-MMM-YY HH24:MI') , count(*) /* or other grouping */
from table
group by to_char(datefield, 'DD-MMM-YY HH24:MI')
If you want to include date/times for which you don't have a value, use something like join with a calendar table or a join with
select to_date('20000101', 'YYYYMMDD') + level / 24 day_and_hours
from dual
connect by level <= 100
Check your performance when doing so.

Oracle Analytics Date Range

While researching Oracle Analytics, I came across this query:
select an_id,
a_date,
min(a_date) over (
partition by an_id, trunc(a_date)
order by a_date
range between (2.5/24) preceding and (2.5/24) following
) mn,
max(a_date) over (
partition by an_id, trunc(a_date)
order by a_date
range between (2.5/24) preceding and (2.5/24) following
) mx
from a_table
I believe this finds the min and max dates for a given an_id and a_date within a 2.5 hour period.
My question is why does this comparison between a_date (a date) and 2.5/24 (a number) work and how can it be modified for ranges of days, months, or years?
The date type allows arithmetic where a unit of 1 is a day, so SYSDATE + 1 is tomorrow.
For example, try select to_char(sysdate + 1/24, 'DD-MON-YY HH:MM:SS') from dual -> 1 hour from now.
Here are the docs that talk about that specific analytic construct to give some foundational info and add to what jwilson mentioned.
http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/functions001.htm#i97640
It's probably clearer to use intervals to specify window ranges:
http://download.oracle.com/docs/cd/B19306%5F01/server.102/b14200/functions104.htm#i89943
SELECT last_name,
hire_date,
salary,
SUM(salary) OVER (ORDER BY hire_date
RANGE NUMTOYMINTERVAL(1,'year') PRECEDING) AS t_sal
FROM employees;

Resources