当 T-SQL 中的另一行 <> 'X' 时,检索具有最近日期的一行数据

Retrieve a row of data with the most recent date when another row <> 'X' in T-SQL

我有一个客户数据库,这些客户的会员资格的生效日期和结束日期都是单独的列。然而,数据有点脏,一个客户可以有多行数据,其中只有一行是他们最近的会员记录。如果成员的结束日期为 NULL,则他们被视为 "active"。

数据看起来有点像这样:

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 日期值和 Membership_End_Date 具有 NULL 值(Bob)的行的客户,我已经能够毫无问题地做到这一点,以及客户有多行只有日期值 (Kim)。

我面临的挑战是像 Susan 和 Larry 这样的数据。它们都有包含日期​​值的行,其中 Membership_Effective_Date = Membership_End_Date。在 Larry 的案例中,这是他拥有的唯一一行数据。在 Susan 的例子中,Membership_Effective_Date = Membership_End_Date 行中的日期大于另一行,因此我当前的查询将自动选取它。

问题是我基本上需要编写一个查询,说明如果客户有多行数据和一行 Membership_Effective_Date = Membership_End_Date 然后选择第二行数据.但是,如果客户只有一行数据并且该行仅包含 Membership_Effective_Date = Membership_End_Date 的值,则选择该行。

如果不从数据拉取中完全删除 Larry,我无法弄清楚如何做到这一点,我需要包括他和类似的客户。

感谢任何帮助!

您可以使用 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

诀窍在于row_number()order by子句:它优先考虑结束日期为空的行,然后是结束日期不等于开始日期的行,然后是最大的结束日期。可以单独运行子查询,看看行号是怎么赋值的。

有了这些信息,剩下要做的就是过滤每组排名靠前的记录。

Demo on DB Fiddle:

name  | id | membership_effective_date | membership_end_date
:---- | -: | :------------------------ | :------------------
Bob   |  1 | 2020-01-01                | null               
Kim   |  2 | 2019-01-01                | 2020-01-01         
Susan |  3 | 2018-01-01                | 2018-12-31         
Larry |  4 | 2020-01-01                | 2020-01-01         

wonder what make you think that your code is better

首先,恕我直言,无意冒犯任何人。

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_NumberInequakity Operator

Inequakity Operator 经常扫描完成 table 以检查 Inequakity 条件。 我确定。

Order by 子句和 Row_Number.

中也是如此 Inequakity Operator

这可能会淹没 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 table 但想法与之前相同。

或多或少我坚持这个想法。