SQL Server 成员资格日期范围

Posted

技术标签:

【中文标题】SQL Server 成员资格日期范围【英文标题】:SQL Server Membership Date Range 【发布时间】:2013-04-04 14:02:38 【问题描述】:

我是 SQL Server 的新手,有一个相当复杂的 SQL 任务,到目前为止我发现的解决方案都不适合我下面的问题。

我有一个数据库表,其中当前包含会员加入信息(摘录如下),其中包含会员支付会员费的每一年/期间的一行;会员可以取消一年/期间的会员资格并在以后重新加入。我们需要将每个连续成员表示为一行,这样下面的提取 1 的内容就变成了提取 2:

提取 1

Member_No    Start_Date                 End_Date
---------    ----------                 --------
10       2010-01-01 00:00:00.000    2011-01-01 00:00:00.000
10       2011-01-01 00:00:00.000    2012-01-01 00:00:00.000
10       2012-01-01 00:00:00.000    2013-01-01 00:00:00.000
10       2013-01-01 00:00:00.000    2014-01-01 00:00:00.000
20       2005-01-01 00:00:00.000    2006-01-01 00:00:00.000
20       2006-01-01 00:00:00.000    2007-01-01 00:00:00.000
20       2007-01-01 00:00:00.000    2008-01-01 00:00:00.000
30       2005-01-01 00:00:00.000    2006-01-01 00:00:00.000
30       2006-01-01 00:00:00.000    2007-01-01 00:00:00.000
30       2007-01-01 00:00:00.000    2008-01-01 00:00:00.000
30       2008-01-01 00:00:00.000    2009-01-01 00:00:00.000
30       2009-01-01 00:00:00.000    2010-01-01 00:00:00.000
30       2010-10-13 00:00:00.000    2011-01-01 00:00:00.000
30       2011-01-01 00:00:00.000    2012-01-01 00:00:00.000
30       2012-01-01 00:00:00.000    2013-01-01 00:00:00.000

我需要用下表替换上表的内容 - 有问题的表中有更多记录,我将非常感谢任何人可以提供的任何助手:

提取 2

Member_No    Start_Date                 End_Date
---------    ----------                 --------
10       2010-01-01 00:00:00.000    2014-01-01 00:00:00.000
20       2005-01-01 00:00:00.000    2008-01-01 00:00:00.000
30       2005-01-01 00:00:00.000    2010-01-01 00:00:00.000
30       2010-10-13 00:00:00.000    2013-01-01 00:00:00.000

【问题讨论】:

【参考方案1】:

这是一种更好的方法(原始方法仍在下方):

with ms as (
      select ms.*,
             (select 1 from Membership ms2 where ms2.Member_no = ms.Member_no and cast(ms2.Start_date as date) = cast(ms.End_date as date)
             ) as LinkedToNext
      from MemberShip ms
     )
select member_no, MIN(start_date) as start_date, MAX(end_date) as end_date
from (select ms.*,
             (select top 1 end_date
              from ms ms2 where ms2.Member_no = ms.Member_No and ms2.LinkedToNext is NULL and ms2.Start_Date >= ms.Start_Date
             order by end_date desc
             ) as grouping
      from ms
     ) ms1
group by Member_no, grouping

CTE 确定某项是否与下一项相关联。然后,分组是第一条记录的结束日期,之后链接到链接链的下一条记录,这始终相同。

回应您关于“不止一行”的评论。这意味着一个成员有两个从同一日期开始的成员资格。如果这是唯一的重叠形式,您可以将其修复为:

with ms as (
      select member_no, start_date, max(end_date) as end_date
             (select 1 from Membership ms2 where ms2.Member_no = ms.Member_no and cast(ms2.Start_date as date) = cast(ms.End_date as date)
             ) as LinkedToNext
      from MemberShip ms
      group by member_no, start_date
     )

比我原来的方法简单得多,我仍然保留在下面:

with ms as (
      select ms.*,
             (select 1 from Membership ms2 where ms2.Member_no = ms.Member_no and ms2.Start_date = ms.End_date
             ) as LinkedToNext,
             (select ms2.End_Date from Membership ms2 where ms2.Member_no = ms.Member_no and ms2.Start_date = ms.End_date
             ) as NextEndDate,
              (select 1 from Membership ms2 where ms2.Member_no = ms.Member_no and ms2.End_date = ms.Start_date
              ) as LinkedToPrev
      from MemberShip ms
     )
select member_no, MIN(start_date) as start_date,
       MAX(coalesce(NextEndDate, End_Date)) as  end_date
from (select ms.*,
             (ROW_NUMBER() over (partition by Member_no order by Start_Date) -
              ROW_NUMBER() over (partition by Member_no, LinkedToNext order by Start_date)
             ) as grouping
      from ms
     ) ms1
where not (LinkedToNext is null and LinkedToPrev = 1)
group by member_no, grouping

这使用相关子查询来确定一个成员资格是否与下一个成员相关联(基于开始日期与结束日期相同——如果您愿意,您实际上可以添加一个捏造因素)。

然后它使用了一个技巧。它按 start_date 为每个成员枚举行。它还根据每个成员是否链接到下一个成员,按开始日期枚举行。对于可以组合在一起的链,差异是恒定的。

最后一步是将其分组以获得最终结果。

一个复杂的问题是获得正确的结束日期。初始代码在计算中不包括链的末端。所以,我从下一条记录中借用了结束日期。然后过滤掉这条结束记录。

【讨论】:

嗨,戈登,感谢您的回复。第一篇文章的结果更接近我想要实现的目标;但是,它包含额外的行 - 您能否帮助更新 SQL 以删除第 2、4、7 和 8 行? (根据我在下面的原始帖子中提供的数据运行您的 sql 的结果)member_no start_date end_date 10 2010-01-01 2014-01-01 10 2013-01-01 2014-01-01 20 2005-01-01 2008-01-01 20 2007-01-01 2008-01-01 30 2005-01-01 2010-01-01 30 2010-10-13 2013-01-01 30 2009-01-01 2010-01-01 30 2012-01-01 2013-01-01 @Babs 。 . .我的错。我在第二个相关子查询中遗漏了order by 很抱歉让您感到痛苦-“更好的方法”确实效果更好。但是,当我对整个数据库/超过 925 行数据运行查询时,出现以下错误:Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression. 上面的错误似乎是由于未在解决方案中使用“JOIN”。我尝试解决这个问题无济于事 - 你能帮忙吗? @Babs 。 . .这意味着您对给定成员具有重叠的成员资格。您可能想就该情况提出另一个问题。它使问题与您当前的问题大不相同(除非我的答案中的简单修复就足够了)。 最新的解决方案工作正常,但是当我引入一个额外的列时,它没有。您能否帮助我需要进行哪些更改以允许结果也按例如分组?会员类型/结果如下?

以上是关于SQL Server 成员资格日期范围的主要内容,如果未能解决你的问题,请参考以下文章

ASP.Net 成员资格 SQL Server

使用 SQL Server 2005 通过 LDAP 访问 Active Directory 角色成员资格

如何使 SQL Server 数据库在附加用户和角色成员资格时继承它们?

部署到 Azure 应用服务时,Asp.net 成员资格提供程序无法连接到本地 SQL Server

如何识别 T-SQL 中每个不同成员的多个开始和结束日期范围中的第一个间隙

sql SQL:更新成员资格密码