问题描述
sql Server 2017
CREATE TABLE [TABLE_1]
(
PLAN_NR decimal(28,6) NULL,START_DATE datetime NULL,);
INSERT INTO TABLE_1 (PLAN_NR,START_DATE)
VALUES (1,'2020-05-01'),(2,'2020-08-01');
CREATE TABLE [TABLE_2]
(
PLAN_NR decimal(28,PERIOD_NR decimal(28,6) NOT NULL
);
INSERT INTO TABLE_2 (PLAN_NR,PERIOD_NR)
VALUES (1,1),(1,2),3),4),5),6),7),8),8);
在 TABLE_1
中有计划编号和计划开始日期。
TABLE_2
包含每个计划编号的期间编号。
我想计算相应的期间开始日期:
每个期间正好是 7 天,除非期间包含月末。那么这个期间应该被划分为月末之前到月底最后一天的区间和月末之后的区间。
选择:
SELECT
t1.PLAN_NR,t2.PERIOD_NR,FORMAT(DATEADD (d,((t2.PERIOD_NR-1)*7),t1.START_DATE ),'yyyy-MM-dd') START_DATE
FROM
TABLE_1 t1
JOIN
TABLE_2 t2 ON t1.PLAN_NR = t2.PLAN_NR
ORDER BY
t1.PLAN_NR,t2.PERIOD_NR ASC
这将返回开始数据,但没有额外考虑相应的月末:
+---------+-----------+------------+
| PLAN_NR | PERIOD_NR | START_DATE |
+---------+-----------+------------+
| 1 | 1 | 2020-05-01 |
| 1 | 2 | 2020-05-08 |
| 1 | 3 | 2020-05-15 |
| 1 | 4 | 2020-05-22 |
| 1 | 5 | 2020-05-29 |
| 1 | 6 | 2020-06-05 |
| 1 | 7 | 2020-06-12 |
| 1 | 8 | 2020-06-19 |
| 2 | 1 | 2020-08-05 |
| 2 | 2 | 2020-08-12 |
| 2 | 3 | 2020-08-19 |
| 2 | 4 | 2020-08-26 |
| 2 | 5 | 2020-09-01 |
| 2 | 6 | 2020-09-02 |
| 2 | 7 | 2020-09-09 |
| 2 | 8 | 2020-09-16 |
+---------+-----------+------------+
我想要这样的输出:
+---------+-----------+----------------------+
| PLAN_NR | PERIOD_NR | START_DATE |
+---------+-----------+----------------------+
| 1 | 1 | 2020-05-01 |
| 1 | 2 | 2020-05-08 |
| 1 | 3 | 2020-05-15 |
| 1 | 4 | 2020-05-22 |
| 1 | 5 | 2020-05-29 |< --- period part before new month
| 1 | 6 | 2020-06-01 |< --- period part after new month
| 1 | 7 | 2020-06-05 |
| 1 | 8 | 2020-06-12 |
| 2 | 1 | 2020-08-05 |
| 2 | 2 | 2020-08-12 |
| 2 | 3 | 2020-08-19 |
| 2 | 4 | 2020-08-26 |< --- period part before new month
| 2 | 5 | 2020-09-01 |< --- period part after new month
| 2 | 6 | 2020-09-02 |
| 2 | 7 | 2020-09-09 |
| 2 | 8 | 2020-09-16 |
+---------+-----------+----------------------+
解决方法
使用窗口函数(LEAD / LAG)获取周期的开始和结束...
SELECT t1.PLAN_NR,t2.PERIOD_NR,FORMAT(DATEADD (d,((t2.PERIOD_NR-1)*7),t1.START_DATE ),'yyyy-MM-dd') START_DATE,CASE
WHEN
lead(
FORMAT(DATEADD (d,'yyyy-MM-dd')
) over (partition by
FORMAT(DATEADD (d,'yyyy-MM')
order by t2.period_nr)
IS NULL THEN '< --- period part before new month'
WHEN lag(
FORMAT(DATEADD (d,'yyyy-MM')
order by t2.period_nr)
IS NULL THEN '< --- period part after new month'
END as period_break
from TABLE_1 t1
join TABLE_2 t2
on t1.PLAN_NR = t2.PLAN_NR
order by t1.PLAN_NR,t2.PERIOD_NR asc
PLAN_NR PERIOD_NR START_DATE period_break
1 1 2020-05-01 < --- period part after new month
1 2 2020-05-08 (null)
1 3 2020-05-15 (null)
1 4 2020-05-22 (null)
1 5 2020-05-29 < --- period part before new month
1 6 2020-06-05 < --- period part after new month
1 7 2020-06-12 (null)
1 8 2020-06-19 < --- period part before new month
2 1 2020-08-01 < --- period part after new month
2 2 2020-08-08 (null)
2 3 2020-08-15 (null)
2 4 2020-08-22 (null)
2 5 2020-08-29 < --- period part before new month
2 6 2020-09-05 < --- period part after new month
2 7 2020-09-12 (null)
2 8 2020-09-19 < --- period part before new month
,
SELECT
t1.PLAN_NR,--row_number() over() but what if PERIOD_NR is not consecutive?
t2.PERIOD_NR + SUM(num.n) OVER(PARTITION BY t2.PLAN_NR ORDER BY t2.PERIOD_NR,num.n) AS PERIOD_NR_x,FORMAT(CASE WHEN num.n = 1 THEN DATEADD(day,1,EOMONTH(DATEADD (d,t1.START_DATE ))) ELSE DATEADD(d,t1.START_DATE ) END,'yyyy-MM-dd') START_DATE
FROM
TABLE_1 t1
JOIN
TABLE_2 t2 ON t1.PLAN_NR = t2.PLAN_NR
CROSS APPLY
(
SELECT 0 AS n
UNION ALL
--new row for month change
SELECT 1 AS n
WHERE DATEDIFF(month,DATEADD(d,(t2.PERIOD_NR-1)*7,t1.START_DATE),t2.PERIOD_NR*7,t1.START_DATE)) = 1
) as num
ORDER BY
t1.PLAN_NR,t2.PERIOD_NR ASC