SQL working week in Oracle

时光怂恿深爱的人放手 提交于 2019-12-12 19:25:19

问题


I need Oracle SQL that returns the 'working' week number in year:

  • no overflowing weeks from one year to another
  • each week starts from monday
  • first few days in year are week 01

So the result should be:

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

回答1:


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

For your requirement, you need to use combination of IW and WW format. You could combine them using a CASE expression.

If you want to generate the list of dates for entire year, then you could use the row generator method.

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.

NOTE:

The value 15 to generate 15 rows and the dates are hard-coded above just for demonstration using the WITH clause since OP did not provide the test case with create and insert statements. In reality, you need to use your table and column names.




回答2:


An approach could be counting the number of days of the year and divide by 7, with some logic to handle the beginning and the end ot the week and of the year:

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

The LEAST is used to avoid going to the next year, while the GREATEST is useful to avoid going to the previous one.




回答3:


I found the answer myself, TO_CHAR(date,'IW') format is of no use because the very first week in a year according to this standard (ISO) can start after the New Year but also before it (look at TO_CHAR(TO_DATE('2014-12-31','YYYY-MM-DD'),'IW')=01 the first week that belongs to the next year!)

           | 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

The logic is quite simple, let's look at the very first day in year and its offset from monday. If current day is bigger than this first day offset then week number should be incremented by 1.

The number of very first day (offset from monday) is calculated with:

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

So the final SQL statement is

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 

Of course, one can create a function for that purpose like:

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;
/


来源:https://stackoverflow.com/questions/35450998/sql-working-week-in-oracle

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!