2017-01-06 3 views
4

Я задал аналогичный вопрос вчера, но я не очень хорошо разбирался в своем описании того, что я хотел. Это будет намного яснее.SQL Server 2014 - используйте предыдущее значение, когда дата не указана

Lead/Lag не получает меня, что мне нужно. Его близко, но недостаточно. Использование SQL Server 2014 для клиента, фактический сервер построен на SQL 2012.

Вот мой код: Создание команды Таблица

CREATE TABLE ##TeamTable 
    ([UserID] varchar(50), [CurrentTeam] varchar(5), [ChangeDate] datetime) 
; 

INSERT INTO ##TeamTable 
    ([UserID], [CurrentTeam], [ChangeDate]) 
VALUES 
    ('User1', 'Team1', '6/1/2016'), 
    ('User1', 'Team2', '9/1/2016'), 
    ('User1', 'Team3', '12/1/2016'), 
    ('User2', 'Team1', '4/1/2016'), 
    ('User2', 'Team2', '10/1/2016'), 
    ('User2', 'Team3', '11/1/2016'); 

Теперь, чтобы создать таблицу данных мне нужно присоединиться к

CREATE TABLE ##DataTable 
    ([UserID] varchar(50), Month_sk datetime, Media varchar(50), NCO int) 
INSERT INTO ##DataTable 
    ([UserID] , Month_sk , Media , NCO) 
VALUES 
    ('User1', '2016-06-01 00:00:00', 'Fax', 100), 
    ('User1', '2016-06-01 00:00:00', 'Voice', 120), 
    ('User1', '2016-07-01 00:00:00', 'Voice', 90), 
    ('User1', '2016-07-01 00:00:00', 'Email', 100), 
    ('User1', '2016-08-01 00:00:00', 'Voice', 150), 
    ('User1', '2016-08-01 00:00:00', 'Email', 100), 
    ('User1', '2016-09-01 00:00:00', 'Voice', 100), 
    ('User1', '2016-09-01 00:00:00', 'Email', 120), 
    ('User1', '2016-10-01 00:00:00', 'Voice', 90), 
    ('User1', '2016-10-01 00:00:00', 'Email', 100), 
    ('User1', '2016-11-01 00:00:00', 'Voice', 150), 
    ('User1', '2016-11-01 00:00:00', 'Email', 100), 
    ('User1', '2016-12-01 00:00:00', 'Voice', 150), 
    ('User1', '2016-12-01 00:00:00', 'Email', 100), 
    ('User2', '2016-04-01 00:00:00', 'Fax', 100), 
    ('User2', '2016-04-01 00:00:00', 'Voice', 120), 
    ('User2', '2016-05-01 00:00:00', 'Fax', 100), 
    ('User2', '2016-05-01 00:00:00', 'Voice', 120), 
    ('User2', '2016-06-01 00:00:00', 'Fax', 100), 
    ('User2', '2016-06-01 00:00:00', 'Voice', 120), 
    ('User2', '2016-07-01 00:00:00', 'Voice', 90), 
    ('User2', '2016-07-01 00:00:00', 'Email', 100), 
    ('User2', '2016-08-01 00:00:00', 'Voice', 150), 
    ('User2', '2016-08-01 00:00:00', 'Email', 100), 
    ('User2', '2016-09-01 00:00:00', 'Voice', 100), 
    ('User2', '2016-09-01 00:00:00', 'Email', 120), 
    ('User2', '2016-10-01 00:00:00', 'Voice', 90), 
    ('User2', '2016-10-01 00:00:00', 'Email', 100), 
    ('User2', '2016-11-01 00:00:00', 'Voice', 150), 
    ('User2', '2016-11-01 00:00:00', 'Email', 100), 
    ('User2', '2016-12-01 00:00:00', 'Voice', 150), 
    ('User2', '2016-12-01 00:00:00', 'Email', 100); 

Вот основной Выберите, чтобы показать, что происходит:

SELECT b.UserID 
     ,b.Media 
     ,b.NCO 
     ,Month_sk 
     ,CurrentTeam 

FROM ##DataTable b 

LEFT OUTER JOIN ##TeamTable a on b.UserID = a.UserID and b.Month_sk = a.ChangeDate 

order by UserID, Month_sk, media 

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

Click for data output

Что мне нужно для того, где у меня есть аннулирует, что он будет тянуть в прежнее название команды, которая не равно нулю. Таким образом, в случае User1 эти 4 нуля в течение месяцев июля и августа будут говорить Team1, поскольку это была команда, в которой он последний раз. То же самое касается нулей после Team2, они должны сказать Team2.

Lead/Lag близок, или я не использую его правильно. Надеюсь, что со всем этим кодом это упростит работу кого-то.

ОБНОВЛЕНИЕ: Lag/Lead дает те же результаты. Тем не менее нужны аннулирует заполнить

SELECT b.UserID 
     ,b.Media 
     ,b.NCO 
     ,Month_sk 
     ,CurrentTeam 
     ,LAG(CurrentTeam,1, currentteam) OVER(PARTITION BY a.userid, changedate ORDER BY ChangeDate) as Lag 

FROM ##DataTable b 

LEFT OUTER JOIN ##TeamTable a on b.UserID = a.UserID and b.Month_sk = a.ChangeDate 

order by UserID, Month_sk, media 
+0

Пожалуйста, добавьте вашу попытку использовать Lead/Lag, чтобы мы могли ее отладить. –

+0

Обновление добавлено в нижней части исходного сообщения. – Anthony

+0

Что должно произойти, если поле команды вашей первой строки равно null? – llouk

ответ

2

(Moving обновление отмечает до конца)

Я думаю, что самое простое решение (концептуально) является объединение против всех месяцев до month_sk и затем фильтруют, чтобы получить только последний совпадение. Это «чувствует» потенциально неэффективно, поэтому вы хотите протестировать его с реалистичным объемом данных, и если есть проблема, тогда ищите что-то лучше. (Но «что-то лучше» может включать в себя изменения в физической модели данных ...)

Итак:

select userid, media, nco, month_sk, currentteam 
    from (SELECT b.UserID 
      , b.Media 
      , b.NCO 
      , Month_sk 
      , CurrentTeam 
      , rank() over(partition by b.userID 
           order by a.changeDate desc) n 
     FROM   ##DataTable b 
      INNER JOIN ##TeamTable a 
        on b.UserID = a.UserID 
        and b.Month_sk >= a.ChangeDate 
     ) x 
where n = 1 
order by UserID, Month_sk, media 

Обратите внимание, что в предыдущих версиях я использовал row_number() over() вместо rank() over() ... и вы можете сделать это , но если вы это сделаете, вам необходимо включить в ключ раздела любые данные из таблицы b, которые могут привести к дублированию строки из таблицы a во время соединения. Использование rank гарантирует, что все такие дубликаты разделяют свой рейтинг так, как должны.

UPDATE - После того, как я изначально написал это, я удалил его, потому что думал, что неправильно прочитал ваш вопрос; но, поскольку я писал замену, я понял, что, возможно, это правильно. Так вот, с оговоркой:

Это предполагает, что единственной причиной, по которой вы получаете значение NULL, является внешнее соединение. Если когда-либо таблица «справа» имеет строку и только значение для столбца в ней равно NULL, то получение предыдущего значения для этого столбца потребует дальнейшей работы с подзапросами или аналитическими функциями. Но даже тогда лидерство/отставание могут не работать, поскольку они основаны на позиции.(Я думаю, что что-то с LAST_VALUE может быть более подходящим, но оставит подробности, что, если это не требуется.)

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

ОБНОВЛЕНИЕ 3 - Я неправильно прочитал ваши данные образца и получил выражение для разбиения на подсчет n. Должно быть установлено, что значения из таблицы b уникальны. Если он еще не исправлен, но требует больше обмана ...

+0

Мне действительно нужны те строки, где нули. Мне просто нужно, чтобы у них была правильная команда. Наша старая таблица, которая не соответствует каким-либо нормальным формам, имеет каждого сотрудника и их команду на каждый месяц. Это тонна избыточных данных, потому что большинство людей не часто меняют команды. Я хотел привести некоторые из наших таблиц в 3nf, и это тот, который я хотел поразить. Наши существующие отчеты должны иметь возможность вытягивать большие группы данных (до одного года за все команды) и перечислять данные по мере их вывода. Я использовал Left Outer Join, чтобы показать, как мне нужно, чтобы данные закончились. – Anthony

+0

Вы считаете, что думаете, что не будете * получать эти строки с ответом, который я опубликовал? Потому что, если я чего-то не упущу, ты это сделаешь. FWIW, в то время как старая структура таблицы, о которой вы упоминаете, может быть менее эффективной, используя '(emp, month)' в качестве ключа и включая все записи, все равно 3nf (опять же, если я что-то не хватает). –

+0

Дополнительное примечание: вы можете поместить (нулевой) конец месяца в записи команды, чтобы простая проверка диапазона могла управлять объединением, получая как компактную таблицу, так и более эффективный запрос. Как я уже сказал, «что-то лучше» может означать другую модель данных. Но это приводит к появлению возможных несоответствий (пробелов в датах или перекрытий) –

0

В этой версии я сначала определяю соответствующий месяц «связывания» в CTE, а затем использую его как поиск в финальном соединении. (Это стало намного легче, когда я понял, Media и NCO не играли реальную роль в объединении.)

WITH cteDateLookup 
as (
    -- Get the ChangeDate for this User/Month 
    SELECT 
     b.UserID 
     ,b.Month_sk 
     ,max(a.ChangeDate) ChangeDate 
    from ##DataTable b 
     left outer join ##TeamTable a 
     on b.UserID = a.UserID 
     and b.Month_sk >= a.ChangeDate 
    group by 
     b.UserID 
     ,b.Month_sk 
) 
-- Use the cte as a "lookup" for the appropriate date 
SELECT 
    b.UserID 
    ,b.Media 
    ,b.NCO 
    ,b.Month_sk 
    ,a.CurrentTeam 
from ##DataTable b 
    left outer join cteDateLookup cte 
    on cte.UserId = b.UserId 
    and b.Month_sk = cte.Month_sk 
    left outer join ##TeamTable a 
    on a.UserId = cte.UserId 
    and a.ChangeDate = cte.ChangeDate 
order by 
    b.UserID 
    ,b.Month_sk 
    ,b.media 
1

Вы можете сделать это с помощью APPLY и подзапросом, как это.

SELECT 
    userid, 
    media, 
    nco, 
    month_sk, 
    currentteam 
FROM 
    ##DataTable td 
    OUTER APPLY (
     SELECT TOP (1) 
      CurrentTeam, 
      ChangeDate 
     FROM 
      ##TeamTable tt 
     WHERE 
      tt.UserID = td.UserID 
      and tt.ChangeDate <= td.Month_sk 
     ORDER BY 
      tt.ChangeDate desc 
    ) dataTableWithTeam 
ORDER BY 
    td.UserID, 
    td.Month_sk, 
    td.media 
Смежные вопросы