Удалите последовательно повторяющиеся значения, которые появляются в пределах временной шкалы на основе даты - PullRequest
0 голосов
/ 07 сентября 2018

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

DECLARE @tbl TABLE (
    UserID int,
    ActionID int,
    ActionDesc nvarchar(50),
    ActionDate datetime
);
INSERT INTO @tbl (UserID, ActionID, ActionDesc, ActionDate)
VALUES 
    -- First person
    (1, 200, 'Promoted',   '2000-01-01'),   
    (1, 200, 'Promoted',   '2001-01-01'),   
    (1, 200, 'Promoted',   '2002-02-01'),   
    (1, 300, 'Moved',      '2004-03-01'),   
    (1, 200, 'Promoted',   '2005-03-01'),   
    (1, 200, 'Promoted',   '2006-03-01'),
    -- Second person
    (2, 200, 'Promoted',   '2006-01-01'),   
    (2, 300, 'Moved',      '2007-01-01'),
    (2, 200, 'Promoted',   '2008-01-01');

SELECT * FROM @tbl ORDER BY UserID, ActionDate DESC;

Это дает следующее, показанное как самое последнее событие первым:

enter image description here

Мне нужно показать таблицу в обратном порядке дат, но удалить все события, которые происходят непосредственно после того, как они уже произошли, на основе совпадения [UserID / ActionID]. Например, если этого человека повысили, а затем снова повысили сразу после этого, второе повышение не будет включено в результаты, поскольку оно будет считаться дубликатом предыдущего действия.

Следовательно, желаемый результат:

enter image description here

После исследования я попытался ROW_NUMBER() идентифицировать дубликаты:

SELECT
    *,
    ROW_NUMBER() OVER (PARTITION BY UserID, ActionID ORDER BY ActionDate ASC) AS RowNum
FROM
    @tbl
ORDER BY
    UserID, ActionDate DESC;

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

Ответы [ 3 ]

0 голосов
/ 07 сентября 2018
SELECT * FROM
    (SELECT *, ROW_NUMBER() over (partition by Q2.userid, Q2.ActionId, rn2 order by Q2.actiondate) rn3 FROM
        (select *, Q1.rn - ROW_NUMBER() over (partition by Q1.userid, Q1.actionid order by Q1.actiondate) rn2 from 
            (SELECT *,ROW_NUMBER() over (order by userid, actiondate) rn from @tbl) Q1
        ) Q2
    ) 
Q3 Where q3.rn3 = 1 ORDER BY Q3.UserID,Q3.ActionDate 

первый (внутренний) запрос назначает row_number для каждой строки, упорядоченный по userid и actiondate, - затем я вычисляю row_number, такой же, как и этот, но также разделяю на 'action' - если я подзадачу B из A, яполучить число, которое может применяться только к одной группе пользовательских идентификаторов и действий - сделав еще один row_number, разделенный по идентификатору пользователя, actionId и моему rown_number и упорядоченный по дате, я могу затем выбрать строку 1, самую раннюю дату.

0 голосов
/ 07 сентября 2018

Я бы использовал LEAD, чтобы исключить ненужные строки.

USE tempdb;

DECLARE @tbl TABLE (
    UserID int,
    ActionID int,
    ActionDesc nvarchar(50),
    ActionDate datetime
);
INSERT INTO @tbl (UserID, ActionID, ActionDesc, ActionDate)
VALUES 
    -- First person
    (1, 200, 'Promoted',   '2000-01-01'),   
    (1, 200, 'Promoted',   '2001-01-01'),   
    (1, 200, 'Promoted',   '2002-02-01'),   
    (1, 300, 'Moved',      '2004-03-01'),   
    (1, 200, 'Promoted',   '2005-03-01'),   
    (1, 200, 'Promoted',   '2006-03-01'),
    -- Second person
    (2, 200, 'Promoted',   '2006-01-01'),   
    (2, 300, 'Moved',      '2007-01-01'),
    (2, 200, 'Promoted',   '2008-01-01');

;WITH src AS
(
    SELECT *
        , l = LEAD(t.ActionID) OVER (PARTITION BY t.UserID ORDER BY t.ActionDate DESC)
    FROM @tbl t
)
SELECT src.UserID
    , src.ActionID
    , src.ActionDesc
    , src.ActionDate
FROM src
WHERE src.l <> src.ActionID 
    OR src.l IS NULL

Предложение WHERE в приведенном выше запросе удаляет повторяющиеся строки из выходных данных, где предыдущая строка является дубликатом ActionID текущей строки. src.l IS NULL гарантирует, что мы видим строки без повторяющихся идентификаторов действий.

Результаты:

╔════════╦══════════╦════════════╦═════════════════════════╗
║ UserID ║ ActionID ║ ActionDesc ║       ActionDate        ║
╠════════╬══════════╬════════════╬═════════════════════════╣
║      1 ║      200 ║ Promoted   ║ 2005-03-01 00:00:00.000 ║
║      1 ║      300 ║ Moved      ║ 2004-03-01 00:00:00.000 ║
║      1 ║      200 ║ Promoted   ║ 2000-01-01 00:00:00.000 ║
║      2 ║      200 ║ Promoted   ║ 2008-01-01 00:00:00.000 ║
║      2 ║      300 ║ Moved      ║ 2007-01-01 00:00:00.000 ║
║      2 ║      200 ║ Promoted   ║ 2006-01-01 00:00:00.000 ║
╚════════╩══════════╩════════════╩═════════════════════════╝

Для таблиц с большим количеством строк вы хотите уменьшить количество агрегатов, используемых в вашем запросе, до минимально возможного; LEAD обеспечивает именно это, требуя только одного агрегата. План выполнения моей версии:

enter image description here

0 голосов
/ 07 сентября 2018
DECLARE @tbl TABLE (
    UserID int,
    ActionID int,
    ActionDesc nvarchar(50),
    ActionDate datetime
);
INSERT INTO @tbl (UserID, ActionID, ActionDesc, ActionDate)
VALUES 
    -- First person
    (1, 200, 'Promoted',   '2000-01-01'),   
    (1, 200, 'Promoted',   '2001-01-01'),   
    (1, 200, 'Promoted',   '2002-02-01'),   
    (1, 300, 'Moved',      '2004-03-01'),   
    (1, 200, 'Promoted',   '2005-03-01'),   
    (1, 200, 'Promoted',   '2006-03-01'),
    -- Second person
    (2, 200, 'Promoted',   '2006-01-01'),   
    (2, 300, 'Moved',      '2007-01-01'), --<<--- here ActionID is 300
    (2, 200, 'Promoted',   '2008-01-01');

select UserID, ActionID, ActionDesc, min(ActionDate) as dt
  from (
         select t.*
              , row_number() over(partition by UserID, ActionID order by ActionDate)
                - row_number() over(partition by UserID order by ActionDate) as grp_id
           from @tbl t
       ) v
 group by grp_id, UserID, ActionID, ActionDesc
 order by UserID, min(ActionDate) desc;

Это обеспечивает ваш результат, но только если ActionID из Moved равно 300, если нет, вам следует разделить на ActionDesc вместо ActionID.

...