【问题标题】:Generate range of dates using CTE Oracle使用 CTE Oracle 生成日期范围
【发布时间】:2017-02-28 13:03:39
【问题描述】:

我想在 Oracle 中使用递归 WITH 子句生成两个不同日期之间的天数范围。

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

返回的结果与预期完全不同:

10-02-2017
09-02-2017
08-02-2017
07-02-2017
06-02-2017
... (unlimited)

预期结果:

10-02-2017
11-02-2017
...
19-02-2017
20-02-2017

Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64 位生产。

编辑: 据我了解,这是Oracle中的一个已知Bug,该Bug存在于Oracle 11.2.0.2中,并已在11.2.0.3中修复。

替代解决方案:

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')))

【问题讨论】:

  • 当我运行您发布的代码时,它会生成从 2017-02-102017-02-20 的 11 行,这是您的预期结果。请重试。
  • 您使用的是什么版本的数据库?递归 WITH 子句仅在 11gR2 及更高版本上受支持
  • 无关,但是:CAST(... AS DATE) 没用。一个简单的TO_DATE('10-02-2017', 'DD-MM-YYYY') 正在做同样的事情。
  • 另外:如果要对行进行排序,则需要将order by 应用于整个select 语句
  • @APC 您使用哪个版本?我的数据库版本是:Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production。

标签: oracle common-table-expression recursive-query


【解决方案1】:

这是 Oracle 11 中递归 CTE 中的一个已知错误(特别是关于日期算术)。在 Oracle 12 中已修复。正是这种行为:无论您在代码中添加还是减去,引擎总是减去,它从不添加。

编辑:实际上,正如 Alex Poole 在对原始帖子的评论中指出的那样,该错误存在于 Oracle 11.2.0.2 中,并已在 11.2.0.3 中修复。 结束编辑

唉,我不是付费客户,所以我不能引用章节和诗句,但是通过一些谷歌搜索你会找到指向这个的链接(包括在 OTN 上,我参与了一些讨论这个和其他的线程递归 CTE 中的错误 - 有些已修复,有些仍然是 Oracle 12.1 中的错误。

已添加 - 这是其中一个讨论:https://community.oracle.com/thread/3974408

【讨论】:

    【解决方案2】:

    除非您确实需要使用 WITH 子句,否则还有另一种解决方案可以通过使用 CONNECT BY 子句来获得预期结果。

    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'));
    

    【讨论】:

    • 为什么要包含一个联合(无论如何应该是联合)?如果您更改第二个分支的选择列表以获取 ... + LEVEL - 1 并将连接限制添加 1,它将包括您当前正在联合的日期。
    • 是的,我可以只使用一个 SELECT 语句,我只是想更接近他最初的解决方案。我的回答只是为了展示 WITH 子句的替代方案。关于 UNION ALL,您说的完全正确,这是我的错误。
    • 我明白了,虽然其中的 union-all 是递归 CTE 结构的一部分。我不确定最初的问题是否暗示他们特别想使用 RCTE 并且不需要替代品,但猜测只有 OP 可以回答 *8-)
    • 我相信 OP 是专门询问观察到的错误(递归 CTE 给出错误的结果),而不是“我怎样才能达到预期的结果”。
    【解决方案3】:

    也许递归的逆序可以解决问题:

    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
    

    注意:由于 RCTE 在 11gR2 中的另一个错误,需要将日期转换为所需日期

    Old discussion here

    【讨论】:

      【解决方案4】:

      您不需要递归 WITH 子句,您只需要一个行数 > 要生成的日期数的表:

      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
      

      【讨论】:

      • 但就其价值而言,您的查询对我来说效果很好
      猜你喜欢
      • 2010-09-29
      • 1970-01-01
      • 1970-01-01
      • 2018-06-17
      • 2011-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-25
      相关资源
      最近更新 更多