SQL 在 Oracle 工作一周

SQL working week in Oracle

我需要 Oracle SQL returns 'working' 年中的周数:

所以结果应该是:

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
...

W - week number in a month

WW - week number in a year, week 1 starts at 1st of Jan

IW - week number in a year, according to ISO standard

根据您的要求,您需要使用 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 没有为测试用例提供创建和插入语句。实际上,您需要使用 table 和列名。

一种方法可以计算一年中的天数并除以 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用于避免进入上一年。

我自己找到了答案,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;
/