查找在 SQL Server 2014 中拥有多个帐户的客户的真正开始结束日期
Posted
技术标签:
【中文标题】查找在 SQL Server 2014 中拥有多个帐户的客户的真正开始结束日期【英文标题】:Find the true start end dates for customers that have multiple accounts in SQL Server 2014 【发布时间】:2020-01-27 20:27:29 【问题描述】:我有一个支票账户表,其中包含 Cust_id
(客户 ID)、Open_Date
(开始日期)和 Closed_Date
(结束日期)列。每个帐户有一行。客户可以在任何给定时间开设多个账户。我想知道此人成为客户多久了。
例如1:
CREATE TABLE [Cust]
(
[Cust_id] [varchar](10) NULL,
[Open_Date] [date] NULL,
[Closed_Date] [date] NULL
)
insert into [Cust] values ('a123', '10/01/2019', '10/15/2019')
insert into [Cust] values ('a123', '10/12/2019', '11/01/2019')
理想情况下,我想将其插入只有一行的表中,表示此人从 2019 年 10 月 1 日到 2019 年 1 月 1 日一直是客户。 (因为他在关闭前一个帐户之前开设了第二个帐户。
类似如 2:
insert into [Cust] values ('b245', '07/01/2019', '09/15/2019')
insert into [Cust] values ('b245', '10/12/2019', '12/01/2019')
我希望在这种情况下看到 2 行 - 一行显示他是 07/01 到 09/15 的客户,然后是 10/12 到 12/01 的客户。
你能告诉我最好的方法吗?
【问题讨论】:
你有没有看过差距和孤岛问题?你累了什么? 【参考方案1】:我会将其视为差距和孤岛问题。您希望将周期重叠的相邻行分组在一起。
这是使用lag()
和累积sum()
来解决它的一种方法。每次打开日期大于上一条记录的关闭日期时,就会开始一个新组。
select
cust_id,
min(open_date) open_date,
max(closed_date) closed_date
from (
select
t.*,
sum(case when not open_date <= lag_closed_date then 1 else 0 end)
over(partition by cust_id order by open_date) grp
from (
select
t.*,
lag(closed_date) over (partition by cust_id order by open_date) lag_closed_date
from cust t
) t
) t
group by cust_id, grp
在 this db fiddle 和您的示例数据中,查询会生成:
cust_id |开放日期 |关闭日期 :-------- | :--------- | :---------- a123 | 2019-10-01 | 2019-11-01 b245 | 2019-07-01 | 2019-09-15 b245 | 2019-10-12 | 2019-12-01【讨论】:
这适用于线性帐户进展,但会因更复杂的时间安排而失败。例如。账户 A 于 2010 年开通,永不关闭;账户 B 于 2012 年开设和关闭;帐户 C 于 2014 年开设和关闭。由于您的查询仅与邻居进行比较,因此尽管最旧的帐户 (A) 仍处于打开状态,但 B 和 C 之间的差距将导致一个新组。 @ChrisHep:是的,可能。但是我在 OP 提供的样本数据中没有看到这种情况(尽管我同意 4 条记录不能被视为一个综合数据集),因此这个答案。 你说得很好,我正在努力的解决方案假设数据比帖子中提供的更复杂。我在回答中提到,如果样本数据表明了预期的复杂性,你的更可取。【参考方案2】:我会用递归解决这个问题。虽然这肯定很繁重,但它甚至应该适应最复杂的帐户时间(假设您的数据有这样的时间)。但是,如果提供的示例数据与您需要解决的一样复杂,我强烈建议您坚持使用上面提供的解决方案。它更加简洁明了。
WITH x (cust_id, open_date, closed_date, lvl, grp) AS (
SELECT cust_id, open_date, closed_date, 1, 1
FROM (
SELECT cust_id
, open_date
, closed_date
, row_number()
OVER (PARTITION BY cust_id ORDER BY closed_date DESC, open_date) AS rn
FROM cust
) AS t
WHERE rn = 1
UNION ALL
SELECT cust_id, open_date, closed_date, lvl, grp
FROM (
SELECT c.cust_id
, c.open_date
, c.closed_date
, x.lvl + 1 AS lvl
, x.grp + CASE WHEN c.closed_date < x.open_date THEN 1 ELSE 0 END AS grp
, row_number() OVER (PARTITION BY c.cust_id ORDER BY c.closed_date DESC) AS rn
FROM cust c
JOIN x
ON x.cust_id = c.cust_id
AND c.open_date < x.open_date
) AS t
WHERE t.rn = 1
)
SELECT cust_id, min(open_date) AS first_open_date, max(closed_date) AS last_closed_date
FROM x
GROUP BY cust_id, grp
ORDER BY cust_id, grp
我还要补充一点我不在 SQL Server 上运行的警告,因此可能存在我没有考虑到的语法差异。希望它们是次要的,如果存在的话。
【讨论】:
这个解决方案does work too - 虽然递归是性能杀手,但这将适应比示例数据中显示的更复杂的情况。【参考方案3】:你可以试试这样的:
select distinct
cust_id,
(select min(Open_Date)
from Cust as b
where b.cust_id = a.cust_id and
a.Open_Date <= b.Closed_Date and
a.Closed_Date >= b.Open_Date
),
(select max(Closed_Date)
from Cust as b
where b.cust_id = a.cust_id and
a.Open_Date <= b.Closed_Date and
a.Closed_Date >= b.Open_Date
)
from Cust as a
所以,对于每一行 - 您从所有重叠范围中选择最小和最大日期,稍后不同的过滤掉重复项
【讨论】:
以上是关于查找在 SQL Server 2014 中拥有多个帐户的客户的真正开始结束日期的主要内容,如果未能解决你的问题,请参考以下文章