我有下表:
ID Rating Rating_from Rating_to
1 2 2010-01-01 2011-01-01
1 2 2011-01-02 2012-02-01
1 3 2012-02-02 2013-03-01
1 2 2013-03-02 2013-04-01
1 2 2013-04-02 9999-12-31
它包含每个 ID 的评级,该评级是临时检查的。每次检查评级时,最后一行都会给出一个 Rating_to
日期,通常是新评级的前一天,并输入一个新行,其中包含新评级的 Rating_from
日期。实际评级日。 Rating_to
设置为 9999-12-31
而不是 NULL
。 🤷♀️ 通常,评级保持不变。有时,评级会发生变化。随着时间的推移,ID 也可能会收到以前的评级。
如何获取每个 ID、每个评级的最早 Rating_from
日期和最新 Rating_to
日期,而不将具有相同评级值但散布的评级分组其他评级?
我正在尝试获取下表:
ID Rating Rating_from Rating_to
1 2 2010-01-01 2012-02-01
1 3 2012-02-02 2013-03-01
1 2 2013-03-02 NULL
使用上面的数据,我尝试按 ID
和 Rating
进行分组(并设置 MIN()
和 MAX( )
在 from 和 to 字段上),但是我只会得到两行,一行用于评级 2,另一行用于评级 3,即使有两个评级 2 的时段。
我问了一位同事,他建议使用 LAG()
和 LEAD()
,但我不确定这在这里有什么帮助。数据位于SQL Server 2017中,大约有100万个ID。非常欢迎任何建议。
我添加了以下带有真实表数据的TABLE CREATE
脚本,希望有所帮助:
CREATE TABLE tbl(
id INT,
rating int,
rating_from DATE,
rating_to DATE
);
INSERT INTO tbl VALUES
(1, 2, '2014-05-23', '2015-04-13'),
(1, 2, '2015-04-14', '2015-06-02'),
(1, 2, '2015-06-03', '2016-05-31'),
(1, 2, '2016-06-01', '2018-03-22'),
(2, 1, '2016-06-01', '9999-12-31'),
(3, 3, '2016-06-01', '9999-12-31'),
(1, 2, '2018-03-23', '2018-08-06'),
(1, 3, '2018-08-07', '2018-08-21'),
(1, 2, '2018-08-22', '2018-09-19'),
(1, 2, '2018-09-20', '9999-12-31');
最佳答案
这是基于 Itzik Ben-Gan's Islands approach 的解决方案。它首先查找发生更改的行。生成这些的运行总计,以获得每个更改的唯一 ID,然后对更改进行分组。这是一种快速而优雅的方法。
With LagAndLead AS
(
SELECT
ID,Rating,Rating_from,Rating_to
, CASE WHEN LAG(Rating) OVER (PARTITION BY ID ORDER BY Rating_from) <> Rating
THEN 1
ELSE 0
END AS IsStart
FROM tbl
),
Islands AS
(
SELECT ID,Rating,Rating_from, rating_to
, SUM(IsStart) OVER (PARTITION BY ID ORDER BY Rating_from ROWS UNBOUNDED PRECEDING) AS IslandID
FROM LagAndLead
)
SELECT S.ID,MIN(S.Rating) AS Rating ,min(S.Rating_from) AS Rating_from, max(S.rating_to) AS rating_to
FROM Islands AS S
GROUP BY S.ID,S.IslandID
示例:dbfiddle.uk
关于带分区的 SQL 分组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58337565/