【问题标题】:Oracle How to list last days of months between 2 datesOracle如何列出两个日期之间月份的最​​后几天
【发布时间】:2021-02-15 14:01:52
【问题描述】:

我设法得到两个日期之间的所有天数。 但我想获得两个日期之间的所有最后一天(使用一个请求)。

两个日期之间的所有天数:

 select to_date('01/01/2000','dd/mm/yyyy') + (LEVEL-1) as jour
 from dual
 connect by level <= to_date('31/12/2050','dd/mm/yyyy')-to_date('01/01/2000','dd/mm/yyyy')

当月最后一天:

 select  LAST_DAY(sysdate)  FROM dual 

我不知道如何将两者混合并得到预期的结果:

20000131
20000228
20000331
etc...

【问题讨论】:

  • 您是否要查找两个 GIVEN 日期之间的所有月末,例如从 2000 年 1 月 1 日到 2050 年 12 月 31 日?或者您是否需要为用户在运行查询时选择的两个“输入”日期执行此操作?

标签: sql oracle datetime recursive-query date-arithmetic


【解决方案1】:

我猜应该是DISTINCT + LAST_DAY

设置日期格式(使其与您的匹配;或者,使用适当的格式掩码将TO_CHAR 应用于jour 值):

SQL> alter session set nls_Date_format = 'yyyymmdd';

Session altered.

缩短时间跨度为 2 年(以节省空间:))。

SQL> select distinct last_day(to_date('01/01/2000','dd/mm/yyyy') + (LEVEL-1)) as jour
  2  from dual
  3  connect by level <= to_date('31/12/2002','dd/mm/yyyy')-to_date('01/01/2000','dd/mm/yyyy')
  4  order by 1;

JOUR
--------
20000131
20000229
20000331
20000430
20000531
20000630
20000731
20000831
<snip>
20020630
20020731
20020831
20020930
20021031
20021130
20021231

36 rows selected.

SQL>

【讨论】:

  • 简单的解决方案,完美!
  • 这似乎非常低效 - 如果我们只需要对一个范围执行此操作,这可能不是问题,但如果 OP 有一个包含 start_date 和 end_date 列以及数十万行的表,这肯定很重要。
【解决方案2】:

我喜欢使用标准递归查询,而不是 Oracle 特定的 CONNECT BY 语法。在这里,您可以枚举月份的开始,然后偏移到月份的结束:

with cte (dt) as (
    select date '2020-01-01' dt from dual
    union all
    select dt + interval '1' month from cte where dt + interval '1' month < date '2051-01-01'
)
select last_day(dt) dt from cte order by dt

请注意,这使用标准日期文字 (date 'YYYY-MM-DD') 而不是 to_date() - 这使得查询更短,而且更标准。

Demo on DB Fiddle

| DT | | :-------- | | 20 年 1 月 31 日 | | 20 年 2 月 29 日 | | 20 年 3 月 31 日 | | 20 年 4 月 30 日 | | 20 年 5 月 31 日 | ... | 50 年 10 月 31 日 | | 50 年 11 月 30 日 | | 50 年 12 月 31 日 |

【讨论】:

  • 这似乎并不能解决 OP 提出的问题。问题是找到两个日期之间的所有月末,例如 2020-01-15 和 2020-08-11。在这样的问题中,第一个日期不能被硬编码。而且,如果日期是 2020 年 1 月 15 日到 2020 年 1 月 30 日,则输出应该没有行。
  • @mathguy:这是对问题的解释。原来的帖子本身并没有给出关于要求的详细程度......我走了一条较短的路线(实际上与 Littlefoot 的非常相似)。
  • 很公平 - 我在他的回答下方的评论中要求 OP 进行澄清。让我们看看他说了什么(如果有的话)。
  • 当输入日期为2020-01-31(或任何不包含该月的较晚月份的日期)时,此操作将失败。 db<>fiddle
【解决方案3】:

您可以使用 CONNECT BY 查询来做到这一点。 (您也可以使用递归查询,就像 GMB 提出的那样,但它必须适应解决您提出的问题 - 它应该允许输入开始和结束日期,如果没有,它应该返回零行两个日期之间的月末。)

在下面的查询中,我使用 WITH 子句给出开始和结束日期。更有可能的是,在您的问题中,它们是绑定变量。 (或者它们是从表格中读取的?)

注意 START WITH 子句。 CONNECT BY 条件仅适用于级别 2 及以上;如果在给定日期之间没有月末(例如,在同一年的 1 月 10 日和 1 月 23 日之间),则需要 START WITH 条件来表示 level=1。

with
  input_dates(start_dt, end_dt) as (
    select date '2020-01-22', date '2020-04-03' from dual
  )
select  add_months(last_day(start_dt), level - 1) as eom
from    input_dates
start   with last_day(start_dt) <= end_dt
connect by add_months(last_day(start_dt), level - 1) <= end_dt
;

EOM       
----------
2020-01-31
2020-02-29
2020-03-31

【讨论】:

    【解决方案4】:

    您的初始查询只需要一些调整。而不是仅仅 1 级(最终每天执行)将其转换为每月增量“1 级)* 间隔 '1' 月。然后通过获取所需日期之间的月份进行连接。注意:我已转换为 ISO日期的标准格式,而不是 to_date 函数。使查询更短,更易于阅读。

     select last_day(date '2000-01-01' + (level-1)*interval '1' month) as jour
       from dual
      connect by level <= 1+months_between(date '2050-12-31',date '2000-01-01');
    

    【讨论】:

    • 当输入日期为2020-01-31(或任何存在不包含该月该天的较晚月份的任何日期)时,此操作将失败。 db<>fiddle
    • 有趣!在这里工作得很好db<>fiddle。那么有什么不同呢?
    • 2 月没有 31 天,如果输入日期是 2020-01-31 并且您添加了 INTERVAL '1' MONTH,那么 Oracle 将尝试(但失败)为下一次迭代创建日期 2020-02-31分层查询。切勿使用间隔来添加月份。
    • 你是对的。猜猜我只是没有遇到它,感谢您指出。顺便说一句,这似乎是 Oracle 间隔处理的错误。也与时间戳一起发生。所以为了完整性db<>fiddle。幸运的是,我最近退休了,我实际上不必再担心了。祝你好运。
    【解决方案5】:

    您可以使用递归子查询。

    这适用于:

    • 多个输入范围;
    • 开始日期为月底时输入;
    • 当范围不包含任何月末时。
    WITH input_ranges ( start_date, end_date ) AS (
      -- Should return a single row.
      SELECT DATE '2020-01-31', DATE '2020-02-01' FROM DUAL UNION ALL
      -- Should return multiple rows.
      SELECT DATE '2021-02-01', DATE '2021-06-01' FROM DUAL UNION ALL
      -- Should not return any rows as there is no end of the month in the range.
      SELECT DATE '2021-10-06', DATE '2021-10-20' FROM DUAL UNION ALL
      -- Should work even though February does not have 30 days.  
      SELECT DATE '2022-01-30', DATE '2022-03-02' FROM DUAL
    ),
    month_ends ( month_end, end_date ) AS (
      SELECT LAST_DAY( start_date ),
             end_date
      FROM   input_ranges
      WHERE  LAST_DAY( start_date ) <= end_date
    UNION ALL
      SELECT ADD_MONTHS( month_end, 1 ),
             end_date
      FROM   month_ends
      WHERE  ADD_MONTHS( month_end, 1 ) <= end_date
    )
    SELECT month_end
    FROM   month_ends
    ORDER BY month_end;
    

    哪些输出:

    | MONTH_END | | :----------------- | | 2020-01-31 00:00:00 | | 2021-02-28 00:00:00 | | 2021-03-31 00:00:00 | | 2021-04-30 00:00:00 | | 2021-05-31 00:00:00 | | 2022-01-31 00:00:00 | | 2022-02-28 00:00:00 |

    db小提琴here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-20
      • 2018-07-01
      • 2019-03-28
      相关资源
      最近更新 更多