Получить строку данных с самой последней датой, когда другая строка <> 'X' в T- SQL - PullRequest
2 голосов
/ 29 марта 2020

У меня есть база данных клиентов, у которых есть дата вступления в силу и дата окончания их членства, оба отдельных столбца. Однако данные немного грязные, и у клиента может быть несколько строк данных, только одна из которых является самой последней записью о членстве. Член считается «активным», если у него конечная дата = NULL.

Данные выглядят примерно так:

Name         ID          Membership_Effective_Date     Membership_End_Date
---------------------------------------------------------------------------
Bob           1                 1/1/2020                           NULL
Bob           1                 1/1/2017                           1/2/2017
Bob           1                 1/1/2017                           9/1/2018
Kim           2                 1/1/2019                           1/1/2020
Kim           2                 1/1/2019                           12/31/2019
Susan         3                 1/1/2018                           12/31/2018
Susan         3                 1/1/2019                           1/1/2019
Larry         4                 1/1/2020                           1/1/2020

Мне нужно получить самую последнюю дату окончания членства для списка неактивных и активных клиентов.

Мои желаемые результаты должны выглядеть следующим образом:

Name ID Membership_Effective_Date Membership_End_Date Bob 1 1/1/2020 NULL Kim 2 1/1/2019 1/1/2020 Susan 3 1/1/2018 12/31/2018 Larry 4 1/1/2020 1/1/2020

Я смог сделать это без проблем для клиентов, у которых есть строка с датой Membership_End_Date value и строка Membership_End_Date со значением NULL (Боб), а также клиенты, у которых есть несколько строк только с датой (Ким).

Проблема, с которой я сталкиваюсь, связана с такими данными, как Сьюзен и Ларри. У них обоих есть строки, которые содержат значения даты, где Membership_Effective_Date = Membership_End_Date. В случае Ларри это единственный ряд данных, который у него есть. А в случае Сьюзен даты в строке, где Membership_Effective_Date = Membership_End_Date, больше, чем в другой строке, поэтому мой текущий запрос подберет его автоматически.

Проблема заключается в том, что мне нужно написать запрос, который говорит, если у клиента несколько строк данных и одна строка, где Membership_Effective_Date = Membership_End_Date затем выбрал вторую самую последнюю строку данных. Однако, если у клиента есть только одна строка данных, и эта строка содержит только те значения, в которых Membership_Effective_Date = Membership_End_Date выберите эту.

Я не могу понять, как это сделать, не удалив Ларри из извлечения данных полностью и мне нужно включить его и аналогичных клиентов.

Любая помощь приветствуется!

Ответы [ 2 ]

2 голосов
/ 02 апреля 2020

Вы можете сделать это с помощью row_number() и условной сортировки:

select name, id, membership_effective_date, membership_end_date
from (
    select 
        t.*,
        row_number() over(
            partition by id 
            order by
                case when membership_end_date is null then 0 else 1 end,
                case when membership_end_date <> membership_effective_date then 0 else 1 end,
                membership_end_date desc
        ) rn
    from mytable t
) t
where rn = 1

Хитрость заключается в предложении order by row_number(): оно отдает приоритет строкам, конечная дата которых равна нулю, затем к строкам, чья дата окончания не равна дате начала, затем к величайшей дате окончания. Вы можете запустить подзапрос отдельно, чтобы увидеть, как присваивается номер строки.

Имея эту информацию под рукой, все, что остается сделать, - это отфильтровать записи с самым высоким рейтингом для каждой группы.

Демонстрация на DB Fiddle :

name  | id | membership_effective_date | membership_end_date
:---- | -: | :------------------------ | :------------------
Bob   |  1 | 2020-01-01                | <em>null</em>               
Kim   |  2 | 2019-01-01                | 2020-01-01         
Susan |  3 | 2018-01-01                | 2018-12-31         
Larry |  4 | 2020-01-01                | 2020-01-01         
0 голосов
/ 02 апреля 2020

Интересно, что заставляет вас думать, что ваш код лучше

Прежде всего, при должном уважении, без обид.

order by
    case when membership_end_date is null then 0 else 1 end,
    case when membership_end_date <> membership_effective_date then 0 else 1 end,
      membership_end_date desc

Я понятия не имею, как выглядят реальные данные.

Я буду избегать Row_Number и Inequakity Operator, если у меня будет много строк для обработки.

Inequakity Operator часто Сканирование полной таблицы, чтобы проверить состояние Inequakity. Я в этом уверен.

Это тоже Inequakity Operator в Order by предложении вместе с описанием дела и Row_Number.

Это может сокрушить Sql Optimizer.

Я не говорю, что всегда избегаю Row_Number

Также вы ничего не упомянули о Membership_Effective_Date

Попробуйте приведенный ниже скрипт с различными примерами данных,

    create table customers1(Name varchar(40),  ID int
    , Membership_Effective_Date datetime, Membership_End_Date datetime)
    insert into customers1 values
    ('Bob',           1    ,'2020-01-01' ,       NULL)
    ,('Bob',           1    ,'2017-01-01' ,     '1/2/2017')
    ,('Bob',           1    ,'2017-01-01' ,   '9/1/2018')
    ,('Kim',           2  ,'2019-01-01' ,   '1/1/2020')
    ,('Kim',           2   ,'2019-01-01' ,  '12/31/2019')
    ,('Susan',         3  ,'2018-01-01' ,  '12/31/2018')
    ,('Susan',         3  ,'2019-01-01' ,  '1/1/2019')
    ,('Larry',         4   ,'2020-01-01' ,   '1/1/2020')

SELECT ID
    ,NAME
    ,Membership_Effective_Date
    ,Membership_End_Date
INTO #temp
FROM customers1
WHERE Membership_End_Date IS NULL
OPTION (MAXDOP 1)

SELECT ID
    ,NAME
    ,Membership_Effective_Date
    ,Membership_End_Date
FROM #temp

UNION ALL

SELECT t.ID
    ,t.NAME
    ,min(t.Membership_Effective_Date) AS Membership_Effective_Date
    ,max(t.Membership_End_Date) AS Membership_End_Date
FROM customers1 t
WHERE Membership_End_Date IS NOT NULL
    AND NOT EXISTS (
        SELECT 1
        FROM #temp ac
        WHERE ac.ID = t.ID
        )
GROUP BY t.ID
    ,t.NAME
OPTION (MAXDOP 1)


drop table #temp
drop table customers1

Да Вы были правы раньше, когда я использовал CTE, он имел бы Scan как минимум дважды.

Теперь я использую #temp таблицу, но идея та же, что и раньше.

Более или менее я придерживаюсь только с этой идеей.

...