计算几乎相等时期的开始日期
Calculate start date of almost equal periods
SQL 服务器
CREATE TABLE [TABLE_1]
(
PLAN_NR decimal(28,6) NULL,
START_DATE datetime NULL,
MAX_PERIODS decimal(28,6) NULL,
);
INSERT INTO TABLE_1 (PLAN_NR, START_DATE, MAX_PERIODS)
VALUES (1, '2020-05-01', 8),
(2, '2020-08-01', 8);
我有一个包含 PLAN_NR
、START_DATE
和 MAX_PERIODS
列的 table。
每个周期正好是 7 天,除非周期包含月末。那么期间应该分为月末前到月末(包括月末)和月末后的区间。
因此对于 SQL fiddle 示例,首选输出如下所示:
+---------+-----------+----------------------+
| 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-01 |
| 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 |
| 2 | 5 | 2020-09-01 |
| 2 | 6 | 2020-09-02 |
| 2 | 7 | 2020-09-09 |
| 2 | 8 | 2020-09-16 |
+---------+-----------+----------------------+
我问过类似的问题 但对于 Oracle 环境,答案包含一个带有最少语句的递归函数,这在 SQL 服务器中不起作用。
我个人会使用 Tally;对于大型数据集,它们的速度要快得多(特别是如果您还需要大量递归)。如果数字很低,即10 或更低,那么您可以在 FROM
中执行此操作。然后您可以使用 CASE
和 LEAD
检查开始日期和结束日期是否相同,以生成新月份开始的额外行:
WITH Dates AS(
SELECT T1.PLAN_NR,
V.I+1 AS PERIOD_NR,
DATEADD(DAY, 7*V.I, T1.START_DATE) AS START_DATE,
LEAD(DATEADD(DAY, 7*V.I, T1.START_DATE)) OVER (PARTITION BY PLAN_NR ORDER BY V.I) AS END_DATE
FROM dbo.TABLE_1 T1
JOIN (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) V(I) ON MAX_PERIODS >= V.I)
SELECT D.PLAN_NR,
D.PERIOD_NR,
V.START_DATE
FROM Dates D
CROSS APPLY (VALUES(START_DATE),(CASE WHEN MONTH(START_DATE) != MONTH(END_DATE) THEN DATEADD(MONTH,DATEDIFF(MONTH,0,END_DATE),0) END)) V(START_DATE)
WHERE V.START_DATE IS NOT NULL;
如果它大于 10,比如 way 更大,那么您可以使用内联的,使用 CTE(参见 Tally Tables in T-SQL)。
您可以为此尝试递归查询。我会分别生成常规日期(每周)和每个月的第一天,然后 union
它们(请注意,我们将使用 union
而不是 union all
因为它将排除重复)。请参阅以下查询:
with cte as (
select datefromparts(2020, 5, 1) dt
union all
select dateadd(dd, 7, dt) from cte
where dt < datefromparts(2020, 9, 16)
), firstDays as (
select datefromparts(2020, 5, 1) firstDay
union all
select dateadd(m, 1, firstDay) from firstDays
where firstDay < datefromparts(2020, 8, 2)
)
select firstDay from firstDays
union
select dt from cte;
使用递归 CTE
和 ROW_NUMBER()
window 函数:
WITH
rec_cte AS (
SELECT PLAN_NR, START_DATE, MAX_PERIODS,
1 period_nr, DATEADD(day, 7, START_DATE) next_date
FROM TABLE_1
UNION ALL
SELECT PLAN_NR, next_date, MAX_PERIODS,
period_nr + 1, DATEADD(day, 7, next_date)
FROM rec_cte
WHERE period_nr < MAX_PERIODS
),
cte1 AS (
SELECT PLAN_NR, period_nr, START_DATE, MAX_PERIODS
FROM rec_cte
UNION ALL
SELECT PLAN_NR, period_nr, DATEADD(DAY, 1, EOMONTH(next_date, -1)), MAX_PERIODS
FROM rec_cte
WHERE MONTH(START_DATE) <> MONTH(next_date)
),
cte2 AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY PLAN_NR ORDER BY START_DATE) rn
FROM cte1
)
SELECT PLAN_NR, rn PERIOD_NR, START_DATE
FROM cte2
WHERE rn <= MAX_PERIODS
ORDER BY PLAN_NR, START_DATE
参见demo。
结果:
> 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-01
> 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
> 2 | 5 | 2020-09-01
> 2 | 6 | 2020-09-02
> 2 | 7 | 2020-09-09
> 2 | 8 | 2020-09-16
SQL 服务器
CREATE TABLE [TABLE_1]
(
PLAN_NR decimal(28,6) NULL,
START_DATE datetime NULL,
MAX_PERIODS decimal(28,6) NULL,
);
INSERT INTO TABLE_1 (PLAN_NR, START_DATE, MAX_PERIODS)
VALUES (1, '2020-05-01', 8),
(2, '2020-08-01', 8);
我有一个包含 PLAN_NR
、START_DATE
和 MAX_PERIODS
列的 table。
每个周期正好是 7 天,除非周期包含月末。那么期间应该分为月末前到月末(包括月末)和月末后的区间。
因此对于 SQL fiddle 示例,首选输出如下所示:
+---------+-----------+----------------------+
| 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-01 |
| 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 |
| 2 | 5 | 2020-09-01 |
| 2 | 6 | 2020-09-02 |
| 2 | 7 | 2020-09-09 |
| 2 | 8 | 2020-09-16 |
+---------+-----------+----------------------+
我问过类似的问题
我个人会使用 Tally;对于大型数据集,它们的速度要快得多(特别是如果您还需要大量递归)。如果数字很低,即10 或更低,那么您可以在 FROM
中执行此操作。然后您可以使用 CASE
和 LEAD
检查开始日期和结束日期是否相同,以生成新月份开始的额外行:
WITH Dates AS(
SELECT T1.PLAN_NR,
V.I+1 AS PERIOD_NR,
DATEADD(DAY, 7*V.I, T1.START_DATE) AS START_DATE,
LEAD(DATEADD(DAY, 7*V.I, T1.START_DATE)) OVER (PARTITION BY PLAN_NR ORDER BY V.I) AS END_DATE
FROM dbo.TABLE_1 T1
JOIN (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) V(I) ON MAX_PERIODS >= V.I)
SELECT D.PLAN_NR,
D.PERIOD_NR,
V.START_DATE
FROM Dates D
CROSS APPLY (VALUES(START_DATE),(CASE WHEN MONTH(START_DATE) != MONTH(END_DATE) THEN DATEADD(MONTH,DATEDIFF(MONTH,0,END_DATE),0) END)) V(START_DATE)
WHERE V.START_DATE IS NOT NULL;
如果它大于 10,比如 way 更大,那么您可以使用内联的,使用 CTE(参见 Tally Tables in T-SQL)。
您可以为此尝试递归查询。我会分别生成常规日期(每周)和每个月的第一天,然后 union
它们(请注意,我们将使用 union
而不是 union all
因为它将排除重复)。请参阅以下查询:
with cte as (
select datefromparts(2020, 5, 1) dt
union all
select dateadd(dd, 7, dt) from cte
where dt < datefromparts(2020, 9, 16)
), firstDays as (
select datefromparts(2020, 5, 1) firstDay
union all
select dateadd(m, 1, firstDay) from firstDays
where firstDay < datefromparts(2020, 8, 2)
)
select firstDay from firstDays
union
select dt from cte;
使用递归 CTE
和 ROW_NUMBER()
window 函数:
WITH
rec_cte AS (
SELECT PLAN_NR, START_DATE, MAX_PERIODS,
1 period_nr, DATEADD(day, 7, START_DATE) next_date
FROM TABLE_1
UNION ALL
SELECT PLAN_NR, next_date, MAX_PERIODS,
period_nr + 1, DATEADD(day, 7, next_date)
FROM rec_cte
WHERE period_nr < MAX_PERIODS
),
cte1 AS (
SELECT PLAN_NR, period_nr, START_DATE, MAX_PERIODS
FROM rec_cte
UNION ALL
SELECT PLAN_NR, period_nr, DATEADD(DAY, 1, EOMONTH(next_date, -1)), MAX_PERIODS
FROM rec_cte
WHERE MONTH(START_DATE) <> MONTH(next_date)
),
cte2 AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY PLAN_NR ORDER BY START_DATE) rn
FROM cte1
)
SELECT PLAN_NR, rn PERIOD_NR, START_DATE
FROM cte2
WHERE rn <= MAX_PERIODS
ORDER BY PLAN_NR, START_DATE
参见demo。
结果:
> 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-01
> 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
> 2 | 5 | 2020-09-01
> 2 | 6 | 2020-09-02
> 2 | 7 | 2020-09-09
> 2 | 8 | 2020-09-16