純 SQL 生成月曆矩陣是報表開發經典需求。本文從基礎日期序列生成、週數計算、行列轉換到最終統計匯總,逐步建構完整月曆查詢。
CONNECT BY LEVEL 生成日期序列(遞迴查詢)LAST_DAY 取得月份最後一天TO_CHAR(date, 'fmt') 日期格式化抽取年/月/日/星期/週數DECODE/CASE 行列轉換(樞紐)GROUP BY 週維度匯總-- 起始日
SELECT TO_DATE('20061101','YYYYMMDD') AS start_day FROM dual;
-- 結束日
SELECT LAST_DAY(TO_DATE('20061101','YYYYMMDD')) AS end_day FROM dual;
-- 天數
SELECT LAST_DAY(TO_DATE('20061101','YYYYMMDD')) - TO_DATE('20061101','YYYYMMDD') + 1 AS day_cnt FROM dual;
SELECT every_day,
TO_CHAR(every_day,'YYYY') AS year,
TO_CHAR(every_day,'MM') AS month,
TO_CHAR(every_day,'DD') AS day,
TO_CHAR(every_day,'DY') AS weekday,
LPAD(TO_CHAR(every_day,'W'),6) AS week_of_month,
LPAD(TO_CHAR(every_day,'WW'),6) AS week_of_year
FROM (SELECT TO_DATE('20061101','YYYYMMDD') + LEVEL - 1 AS every_day
FROM dual
CONNECT BY LEVEL <= LAST_DAY(TO_DATE('20061101','YYYYMMDD')) - TO_DATE('20061101','YYYYMMDD') + 1);
SELECT every_day,
TO_CHAR(every_day,'YYYY') AS year,
TO_CHAR(every_day,'MM') AS month,
TO_CHAR(every_day,'DD') AS day,
TO_CHAR(every_day,'DY') AS weekday,
LPAD(TO_CHAR(every_day,'W'),6) AS week_of_month,
LPAD(TO_CHAR(every_day,'WW'),6) AS week_of_year,
LPAD(DECODE(TO_CHAR(every_day,'DY'),'星期日',TO_CHAR(every_day,'DD')),3) AS sun,
LPAD(DECODE(TO_CHAR(every_day,'DY'),'星期一',TO_CHAR(every_day,'DD')),3) AS mon,
LPAD(DECODE(TO_CHAR(every_day,'DY'),'星期二',TO_CHAR(every_day,'DD')),3) AS tue,
LPAD(DECODE(TO_CHAR(every_day,'DY'),'星期三',TO_CHAR(every_day,'DD')),3) AS wed,
LPAD(DECODE(TO_CHAR(every_day,'DY'),'星期四',TO_CHAR(every_day,'DD')),3) AS thu,
LPAD(DECODE(TO_CHAR(every_day,'DY'),'星期五',TO_CHAR(every_day,'DD')),3) AS fri,
LPAD(DECODE(TO_CHAR(every_day,'DY'),'星期六',TO_CHAR(every_day,'DD')),3) AS sat
FROM (SELECT TO_DATE('20061101','YYYYMMDD') + LEVEL - 1 AS every_day
FROM dual
CONNECT BY LEVEL <= LAST_DAY(TO_DATE('20061101','YYYYMMDD')) - TO_DATE('20061101','YYYYMMDD') + 1);
SELECT TO_CHAR(every_day,'W') AS week,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期日',TO_CHAR(every_day,'DD'))) AS sun,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期一',TO_CHAR(every_day,'DD'))) AS mon,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期二',TO_CHAR(every_day,'DD'))) AS tue,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期三',TO_CHAR(every_day,'DD'))) AS wed,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期四',TO_CHAR(every_day,'DD'))) AS thu,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期五',TO_CHAR(every_day,'DD'))) AS fri,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期六',TO_CHAR(every_day,'DD'))) AS sat
FROM (SELECT TO_DATE('20061101','YYYYMMDD') + LEVEL - 1 AS every_day
FROM dual
CONNECT BY LEVEL <= LAST_DAY(TO_DATE('20061101','YYYYMMDD')) - TO_DATE('20061101','YYYYMMDD') + 1)
GROUP BY TO_CHAR(every_day,'W')
ORDER BY TO_CHAR(every_day,'W');
Oracle 提供三種週數格式,語意不同:
| 格式 | 含義 | 適用場景 |
|---|---|---|
W | 月內週數(1 日至 7 日為第 1 週) | 月曆顯示 |
WW | 年內週數(1 月 1 日所在週為第 1 週) | 年度報表 |
IW | ISO 週數(ISO 8601 標準,週一為週首) | 國際標準報表 |
SELECT CEIL((TO_CHAR(every_day,'DD') + (TO_CHAR(TO_DATE('20061101','YYYYMMDD'),'D') - 1)) / 7) AS week,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期日',TO_CHAR(every_day,'DD'))) AS sun,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期一',TO_CHAR(every_day,'DD'))) AS mon,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期二',TO_CHAR(every_day,'DD'))) AS tue,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期三',TO_CHAR(every_day,'DD'))) AS wed,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期四',TO_CHAR(every_day,'DD'))) AS thu,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期五',TO_CHAR(every_day,'DD'))) AS fri,
SUM(DECODE(TO_CHAR(every_day,'DY'),'星期六',TO_CHAR(every_day,'DD'))) AS sat
FROM (SELECT TO_DATE('20061101','YYYYMMDD') + LEVEL - 1 AS every_day
FROM dual
CONNECT BY LEVEL <= LAST_DAY(TO_DATE('20061101','YYYYMMDD')) - TO_DATE('20061101','YYYYMMDD') + 1)
GROUP BY CEIL((TO_CHAR(every_day,'DD') + (TO_CHAR(TO_DATE('20061101','YYYYMMDD'),'D') - 1)) / 7)
ORDER BY week;
PARTITION BY month 進行分組匯總:p_year, :p_month)DY 顯示語言,建議在 Session 設定 NLS_DATE_LANGUAGELATERAL / CROSS APPLY 簡化結構