Запросить временную таблицу и объединить строки - PullRequest
0 голосов
/ 10 июля 2020

Допустим, у меня есть временная таблица с именем ProductDetails, которая с помощью следующего запроса возвращает некоторые исторические данные.

SELECT * FROM ProductDetails
FOR system_time
BETWEEN '1900-01-01 00:00:00' AND '9999-12-31 00:00:00'
WHERE ProductID = 8


ID    ProductID(FK)    Attribute    Value    SysStartTime           SysEndTime
--    -------------    ---------    -----    -------------------    ----------
1     8                Size         S        2020-07-06 05:00:00    9999-12-31 23:59:59
2     8                Color        Blue     2020-07-06 05:00:01    2020-07-09 11:11:11
2     8                Color        Green    2020-07-09 11:11:11    9999-12-31 23:59:59

Это означает, что продукт с ID = 8 был создан в 2020-07-06 05: 00:00, были добавлены 2 атрибута, а затем одна из записей была отредактирована с изменением с «Синего» на «Зеленый». Обратите внимание, что SysStartTime для второй строки имеет разницу в 1 секунду, когда они были сохранены.

Теперь мне нужно написать запрос, чтобы получить результаты ниже. По сути, это значения атрибутов в разные моментальные снимки времени, когда произошли изменения. Время сокращено до минуты.

Start Time          End Time            Attributes Values
----------------    ----------------    -----------------
2020-07-06 05:00    2020-07-09 11:11    Size = S, Color = Blue
2020-07-09 11:11    NULL                Size = S, Color = Green

Как я могу этого добиться? У каждого продукта могут быть разные атрибуты, но запрос предназначен для одного продукта за раз.

1 Ответ

1 голос
/ 16 июля 2020

Ниже представлено решение, которое форматирует ваши данные одним запросом. Производительность не является проблемой для небольшого набора данных из 4 строк (я добавил строку в ваш пример), но я предполагаю, что это не будет быстро для миллионов записей.

Предлагаемое здесь решение генерирует разные наборы данных в виде общих табличных выражений (CTE) и используют некоторые методы из других ответов StackOverflow на удаление секунд и объединяют значения строк . Плюс перекрестное применение в конце.

Подход можно описать в шагах, которые соответствуют последовательным CTE / объединениям:

  1. Создайте набор атрибутов для каждого продукта.
  2. Создайте набор моментов начала периода для каждого продукта (исключая секунды).
  3. Объедините атрибуты для каждого продукта с каждым периодом и найдите соответствующее значение.
  4. Используйте некоторые функции XML для форматирования значений атрибутов в одной строке.
  5. Используйте cross apply, чтобы получить конец периода.

Полное решение:

-- sample data
declare @data table
(
    ID              int,
    ProductId       int,
    Attribute       nvarchar(10),
    Value           nvarchar(10),
    SysStartTime    datetime2(0),
    SysEndTime      datetime2(0)
);

insert into @data (ID, ProductId, Attribute, Value, SysStartTime, SysEndTime) values
(1, 8, 'Size', 'S', '2020-07-06 05:00:00', '9999-12-31 23:59:59'),
(2, 8, 'Color', 'Blue', '2020-07-06 05:00:01', '2020-07-09 11:11:11'),
(2, 8, 'Color', 'Green', '2020-07-09 11:11:11', '9999-12-31 23:59:59'),
(2, 8, 'Weight', 'Light', '2020-07-10 10:11:12', '9999-12-31 23:59:59'); -- additional data to have extra attribute not available from start

-- solution
with prodAttrib as -- attributes per product
(
    select d.ProductId, d.Attribute
    from @data d
    group by d.ProductId, d.Attribute
),
prodPeriod as -- periods per product
(
    select  d.ProductId,
            dateadd(minute, datediff(minute, 0, d.SysStartTime), 0) as 'SysStartTimeNS' -- start time No Seconds
    from @data d
    group by ProductId, dateadd(minute, datediff(minute, 0, d.SysStartTime), 0)
),
prodResult as -- attribute value per period per product
(
    select  pp.ProductId,
            convert(nvarchar(16), pp.SysStartTimeNS, 120) as 'FromDateTime',
            convert(nvarchar(16), coalesce(pe.SysEndTime, '9999-12-31 23:59:59'), 120) as 'ToDateTime',
            pa.Attribute,
            av.Value
    from prodPeriod pp
    join prodAttrib pa
        on  pa.ProductId = pp.ProductId
    outer apply (   select top 1 d.Value
                    from @data d
                    where d.ProductId = pp.ProductId
                      and d.Attribute = pa.Attribute
                      and dateadd(minute, datediff(minute, 0, d.SysStartTime), 0) <= pp.SysStartTimeNS
                    order by d.SysStartTime desc ) av -- attribute values per product
    outer apply (   select top 1 dateadd(second, -1, d.SysStartTime) as 'SysEndTime'
                    from @data d
                    where d.ProductId = pp.ProductId
                      and dateadd(minute, datediff(minute, 0, d.SysStartTime), 0) > pp.SysStartTimeNS
                    order by d.SysStartTime ) pe -- period end
),
prodResultFormat as -- concatenate attribute values per period
(
    select  pp.ProductId,
            convert(nvarchar(16), pp.SysStartTimeNS, 120) as 'FromDateTime',
            (
                select pr.Attribute + ' = ' + coalesce(pr.Value,'') + ', ' as [text()]
                from prodResult pr
                where pr.ProductId = pp.ProductId
                  and pr.FromDateTime = convert(nvarchar(16), pp.SysStartTimeNS, 120)
                order by pr.Attribute
                for xml path('')
            ) as 'Attributes'
    from prodPeriod pp
)
select  prf.ProductId,
        prf.FromDateTime,
        x.ToDateTime,
        left(prf.Attributes, len(prf.Attributes)-1) as 'Attributes'
from prodResultFormat prf
cross apply (   select top 1 pr.ToDateTime
                from prodResult pr
                where pr.ProductId = prf.ProductId
                  and pr.FromDateTime = prf.FromDateTime ) x
order by prf.ProductId, prf.FromDateTime;

Результат для расширенных данных примера:

ProductId    FromDateTime      ToDateTime        Attributes
-----------  ----------------  ----------------  ----------------------------------------
8            2020-07-06 05:00  2020-07-09 11:11  Color = Blue, Size = S, Weight = 
8            2020-07-09 11:11  2020-07-10 10:11  Color = Green, Size = S, Weight = 
8            2020-07-10 10:11  9999-12-31 23:59  Color = Green, Size = S, Weight = Light

PS замените x.EndDateTime на case when x.ToDateTime = '9999-12-31 23:59' then NULL else x.ToDateTime end as 'ToDateTime', если вам действительно нужны значения NULL.

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