问题
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