Используйте SQL для удаления дубликатов из медленно меняющегося измерения типа 2.

Я создаю хранилище данных с несколькими слоями, в которых хранятся одни и те же данные. Все данные в одном из средних слоев имеют версии с начальной и конечной датами, как если бы это было медленно меняющееся измерение типа 2. Проблема возникает, когда я запрашиваю эти таблицы. Обычно в таблице больше столбцов, чем в запросе, поэтому смежные версии в запросе имеют разные даты начала и окончания, но в остальном идентичны. Я хочу объединить эти версии, чтобы показывать даты, когда изменяются столбцы в запросе, а не когда изменяются строки в таблице.

У меня есть SQL, который почти работает:

create table versions 
(id int
, name varchar(100) Not null
, RowStartDate datetime Not null
, RowEndDate datetime Not null
, primary key (id,RowStartDate)
, check (RowStartDate < RowEndDate));

insert into versions values 
 (1,'A','2014-01-01','9999-12-31')
,(2,'B','2014-01-01','2014-12-31')
,(2,'B','2014-12-31','9999-12-31')
,(3,'C','2014-01-01','2014-12-31')
,(3,'CC','2014-12-31','2015-12-31')
,(3,'CC','2015-12-31','9999-12-31')
,(4,'D','2014-01-01','2014-12-31')
,(4,'DD','2014-12-31','2015-12-31')
,(4,'DD','2015-12-31','2016-12-31')
,(4,'D','2016-12-31','9999-12-31')
,(5,'E','2014-01-01','2014-12-31')
,(5,'E','2014-12-31','2015-12-31')
,(5,'E','2015-12-31','2016-12-31')
,(5,'E','2016-12-31','2017-12-31')
,(5,'E','2017-12-31','9999-12-31')
;

WITH CTE_detect_duplicates AS (SELECT [id]
      ,[name]
      ,[RowStartDate]
      ,[RowEndDate]
      ,LAST_VALUE(RowEndDate) OVER (PARTITION BY id, name ORDER BY RowStartDate, RowEndDate ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) as LastEndDate
      ,rank() OVER (PARTITION BY id, name ORDER BY RowStartDate, RowEndDate) as duplicateNumber
  FROM versions
 )
SELECT [id]
      ,[name]
      ,[RowStartDate]
      ,LastEndDate as RowEndDate
FROM CTE_detect_duplicates
WHERE duplicateNumber = 1

The problem here is that it returns two rows for id "4", when three are desired. Actual:

id  name    RowStartDate    RowEndDate
4   D   2014-01-01 00:00:00.000 9999-12-31 00:00:00.000
4   DD  2014-12-31 00:00:00.000 2016-12-31 00:00:00.000
Desired:
id  name    RowStartDate    RowEndDate
4   D   2014-01-01 00:00:00.000 2014-12-31 00:00:00.000
4   DD  2014-12-31 00:00:00.000 2016-12-31 00:00:00.000
4   D   2016-12-31 00:00:00.000 9999-12-31 00:00:00.000
The value D is not correct for the period when the value DD is correct, so the version dates are incorrect on the first row (4,'D') from the my query.

Я хочу иметь возможность удалять эти дубликаты в чистом SQL или встроенной функции с табличным значением (у меня есть генератор, который создает многооператорные табличные функции, которые делают это, но результирующие функции работают плохо). У кого-нибудь есть идеи?


person ensslen    schedule 10.07.2014    source источник


Ответы (1)


Следующий запрос, содержащий несколько CTE, сжимает диапазоны дат обновлений и удаляет повторяющиеся значения.

1 Первые ранги назначаются в каждой группе идентификаторов на основе RowStartDate.

2 Затем определяется максимальный ранг (next_rank_no) диапазона рангов, который имеет одно и то же значение для NAME. Таким образом, для примера данных строка 1 с id=5 будет иметь next_rank_no=5, а строка 2 с id=4 будет иметь next_rank_no=3. Эта версия обрабатывает только столбец NAME. Если вы хотите обрабатывать дополнительные столбцы, они также должны быть включены в условие. Например, если вы хотите включить столбец LOCATION, условия соединения будут выглядеть так:

  left join sorted_versions sv2 on sv2.id = sv1.id and sv2.rank_no > sv1.rank_no and sv2.name = sv1.name and sv2.location = sv1.location
  left join sorted_versions sv3 on sv3.id = sv1.id and sv3.rank_no > sv1.rank_no and (sv3.name <> sv1.name or sv3.location <> sv1.location)

3 Наконец, выбирается первая строка для каждого идентификатора. Затем рекурсивным образом выбирается строка, соответствующая next_rank_no.

with sorted_versions as --ranks are assigned within each id group
(
  select 
    v1.id,
    v1.name,
    v1.RowStartDate,
    v1.RowEndDate,
    rank() over (partition by v1.id order by v1.RowStartDate) rank_no
  from versions v1
  left join versions v2 on (v1.id = v2.id and v2.RowStartDate = v1.RowEndDate)
),
next_rank as --the maximum rank of the range of ranks which has the same value for NAME
(
  select 
  sv1.id id, sv1.rank_no rank_no, COALESCE(min(sv3.rank_no)-1 , COALESCE(max(sv2.rank_no), sv1.rank_no)) next_rank_no
  from sorted_versions sv1
  left join sorted_versions sv2 on sv2.id = sv1.id and sv2.rank_no > sv1.rank_no and sv2.name = sv1.name
  left join sorted_versions sv3 on sv3.id = sv1.id and sv3.rank_no > sv1.rank_no and sv3.name <> sv1.name
  group by sv1.id, sv1.rank_no
),
versions_cte as --the rowenddate of the "maximum rank" is selected 
(
  select sv.id, sv.name, sv.rowstartdate, sv3.rowenddate, nr.next_rank_no rank_no
  from sorted_versions sv
  inner join next_rank nr on sv.id = nr.id and sv.rank_no = nr.rank_no and sv.rank_no = 1
  inner join sorted_versions sv3 on nr.id = sv3.id and nr.next_rank_no = sv3.rank_no  
  union all
  select
    sv2.id,
    sv2.name, 
    sv2.rowstartdate,
    sv3.rowenddate,
    nr.next_rank_no
  from versions_cte vc
  inner join sorted_versions sv2 on sv2.id = vc.id and sv2.rank_no = vc.rank_no + 1
  inner join next_rank nr on sv2.id = nr.id and sv2.rank_no = nr.rank_no  
  inner join sorted_versions sv3 on nr.id = sv3.id and nr.next_rank_no = sv3.rank_no
)
select id, name, rowstartdate, rowenddate
from versions_cte
order by id, rowstartdate;

Демо SQL Fiddle

person Joseph B    schedule 10.07.2014