【问题标题】:SQL working week in OracleOracle 中的 SQL 工作周
【发布时间】:2016-02-17 08:05:36
【问题描述】:

我需要返回一年中“工作”周数的 Oracle SQL:

  • 从一年到另一年没有溢出的周数
  • 每周从星期一开始
  • 一年中的前几天是第 1 周

所以结果应该是:

2015-12-28 - MON - week 53
2015-12-29 - TUE - week 53
2015-12-30 - WED - week 53
2015-12-31 - THU - week 53
===
2016-01-01 - FRI - week 01 - reseting yearly week counter
2016-01-02 - SAT - week 01
2016-01-03 - SUN - week 01
---
2016-01-04 - MON - week 02 - monday start of new week
2016-01-05 - TUE - week 02
...
2016-12-31 - SAT - week 53
===
2017-01-01 - SUN - week 01 - reseting yearly week counter
2017-01-02 - MON - week 02 - monday start of new week
...

【问题讨论】:

  • 您的原始表/数据在哪里?
  • @Jan 不,它根本不是重复的。这个话题比这要复杂得多。请参阅下面的答案。

标签: sql oracle week-number


【解决方案1】:

W - 一个月的周数

WW - 一年中的周数,第 1 周从 1 月 1 日开始

IW - 一年中的周数,根据 ISO 标准

根据您的要求,您需要使用IWWW 格式的组合。您可以使用 CASE 表达式将它们组合起来。

如果要生成全年的日期列表,则可以使用 row generator 方法。

SQL> WITH sample_data AS(
  2  SELECT DATE '2015-12-28'    + LEVEL -1 dt FROM dual
  3  CONNECT BY LEVEL <= 15
  4  )
  5  -- end of sample_data mimicking real table
  6  SELECT dt,
  7    TO_CHAR(dt, 'DY') DAY,
  8    NVL(
  9    CASE
 10      WHEN dt < DATE '2016-01-01'
 11      THEN TO_CHAR(dt, 'IW')
 12      WHEN dt >= next_day(TRUNC(DATE '2016-01-01', 'YYYY') - 1, 'Monday')
 13      THEN TO_CHAR(dt                                      +7, 'IW')
 14    END, '01') week_number
 15  FROM sample_data;

DT         DAY WEEK_NUMBER
---------- --- -----------
2015-12-28 MON 53
2015-12-29 TUE 53
2015-12-30 WED 53
2015-12-31 THU 53
2016-01-01 FRI 01
2016-01-02 SAT 01
2016-01-03 SUN 01
2016-01-04 MON 02
2016-01-05 TUE 02
2016-01-06 WED 02
2016-01-07 THU 02
2016-01-08 FRI 02
2016-01-09 SAT 02
2016-01-10 SUN 02
2016-01-11 MON 03

15 rows selected.

注意:

生成 15 行的值 15 和上面的日期是硬编码的,只是为了使用 WITH 子句进行演示,因为 OP 没有为测试用例提供创建和插入语句。实际上,您需要使用表名和列名。

【讨论】:

  • 不,WW 不会按要求从星期一开始一周(星期一重置)! 'WW' 从一年中的第一天开始,无论是星期一还是一周中的任何其他日子。
  • @sbrbot 这将是一个复杂的逻辑,请等待几分钟。将更新我的答案。
  • 好的,这将适用于 2016 年,同时它被硬编码到 SQL 中(在 CASE 语句中)。我可以使用 TO_CHAR(date,'IW')+1 轻松实现这一目标,但这不是重点。我想要一个通用的代码,可以在往年和未来几年使用。
  • @sbrbot 查看更新后的答案。生成 15 行的值 15 和日期现在是硬编码的,只是为了演示,因为您没有提供创建和插入语句。在您的情况下,您只需要使用表名和列名。
  • 感谢您为回答我的问题所做的努力,但很抱歉您的回答不正确。它仅适用于 2016 年。如前所述,您仍然在代码中硬编码了 2016 年,这就是为什么此代码在其他年份无法正常工作的原因,因为该年份的第一个星期一有各种偏移量(尝试 2014 年),甚至更多最后一个代码你添加了一个硬编码的东西——“星期一”(这个代码在我的 Oracle 中不起作用,而这取决于 NLS——在我的例子中,它适用于我的语言中的“星期一”)。我一直在寻找适用于所有年份的通用解决方案。
【解决方案2】:

一种方法可以是计算一年中的天数并除以 7,并使用一些逻辑来处理一周和一年的开始和结束:

with test(date_) as 
(
select to_date('23122016', 'ddmmyyyy') + level -1 from dual connect by level < 30
)
SELECT date_,
       floor( to_number( to_char( 
                                    greatest( least(
                                                    trunc(date_, 'iw')+6 ,
                                                    add_months( trunc(date_, 'YEAR'),12) -1
                                                   ),
                                              trunc(date_, 'yyyy')),
                                    'ddd'
                                 )
                        ) /7 +1
             ) week
FROM test

LEAST 用于避免进入下一年,而GREATEST 用于避免进入上一年。

【讨论】:

    【解决方案3】:

    我自己找到了答案,TO_CHAR(date,'IW') 格式没有用,因为根据这个标准 (ISO) 一年中的第一周可以在新年之后开始,也可以在新年之前开始(看看TO_CHAR(TO_DATE('2014-12-31','YYYY-MM-DD'),'IW')=01 第一个属于下一年的那一周!)

               | DAY | WW | IW | MY
    ===========+=====+====+====+====
    2014-12-28 | SUN | 52 | 52 | 52
    2014-12-29 | MON | 52 | 01 | 53
    2014-12-30 | TUE | 52 | 01 | 53
    2014-12-31 | WED | 52 | 01 | 53
    2015-01-01 | THU | 53 | 01 | 53
    ...        | ... | .. | .. | ..
    2016-12-31 | THU | 53 | 53 | 01
    2016-01-01 | FRI | 01 | 53 | 01
    2016-01-02 | SAT | 01 | 53 | 01
    2016-01-03 | SUN | 01 | 53 | 01
    2016-01-04 | MON | 01 | 01 | 02
    2016-01-05 | TUE | 01 | 01 | 02
    2016-01-06 | WED | 01 | 01 | 02
    2016-01-07 | THU | 01 | 01 | 02
    2016-01-08 | FRI | 02 | 01 | 02
    

    逻辑很简单,让我们看看一年中的第一天及其与星期一的偏移量。如果当前日期大于第一天偏移量,则周数应增加 1。

    第一天的数量(从星期一偏移)计算如下:

    TO_CHAR(TO_DATE(TO_CHAR(dt,'YYYY')||'0101','YYYYMMDD'),'D'))
    

    所以最终的SQL语句是

    WITH DATES AS
    (
      SELECT DATE '2014-12-25' + LEVEL -1 dt FROM DUAL CONNECT BY LEVEL <= 500
    )
    SELECT dt,TO_CHAR(dt,'DY') DAY,TO_CHAR(dt,'WW') WW,TO_CHAR(dt,'IW') IW,
       CASE WHEN TO_CHAR(dt,'D')<TO_CHAR(TO_DATE(TO_CHAR(dt,'YYYY')||'0101','YYYYMMDD'),'D') THEN 
         LPAD(TO_CHAR(dt,'WW')+1,2,'0')
       ELSE 
         TO_CHAR(dt,'WW')
       END MY
    FROM dates 
    

    当然,可以为此目的创建一个函数,例如:

    CREATE OR REPLACE FUNCTION WorkingWeek(dt IN DATE) RETURN CHAR
    IS
    BEGIN
      IF(TO_CHAR(dt,'D')<TO_CHAR(TO_DATE('0101'||TO_CHAR(dt,'YYYY'),'DDMMYYYY'),'D')) THEN 
       RETURN LPAD(TO_CHAR(dt,'WW')+1,2,'0'); 
     ELSE 
       RETURN TO_CHAR(dt,'WW');
     END IF;
    END WorkingWeek;
    /
    

    【讨论】:

    • TO_CHAR(dt,'WW')+1 您正在强制 Oracle 进行应避免的隐式数据类型转换。
    • 可以使用 TO_CHAR(dt,'WW')+1 代替 TO_CHAR(dt+7,'WW') - 一周漂移。
    • 是的,我就是这么做的。我想我的回答给了你想要的。
    • 我要撤回我的上一条评论,TO_CHAR(dt,'WW')+1 和 TO_CHAR(dt+7,'WW') 在年末的日期不会给出相同的结果!!!
    猜你喜欢
    • 2021-05-09
    • 1970-01-01
    • 2021-10-21
    • 2016-09-06
    • 2018-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-01
    相关资源
    最近更新 更多