【问题标题】:How to convert week number to date range in Oracle?如何在 Oracle 中将周数转换为日期范围?
【发布时间】:2017-09-27 06:52:46
【问题描述】:

在 Oracle 中,我们从以下查询中获得周数:

select to_char(TO_DATE(SYSDATE,'DD-MM-YY'),'IW') from dual

我想获取给定周数的日期范围,
例如,第 1 周的日期范围是 01-01-2017 到 08-01-2017。

有没有办法获取给定周数的日期范围?

【问题讨论】:

  • 也许这个问题可以帮到你:stackoverflow.com/questions/38180947/…
  • 永远不要在已经是 DATE 的内容上使用 to_date。您会导致发生额外的隐式转换,该转换依赖于您的默认 NLS_DATE_FORMAT 参数 - 例如您的查询变为select to_char(to_date(to_char(sysdate, '<your NLS_DATE_FORMAT value>'), 'DD-MM-YY'), 'IW') from dual,如果您的 NLS_DATE_FORMAT 未设置为与您指定的格式相同,您很可能会遇到错误。为什么你仍然使用两位数的年份?年份有 4 位数字!您的查询应该是:select to_char(sysdate, 'iw') from dual
  • 那一年呢?请记住,例如 2017-01-01 是第 2016W52 周。那么您预计第 52 周的哪一天(未指定年份)?甚至您的“当前年份”也可能是 2016-12-26 或 2017-12-25

标签: oracle


【解决方案1】:

"第 1 周日期范围是 01-01-2017 到 08-01-2017"

不,不是。您将 'IW'(运行 MON - SUN)与从一年的第一天运行的 'WW' 混淆了:

SQL> with dts as (
  2       select date '2017-01-01' + (level-1) as dt
  3       from dual
  4       connect by level <= 8
  5  )
  6  select dt
  7         , to_char(dt, 'DY') as dy_dt
  8         , to_char(dt, 'IW') as iw_dt
  9         , to_char(dt, 'WW') as ww_dt
 10  from dts
 11  order by 1;

DT        DY_DT        IW WW
--------- ------------ -- --
01-JAN-17 SUN          52 01
02-JAN-17 MON          01 01
03-JAN-17 TUE          01 01
04-JAN-17 WED          01 01
05-JAN-17 THU          01 01
06-JAN-17 FRI          01 01
07-JAN-17 SAT          01 01
08-JAN-17 SUN          01 02

8 rows selected.

SQL> 

但是,为 IW 周数生成一个范围很容易。您需要将 IW 编号乘以 7,您可以将其转换为带有日期掩码的日期。然后您可以使用next_day() 函数获取相对于该日期的上一个星期一和下一个星期日:

SQL> with tgt as (
  2      select to_date( &iw *7, 'DDD') as dt from dual
  3      )
  4  select next_day(dt-8, 'mon') as start_date
  5         , next_day(dt, 'sun') as end_date
  6* from tgt;
Enter value for iw: 23
old   2:     select to_date( &iw *7, 'DDD') as dt from dual
new   2:     select to_date( 23 *7, 'DDD') as dt from dual

START_DAT END_DATE
--------- ---------
05-JUN-17 11-JUN-17

SQL> 

显然此解决方案使用我的 NLS 设置(英文):如果您使用不同的设置,您可能需要调整解决方案。

【讨论】:

  • 为了避免 nls 依赖,你可以这样做:trunc(to_date(week_num*7, 'ddd'), 'iw') 找到一周的开始,然后添加 6 来找到一周的结束
  • @Boneist - 该公式不正确。不确定它在其他情况下是否正常工作(我怀疑不是,但我可能是错的);但是当week_num 为53 时肯定会失败,因为53 + 7 = 371 并且'ddd' 永远不可能是371;闰周 (num = 53) 确实存在 - 这就是 ISO 周概念的重点。
【解决方案2】:

这类问题用calendar tables很容易解决。

以下查询基于 1 月 4 日是一年中的第一周这一假设 (ISO 8601)。因此,我可以通过构造 1 月 4 日来在任何一年的第一周生成一个有效日期,例如:to_date(year || '-01-04', 'yyyy-mm-dd')。 Oracle 将使用to_char(date, 'D') 告诉我任何日期的星期几(sun=1,sat=7)。 2017 年 1 月 4 日恰好是一个星期三(第 4 天)。减去 3 天将给我一年中第一周的第一天(星期日)。 现在,只需为每周添加 7 天(不包括第一周),即可轻松找到一年中任何一周的开始日期。

with weeks as(
   select 2017 as year, 39 as week from dual union all
   select 2017 as year, 40 as week from dual union all
   select 2018 as year, 35 as week from dual
)
select a.*
      ,to_date(year || '-01-04', 'yyyy-mm-dd')     - to_number(to_char(to_date(year || '-01-04', 'yyyy-mm-dd'), 'D')) + 1 + (7 * (week-1)) as start_day
      ,to_date(year || '-01-04', 'yyyy-mm-dd') + 7 - to_number(to_char(to_date(year || '-01-04', 'yyyy-mm-dd'), 'D'))     + (7 * (week-1)) as end_day
 from weeks a;

编辑:这些是您需要从星期转换为日期范围的“转换”表达式。请注意,2017 和 39 是可变的...

start date = to_date(2017 || '-01-04', 'yyyy-mm-dd')     - to_number(to_char(to_date(2017 || '-01-04', 'yyyy-mm-dd'), 'D')) + 1 + (7 * (39-1))
end date   = to_date(2017 || '-01-04', 'yyyy-mm-dd') + 7 - to_number(to_char(to_date(2017 || '-01-04', 'yyyy-mm-dd'), 'D'))     + (7 * (39-1))

【讨论】:

  • 感谢您的回答,我必须提供全部 52 周才能使此查询正常工作?
  • @imsome1,编辑是否澄清了您的问题?不需要 WITH 构造。我提供它只是为了向您展示示例。我想您的周数将来自某个地方的另一张桌子。
  • 感谢您的解释。这个假设确实有帮助,因为 to_date() 无法处理周数。
【解决方案3】:

这是一个列出从 2001 年到 2099 年所有 ISO 周的查询

SELECT TO_CHAR(TRUNC(dt, 'IW') + 6, 'IYYY-IW') AS week, 
       TRUNC(dt, 'IW') AS start_date, 
       TRUNC(dt, 'IW') + 6 AS end_date
  FROM (SELECT DATE '2001-01-01' + ((LEVEL - 1) * 7) dt
          FROM DUAL
        CONNECT BY LEVEL <= 5165);

【讨论】:

  • 两年前到本周:SELECT TO_CHAR(TRUNC(dt, 'IW') + 6, 'IYYY-IW') AS week, TRUNC(dt, 'IW') AS start_date, TRUNC(dt, 'IW') + 6 AS end_date FROM ( SELECT sysdate-730 + ((LEVEL - 1) * 7) dt FROM DUAL CONNECT BY LEVEL &lt;= 105 ) ORDER BY dt DESC; 谢谢@philippe-malera
【解决方案4】:

对于一年中的第一周和最后一周,此查询需要一些 CASE 逻辑,但对于其他周来说效果很好。此解决方案使用当前的 NLS 设置。

select to_char( start_of_week, 'day dd.mm.yyyy'  ) start_of_week,
       to_char( start_of_week + 6, 'day dd.mm.yyyy'  ) end_of_week
 from  
( 
select trunc( date '2017-01-01' + 38*7 , 'day')   start_of_week        
 from dual   
  )

1) 日期 '2017-01-01' - 我们在哪一年寻找几周 或者它可能是 trunc (sysdate, 'YEAR') 取当年的第一天

2) 日期 '2017-01-01' + 38*7 - 跳转到第 38 周

3) trunc ( ... , 'day' ) - 给出一周中第一天的日期

https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions201.htm https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions230.htm

【讨论】:

    【解决方案5】:

    我使用这个功能:

    FUNCTION ISOWeekDate(WEEK INTEGER, YEAR INTEGER) RETURN DATE DETERMINISTIC IS
        res DATE;
    BEGIN
        IF WEEK > 53 OR WEEK < 1 THEN
            RAISE VALUE_ERROR;      
        END IF;
        res := NEXT_DAY(TO_DATE( YEAR || '0104', 'YYYYMMDD' ) - 7, 'MONDAY') + ( WEEK - 1 ) * 7;
        IF TO_CHAR(res, 'fmIYYY') = YEAR THEN
            RETURN res;
        ELSE
            RAISE VALUE_ERROR;
        END IF;
    END ISOWeekDate;
    

    请注意,根据我的评论,如果您仅提供周数而不提供年份,则将是模棱两可的。该函数返回给定 ISO 周的第一天。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-03
      • 1970-01-01
      • 2011-10-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多