在空白月份中继续记录序列,直到出现另一条记录

问题描述 投票:0回答:1

我有一个表,该表继续客户 ID 并根据其结果记录分数。我遇到的问题是我需要按月显示分数,但会结转前几个月的分数,其中客户在过去的月份或当月没有完成测试。

例如,ClientID 1 在 2023 年 11 月、2024 年 1 月进行了测试,但在 2023 年 12 月、2024 年 2 月或 2024 年 3 月没有进行测试。因此查询需要将 2023 年 11 月的值带到 2023 年 12 月,然后再携带 1 月的值2024年进入2024年2月和2024年3月。

我真的不知道该怎么做。有人有什么想法吗?

    Create Table #temp
(
    ClientID int,
    DateCreated datetime,
    Score decimal(18,2)
)

insert into #temp
(
    ClientID,
    DateCreated,
    Score
)
select
    1,
    '01 Nov 2023',
    56
union all
select
    1,
    '15 Jan 2024',
    90
union all
select
    2,
    '08 Dec 2023',
    76
union all
select
    2,
    '25 Jan 2024',
    98
union all 
select
    2,
    '01 Mar 2024',
    23

预期结果

客户端ID 创建日期 分数 评论
1 2023-11-01 00:00:00.000 56.00
1 2023-12-01 00:00:00.000 56.00 (自动生成)
1 2024-01-15 00:00:00.000 90.00
1 2024-02-01 00:00:00.000 90.00 (自动生成)
1 2024-03-01 00:00:00.000 90.00 (自动生成)
2 2023-12-08 00:00:00.000 76.00
2 2024-01-25 00:00:00.000 98.00
2 2024-02-01 00:00:00.000 98.00 (自动生成)
2 2024-03-01 00:00:00.000 23.00 (自动生成)

这不像这个问题:获取最后一个非空值?因为我没有日期的空值。原始日期实际上并不存在。这个问题也有四年的历史了,接受的答案提到有更好的方法来做到这一点,新功能即将到来。

sql sql-server t-sql sql-server-2017
1个回答
0
投票

看起来你的问题有两个部分:

  1. 为原始表没有数据的月份/客户组合生成行。
  2. 使用最新的可用先验值填充这些生成的行中的分数。

第一部分可以通过生成一个列表或所有感兴趣的月份(递归 CTE 是一种技术)加上所有客户端 ID 的不同列表来完成。然后,您可以交叉连接两个源并应用

WHERE NOT EXISTS(...)
条件来排除数据已存在的源。

然后,您可以使用

CROSS APPLY(SELECT TOP 1 ... ORDER BY ...)
模式查找“最佳匹配”(最近的先前)数据行以检索该分数。这将为您提供所需的生成行。

然后可以使用

UNION ALL
将其与您的原始数据相结合以获得最终结果。

完成的查询将类似于:

WITH Months AS (
    SELECT DATEADD(month, DATEDIFF(month, 0,
             (SELECT MIN(T.DateCreated) FROM #Temp T)
             ), 0) AS Month  -- Tricky, equivalent of DATETRUNC(month, ...) 
    UNION ALL
    SELECT DATEADD(month, 1, M.Month) AS Month
    FROM Months M
    WHERE DATEADD(month, 1, M.Month) < GETDATE()
),
Clients AS (
    SELECT DISTINCT T.ClientId
    FROM #Temp T
)
SELECT T.ClientID, M.Month AS DateCreated, T.Score, Comments = '(auto-generated)'
FROM Months M
CROSS APPLY Clients C
CROSS APPLY (
    SELECT TOP 1 *
    FROM #Temp T
    WHERE T.ClientId = C.ClientId
    AND T.DateCreated < M.Month -- Prior
    ORDER BY T.DateCreated DESC -- Most Recent
) T
WHERE NOT EXISTS (
    -- Row does not exists for current month and client
    SELECT *
    FROM #Temp T
    WHERE T.ClientId = C.ClientId
    AND T.DateCreated >= M.Month
    AND T.DateCreated < DATEADD(month, 1, M.Month)
)
UNION ALL
SELECT T.ClientID, T.DateCreated, T.Score, Comments = ''
FROM #Temp T
ORDER BY CLientId, DateCreated

以上适用于 SQL Server 2017。如果我们使用更高版本的 SQL Server,我们可以使用

DATE_TRUNC()
函数,或许还可以使用
GENERATE_SERIES()
的形式来简化月份范围 CTE。

结果:

客户端ID 创建日期 分数 评论
1 2023-11-01 00:00:00.000 56.00
1 2023-12-01 00:00:00.000 56.00 (自动生成)
1 2024-01-15 00:00:00.000 90.00
1 2024-02-01 00:00:00.000 90.00 (自动生成)
1 2024-03-01 00:00:00.000 90.00 (自动生成)
2 2023-12-08 00:00:00.000 76.00
2 2024-01-25 00:00:00.000 98.00
2 2024-02-01 00:00:00.000 98.00 (自动生成)
2 2024-03-01 00:00:00.000 23.00

请参阅 this db<>fiddle(适用于 SQL Server 2017)进行演示。

© www.soinside.com 2019 - 2024. All rights reserved.