SQL группировка с разделами - PullRequest
3 голосов
/ 11 октября 2019

У меня есть следующая таблица:

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

Содержит рейтинг по идентификатору, который проверяется на разовой основе. Каждый раз, когда проверяется рейтинг, последней строке присваивается дата Rating_to, обычно за день до нового рейтинга, и вводится новая строка с датой Rating_from фактического дня рейтинга. Rating_to установлен на 9999-12-31 вместо NULL. ?‍♀️ Часто рейтинг остается прежним. Время от времени рейтинг меняется. Идентификатор может также со временем получить рейтинг, который он имел раньше.

Как получить самую раннюю дату 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, и их число составляет около миллиона. Любые предложения приветствуются.

Я добавил нижеприведенный скрипт 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');

Ответы [ 4 ]

1 голос
/ 11 октября 2019

Я считаю, что удобное решение похоже на подход lag(). Вместо lag() он ищет максимальное значение «на дату»

select id, rating, min(rating_from), max(rating_to)
from (select t.*,
             sum(case when dateadd(day, 1, prev_rating_to) >= rating_from then 0 else 1 end) over
                 (partition by id, rating order by rating_from) as grp
      from (select t.*,
                   max(rating_to) over (partition by id, rating
                                        order by rating_from
                                        rows between unbounded preceding and 1 preceding
                                       ) as prev_rating_to
            from tbl t
           ) t
     ) t
group by id, rating, grp
order by id, rating, min(rating_from);

. Этот метод также учитывает даты rating_to. Таким образом, он найдет пробелы, даже если рейтинг не изменится.

Здесь - это дБ <> скрипка.

1 голос
/ 11 октября 2019

Вот решение, основанное на подход островов Ицик Бен-Гана . Сначала он находит строки, в которых произошли изменения. Генерирует их итоговую сумму, чтобы получить уникальный идентификатор для каждого изменения, а затем группирует изменения. Это быстрый и элегантный подход.

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

0 голосов
/ 11 октября 2019

Пожалуйста, попробуйте следующий запрос, если он дает правильные результаты и лучшую производительность:

SELECT * FROM
(
SELECT 
  ID, Rating, 
  MIN(Rating_from) AS Rating_from, 
  MAX(Rating_to) AS Rating_to
FROM (

SELECT 
  ID, Rating, Rating_from, Rating_to
  ,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY Rating_from, ID) R_NUM 
  ,ROW_NUMBER() OVER(PARTITION BY Rating, ID ORDER BY Rating_from, ID) R_NUM_Rating
FROM TEST
) AS A
WHERE A.R_NUM = A.R_NUM_Rating OR A.R_NUM_Rating = 1
GROUP BY ID, Rating

UNION ALL

SELECT 
  ID, Rating, 
  MIN(Rating_from) AS Rating_from, 
  MAX(Rating_to) AS Rating_to
FROM (

SELECT 
  ID, Rating, Rating_from, Rating_to
  ,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY Rating_from, ID) R_NUM 
  ,ROW_NUMBER() OVER(PARTITION BY Rating, ID ORDER BY Rating_from, ID) R_NUM_Rating
FROM TEST
) AS A
WHERE A.R_NUM <> A.R_NUM_Rating AND A.R_NUM_Rating <> 1
GROUP BY ID, Rating
) AS FINAL
ORDER BY 3, 1
0 голосов
/ 11 октября 2019

Я просто попробую один пример и хочу поделиться с вами. если вы чувствуете, что результат в порядке, тогда примите это.

declare @temp as table 
(
    id int,
    rating int,
    rating_from date,
    rating_to date null
);

insert into @temp (id,rating,rating_from,rating_to)values
(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','2011-01-01'),
(1,2,'2013-04-02',null);

select id,rating,min(rating_from) rating_from,max(Rating_to) rating_to from @temp
group by id,rating
union 
select id,rating,max(rating_from) rating_from,max(Rating_to) rating_to from @temp
where Rating_to is null
group by id,rating
order by 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-04-02  NULL

Надеюсь, это поможет вам ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...