Как я могу написать запрос для извлечения отдельных изменений из моментальных снимков данных? - PullRequest
2 голосов
/ 12 октября 2011

Мне нужно создать процесс, который будет извлекать изменения из таблицы, где каждая строка является снимком строки в другой таблице.В реальной проблеме много таблиц со многими полями, но в качестве простого примера предположим, что у меня есть следующие данные снимка:

Sequence    DateTaken      ID       Field1    Field2
--------    -----------    ----     ------    ------
       1    '2011-01-01'      1     'Red'          2
       2    '2011-01-01'      2     'Blue'        10
       3    '2011-02-01'      1     'Green'        2
       4    '2011-03-01'      1     'Green'        3
       5    '2011-03-01'      2     'Purple'       2
       6    '2011-04-01'      1     'Yellow'       2

Поля Sequence и DateTaken относятся непосредственно к снимкуСам стол.Поле ID является первичным ключом исходной таблицы, а Field1 и Field2 - другие поля в той же (исходной) таблице.

Я могу частично получить решение с помощью запросакак это:

WITH Snapshots (Sequence, DateTaken, ID, Field1, Field2, _Index)
AS
(
    SELECT Sequence, DateTaken, ID, Field1, Field2, ROW_NUMBER() OVER (ORDER BY ID, Sequence) _Index
    FROM #Snapshots
)
SELECT
      c.DateTaken, c.ID
    , c.Field1 Field1_Current, p.Field1 Field1_Previous, CASE WHEN c.Field1 = p.Field1 THEN 0 ELSE 1 END Field1_Changed
    , c.Field2 Field2_Current, p.Field2 Field2_Previous, CASE WHEN c.Field2 = p.Field2 THEN 0 ELSE 1 END Field2_Changed
FROM Snapshots c
JOIN Snapshots p ON p.ID = c.ID AND (p._Index + 1) = c._Index
ORDER BY c.Sequence DESC

Приведенный выше запрос определит, что меняется от одного снимка к другому, но он все еще не в той форме, которая мне нужна.Каждая строка в выводе может содержать несколько изменений.В конце дня мне нужно по одной строке на изменение, которое идентифицирует, какое поле было изменено, вместе с его предыдущими / текущими значениями.Поля, которые фактически не изменились, должны быть исключены из окончательного результата.Поэтому, если приведенный выше запрос выглядит следующим образом:

DateTaken   ID  Field1_Current  Field1_Previous  Field1_Changed  Field2_Current  Field2_Previous  Field2_Changed
----------  --  --------------  ---------------  --------------  --------------  ---------------  --------------
2011-04-01  1   Yellow          Green            1               2               3                1
2011-02-01  1   Green           Red              1               2               2                0

Мне нужно преобразовать это в нечто вроде этого:

DateTaken   ID  Field    Previous   Current
----------  --  -------  --------   ---------
2011-04-01  1   Field1   Green      Yellow
2011-04-01  1   Field2   3          2
2011-02-01  1   Field1   Red        Green

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

Большое спасибо за любые советы.

Ответы [ 2 ]

3 голосов
/ 12 октября 2011

Вот рабочий пример, который использует UNPIVOT. Это основано на моем ответе на мой вопрос Лучший способ частично UNPIVOT в пар в SQL

Это имеет несколько приятных особенностей.

  1. Добавление дополнительных полей легко. Просто добавьте значения в предложение SELECT и UNPIVOT. Вам не нужно добавлять дополнительные пункты UNION

  2. Предложение where WHERE curr.value <> prev.value никогда не изменяется независимо от того, сколько полей добавлено.

  3. Производительность на удивление быстро.

  4. Переносим на текущие версии Oracle, если вам нужно это


SQL

Declare @Snapshots as table(
Sequence int,
DateTaken      datetime,
[id] int,
field1 varchar(20),
field2 int)



INSERT INTO @Snapshots VALUES 

      (1,    '2011-01-01',      1,     'Red',          2),
      (2,    '2011-01-01',      2,     'Blue',        10),
      (3,    '2011-02-01',      1,     'Green',        2),
      (4,    '2011-03-01',      1,     'Green' ,       3),
      (5,    '2011-03-01',      2,     'Purple',       2),
      (6,    '2011-04-01',      1,     'Yellow',       2)

;WITH Snapshots (Sequence, DateTaken, ID, Field1, Field2, _Index)
AS
(
    SELECT Sequence, DateTaken, ID, Field1, Field2, ROW_NUMBER() OVER (ORDER BY ID, Sequence) _Index
    FROM @Snapshots
)
,  data as(
SELECT
     c._Index
    , c.DateTaken
    ,  c.ID
    , cast(c.Field1  as varchar(max)) Field1
    , cast(p.Field1  as varchar(max))Field1_Previous
    , cast(c.Field2   as varchar(max))Field2
    , cast(p.Field2  as varchar(max)) Field2_Previous 


FROM Snapshots c
JOIN Snapshots p ON p.ID = c.ID AND (p._Index + 1) = c._Index
)


, fieldsToRows 
     AS (SELECT DateTaken, 
                id,
                _Index,
                value,
                field

         FROM   data p UNPIVOT (value FOR field IN (field1, field1_previous, 
                                                        field2, field2_previous) ) 
                AS unpvt
        ) 
SELECT 
    curr.DateTaken,
    curr.ID,
    curr.field,
    prev.value previous,
    curr.value 'current'

FROM 
        fieldsToRows curr 
        INNER  JOIN  fieldsToRows prev
        ON curr.ID = prev.id
            AND curr._Index = prev._Index 
            AND curr.field + '_Previous' = prev.field
WHERE 
    curr.value <> prev.value

выход

DateTaken               ID          field     previous current
----------------------- ----------- --------- -------- -------
2011-02-01 00:00:00.000 1           Field1    Red      Green
2011-03-01 00:00:00.000 1           Field2    2        3
2011-04-01 00:00:00.000 1           Field1    Green    Yellow
2011-04-01 00:00:00.000 1           Field2    3        2
2011-03-01 00:00:00.000 2           Field1    Blue     Purple
2011-03-01 00:00:00.000 2           Field2    10       2
1 голос
/ 12 октября 2011
WITH Snapshots (Sequence, DateTaken, ID, Field, FieldValue, _Index) AS
(
    SELECT
        Sequence,
        DateTaken,
        ID,
        'Field1' AS Field
        CAST(Field1 AS VARCHAR(100)) AS FieldValue,  -- Find an appropriate length
        ROW_NUMBER() OVER (ORDER BY ID, Sequence)
    FROM
        #Snapshots
    UNION ALL
    SELECT
        Sequence,
        DateTaken,
        ID,
        'Field2' AS Field
        CAST(Field2 AS VARCHAR(100)) AS FieldValue,  -- Find an appropriate length
        ROW_NUMBER() OVER (ORDER BY ID, Sequence)
    FROM
        #Snapshots
)
SELECT
    S1.DateTaken,
    S1.ID,
    S1.Field,
    S1.FieldValue AS Previous,
    S2.FieldValue As New   -- Not necessarily "Current"
FROM
    Snapshots S1
INNER JOIN Snapshots S2 ON
    S2.ID = S1.ID AND
    S2.Field = S1.Field AND
    S2._Index = S1._Index + 1 AND
    S2.FieldValue <> S1.FieldValue    -- Might need to handle NULL values
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...