【问题标题】:How to get date ranges over equal consecutive values having a single date如何在具有单个日期的相等连续值上获取日期范围
【发布时间】:2022-09-24 01:24:16
【问题描述】:

我有这样的表:

CREATE TABLE Rates
(
   RateGroup int NOT NULL,
   Rate decimal(5, 2) NOT NULL,
   DueDate date NOT NULL
);

此表包含从某个到期日到前一天有效的费率。 下一个到期日。如果不存在下一个到期日,则费率的有效期没有结束。有可以 是多个连续的到期日,利率相同,并且某个可能出现在不同的日期 非连续到期日也是如此。

费率按组划分。一个截止日期可以出现在多个组中,但只能出现在多个组中 每组一次。

这是一些示例数据:

INSERT INTO Rates(RateGroup, Rate, DueDate)
   VALUES
      (1, 1.2, \'20210101\'), (1, 1.2, \'20210215\'), (1, 1.5, \'20210216\'),
      (1, 1.2, \'20210501\'), (2, 3.7, \'20210101\'), (2, 3.7, \'20210215\'),
      (2, 3.7, \'20210216\'), (2, 3.7, \'20210501\'), (3, 2.9, \'20210101\'),
      (3, 2.5, \'20210215\'), (3, 2.5, \'20210216\'), (3, 2.1, \'20210501\');
RateGroup Rate DueDate
1 1.20 2021-01-01
1 1.20 2021-02-15
1 1.50 2021-02-16
1 1.20 2021-05-01
2 3.70 2021-01-01
2 3.70 2021-02-15
2 3.70 2021-02-16
2 3.70 2021-05-01
3 2.90 2021-01-01
3 2.50 2021-02-15
3 2.50 2021-02-16
3 2.10 2021-05-01

现在我想要一个查询,它将具有相同速率的速率组的多个连续行折叠到 单行包含汇率有效的日期范围(开始和结束日期)。这是 期望的结果:

RateGroup Rate StartDate EndDate
1 1.20 2021-01-01 2021-02-15
1 1.50 2021-02-16 2021-04-30
1 1.20 2021-05-01 NULL
2 3.70 2021-01-01 NULL
3 2.90 2021-01-01 2021-02-14
3 2.50 2021-02-15 2021-04-30
3 2.10 2021-05-01 NULL

我怎样才能做到这一点?

    标签: sql sql-server


    【解决方案1】:

    这可以通过Common Table Expressions 完成 使用OVER Clause 如以下查询:

    WITH
        RatesWithBegin AS
            (
                SELECT RateGroup, Rate, DueDate,
                        CASE
                            WHEN Rate = LAG(Rate) OVER (PARTITION BY RateGroup ORDER BY DueDate)
                                THEN 0
                            ELSE 1
                        END AS IsBegin
                    FROM Rates
            ),
        RatesFromTo AS
            (
                SELECT RateGroup, Rate, DueDate AS StartDate,
                        LEAD (DATEADD(day, -1, DueDate)) OVER
                            (
                                PARTITION BY RateGroup
                                ORDER BY DueDate
                            ) AS EndDate,
                        SUM (IsBegin) OVER
                            (
                                PARTITION BY RateGroup
                                ORDER BY DueDate
                                ROWS UNBOUNDED PRECEDING
                            ) AS RangeID
                    FROM RatesWithBegin
            )
        SELECT RateGroup, MAX(Rate) AS Rate, MIN(StartDate) AS StartDate,
                NULLIF(MAX(COALESCE(EndDate, '99990101')), '99990101') AS EndDate
            FROM RatesFromTo
            GROUP BY RateGroup, RangeID
            ORDER BY RateGroup, StartDate;
    

    它是如何工作的?

    RatesWithBegin AS
        (
            SELECT RateGroup, Rate, DueDate,
                    CASE
                        WHEN Rate = LAG(Rate) OVER (PARTITION BY RateGroup ORDER BY DueDate)
                            THEN 0
                        ELSE 1
                    END AS IsBegin
                FROM Rates
        ),
    

    这里我们使用LAG() 将当前汇率与其前身进行比较。 PARTITION BY RateGroup 确保我们不会 混合比率组和ORDER BY DueDate 确定我们查看行的顺序。

    如果当前速率等于它的前身,我们用 0 标记该行,否则用 1 标记。 对于第一速率组,此 CTE 的结果如下所示:

    RateGroup Rate DueDate IsBegin
    1 1.20 2021-01-01 1
    1 1.20 2021-02-15 0
    1 1.50 2021-02-16 1
    1 1.20 2021-05-01 1

    0 和 1 不是任意值;下一步需要它们。

    RatesFromTo AS
        (
            SELECT RateGroup, Rate, DueDate AS StartDate,
                    LEAD (DATEADD(day, -1, DueDate)) OVER
                        (
                            PARTITION BY RateGroup
                            ORDER BY DueDate
                        ) AS EndDate,
                    SUM (IsBegin) OVER
                        (
                            PARTITION BY RateGroup
                            ORDER BY DueDate
                            ROWS UNBOUNDED PRECEDING
                        ) AS RangeID
                FROM RatesWithBegin
        )
    

    在这个 CTE 中,我们在 IsBegin 列上构建了一个运行总计 SUM()。由于一开始的值为 1 一个新的范围和一个范围内的 0 我们的运行总增量总是在一个新的开始 范围。这导致每个范围都有一个唯一的数字。

    使用LEAD() 我们添加 在我们的输出范围内的下一个截止日期的前一天。对于第一个速率组,此步骤的结果是:

    RateGroup Rate StartDate EndDate RangeID
    1 1.20 2021-01-01 2021-02-14 1
    1 1.20 2021-02-15 2021-02-15 1
    1 1.50 2021-02-16 2021-04-30 2
    1 1.20 2021-05-01 NULL 3
    SELECT RateGroup, MAX(Rate) AS Rate, MIN(StartDate) AS StartDate,
            NULLIF(MAX(COALESCE(EndDate, '99990101')), '99990101') AS EndDate
        FROM RatesFromTo
        GROUP BY RateGroup, RangeID
        ORDER BY RateGroup, StartDate;
    

    现在我们有了日期范围的唯一标识符 (RangeID),我们可以做一个简单的 与GROUP BY 聚合以获得我们想要的结果。由于范围可以是开放式的 (没有下一个截止日期)我们使用

    NULLIF(MAX(COALESCE(EndDate, '99990101')), '99990101')
    

    确保NULL 始终被视为可能的最新日期。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-06
      相关资源
      最近更新 更多