SQL:每月在日期范围内生成记录

时间:2018-06-27 08:50:20

标签: sql sql-server

我有一张表,描述了在几天/几个月的特定时间内有效的值。 该表如下所示:

+----+------------+------------+-------+
| Id |    From    |     To     | Value |
+----+------------+------------+-------+
|  1 | 2018-01-01 | 2018-03-31 | ValA  |
|  2 | 2018-01-16 | NULL       | ValB  |
|  3 | 2018-04-01 | 2018-05-12 | ValC  |
+----+------------+------------+-------+

如您所见,这一天唯一仍然有效的值是ValB(To为可为空,From不是)。

我正在尝试在这张桌子上实现这样的视图(假设我在2018年7月的某个天呈现此视图):

+----------+------------+------------+-------+
| RecordId |    From    |     To     | Value |
+----------+------------+------------+-------+
|        1 | 2018-01-01 | 2018-01-31 | ValA  |
|        1 | 2018-02-01 | 2018-02-28 | ValA  |
|        1 | 2018-03-01 | 2018-03-31 | ValA  |
|        2 | 2018-01-16 | 2018-01-31 | ValB  |
|        2 | 2018-02-01 | 2018-02-28 | ValB  |
|        2 | 2018-03-01 | 2018-03-31 | ValB  |
|        2 | 2018-04-01 | 2018-04-30 | ValB  |
|        2 | 2018-05-01 | 2018-05-31 | ValB  |
|        2 | 2018-06-01 | 2018-06-30 | ValB  |
|        3 | 2018-04-01 | 2018-04-30 | ValC  |
|        3 | 2018-05-01 | 2018-05-12 | ValC  |
+----------+------------+------------+-------+

此视图基本上为表中的每个记录创建一条记录,但使用正确的日期(尤其要注意不在该月的第一天或最后一天的开始日期和结束日期)按月进行拆分。 一条没有“截止日期”的记录(因此到今天仍然有效)被呈现到我呈现视图的当月的最后一天,因此在撰写本文时,这是2018年7月。

这是一个简单的示例,但是一个解决方案将对我有很大帮助。我需要进行多次计算,包括金额分配。

这是一个表脚本和一些您可以使用的插入语句:

CREATE TABLE [dbo].[Test]
(
    [Id] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
    [From] SMALLDATETIME NOT NULL,
    [To] SMALLDATETIME NULL,
    [Value] NVARCHAR(100) NOT NULL
)

INSERT INTO dbo.Test ([From],[To],[Value])
VALUES 
('2018-01-01','2018-03-31','ValA'),
('2018-01-16',null,'ValB'),
('2018-04-01','2018-05-12','ValC');

谢谢!

4 个答案:

答案 0 :(得分:2)

生成可能出现在您的值上的所有月份(带有开始和结束),然后在每个月与值的期限重叠的地方合并。更改结果,以便如果一个月不能完全重叠,则只需显示您的期限即可。

DECLARE @StartDate DATE = '2018-01-01'
DECLARE @EndDate DATE = '2020-01-01'

;WITH GeneratedMonths AS
(
    SELECT
        StartDate = @StartDate,
        EndDate = EOMONTH(@StartDate)
    UNION ALL
    SELECT
        StartDate = DATEADD(MONTH, 1, G.StartDate),
        EndDate = EOMONTH(DATEADD(MONTH, 1, G.StartDate))
    FROM
        GeneratedMonths AS G
    WHERE
        DATEADD(MONTH, 1, G.StartDate) < @EndDate
)
SELECT
    T.Id,
    [From] = CASE WHEN T.[From] >= G.StartDate THEN T.[From] ELSE G.StartDate END,
    [To] = CASE WHEN G.EndDate >= T.[To] THEN T.[To] ELSE G.EndDate END,
    T.Value
FROM
    dbo.Test AS T
    INNER JOIN GeneratedMonths AS G ON
        G.EndDate >= T.[From] AND
        G.StartDate <= ISNULL(T.[To], GETDATE())
ORDER BY
    T.Id,
    G.StartDate
OPTION
    (MAXRECURSION 3000)

答案 1 :(得分:2)

@EzLo您的解决方案很好,但需要设置2个具有固定值的变量。 为避免这种情况,您可以对真实数据进行递归CTE

WITH A AS(
    SELECT
    T.Id, CAST(T.[From] AS DATE) AS [From], CASE WHEN T.[To]<EOMONTH(T.[From], 0) THEN T.[To] ELSE EOMONTH(T.[From], 0) END AS [To], T.Value, CAST(0 AS INTEGER) AS ADD_M
    FROM
    TEST T
    UNION ALL
    SELECT
    T.Id, DATEADD(DAY, 1, EOMONTH(T.[From], -1+(A.ADD_M+1))), CASE WHEN T.[To]<EOMONTH(T.[From], A.ADD_M+1) THEN T.[To] ELSE EOMONTH(T.[From], A.ADD_M+1) END AS [To], T.Value, A.ADD_M+1
    FROM 
    TEST T
    INNER JOIN A ON T.Id=A.Id AND DATEADD(MONTH, A.ADD_M+1, T.[From]) < CASE WHEN T.[To] IS NULL THEN CAST(GETDATE() AS DATE) ELSE T.[To] END

) 
SELECT
    A.[Id], A.[From], A.[To], A.[Value]
FROM
    A
ORDER BY A.[Id], A.[From]

答案 2 :(得分:2)

通过使用日期函数和递归CTE。

with cte as
(
Select Id, Cast([From] as date) as [From], EOMONTH([from]) as [To1],
COALESCE([To],EOMONTH(GETDATE())) AS [TO],Value from test

UNION ALL

Select Id, DATEADD(DAY,1,[To1]), 
CASE when EOMONTH(DATEADD(DAY,1,[To1])) > [To] THEN CAST([To] AS DATE)
ELSE EOMONTH(DATEADD(DAY,1,[To1])) END as [To1], 
[To],Value from cte where TO1 <>  [To]
)
Select Id, [From],[To1] as [To], Value from cte order by Id 

答案 3 :(得分:2)

如果没有大型数据集,则

递归 cte 是非常简单的方法:

with t as (
    select id, [from], [to], Value
    from Test
    union all
    select id, dateadd(mm, 1, [from]), [to], value
    from t
    where dateadd(mm, 1, [from]) < coalesce([to], getdate())
)

select id, [from], (case when eomonth([from]) <= coalesce([to], cast(getdate() as date)) 
                         then eomonth([from]) else coalesce([to], eomonth([from])) 
                    end) as [To], 
       Value
from t
order by id;