当 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
子句:它优先考虑结束日期为空的行,然后是结束日期不等于开始日期的行,然后是最大的结束日期。可以单独运行子查询,看看行号是怎么赋值的。
有了这些信息,剩下要做的就是过滤每组排名靠前的记录。
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_Number
和 Inequakity 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 但想法与之前相同。
或多或少我坚持这个想法。
我有一个客户数据库,这些客户的会员资格的生效日期和结束日期都是单独的列。然而,数据有点脏,一个客户可以有多行数据,其中只有一行是他们最近的会员记录。如果成员的结束日期为 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
子句:它优先考虑结束日期为空的行,然后是结束日期不等于开始日期的行,然后是最大的结束日期。可以单独运行子查询,看看行号是怎么赋值的。
有了这些信息,剩下要做的就是过滤每组排名靠前的记录。
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_Number
和 Inequakity 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 但想法与之前相同。
或多或少我坚持这个想法。