查找在 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 中拥有多个帐户的客户的真正开始结束日期的主要内容,如果未能解决你的问题,请参考以下文章

在没有 VIEW SERVER STATE 权限的情况下查找 SQL Server 2014 中最常访问的表

如何从 SQL Server 中的同一列中查找多个最大值

SQL Server - 包含查询

在 SQL Server 数据库中查找分区架构定义

SQL sever 查找的结果如何判断是不是为空

SQL sever 查找的结果如何判断是不是为空