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

Sql server 2008 如何识别T-SQL中每个不同成员的多个开始日期和结束日期范围中的第一个间隔,sql-server-2008,tsql,gaps-and-islands,Sql Server 2008,Tsql,Gaps And Islands,我一直在做下面的工作,但没有得到任何结果,最后期限很快就要到了。此外,还有超过一百万行,如下所示。感谢您在以下方面的帮助 目标:按成员对结果进行分组,并通过组合相互重叠或连续运行的各个日期范围,为每个成员建立连续的覆盖范围,在范围的开始和结束日期之间没有中断 我有以下格式的数据: MemberCode ----- ClaimID ----- StartDate ----- EndDate 00001 ----- 012345 -----

我一直在做下面的工作,但没有得到任何结果,最后期限很快就要到了。此外,还有超过一百万行,如下所示。感谢您在以下方面的帮助

目标:按成员对结果进行分组,并通过组合相互重叠或连续运行的各个日期范围,为每个成员建立连续的覆盖范围,在范围的开始和结束日期之间没有中断

我有以下格式的数据:

MemberCode  -----   ClaimID   -----       StartDate   -----       EndDate
00001   -----       012345   -----       2010-01-15   -----       2010-01-20
00001   -----       012350   -----       2010-01-19   -----       2010-01-22
00001   -----       012352   -----       2010-01-20   -----       2010-01-25
00001   -----       012355   -----       2010-01-26   -----       2010-01-30
00002   -----       012357   -----       2010-01-20   -----       2010-01-25
00002   -----       012359   -----       2010-01-30   -----       2010-02-05
00002   -----       012360   -----       2010-02-04   -----       2010-02-15
00003   -----       012365   -----       2010-02-15   -----       2010-02-30

在上面的示例中,成员00001是有效成员,因为存在从2010-01-15到2010-01-30的连续日期范围,没有空白。请注意,该成员的索赔ID 012355在索赔ID 012352的结束日期之后立即开始。这仍然有效,因为它形成了一个连续的范围

但是,成员00002应该是无效成员,因为在索赔ID 012357的结束日期和索赔ID 012359的开始日期之间有5天的间隔

我想做的是得到一个列表,其中只列出那些对每个成员的连续日期范围内的每一天有索赔权的成员,每个不同成员的MINStart日期和MaxEnd日期之间没有间隔。有间隙的成员将被丢弃

提前谢谢

更新:

我一直到这里。 注:填写日期=开始日期,预发现日期=结束日期

上面的代码似乎有错误,因为我只得到一行,这也是不正确的。我想要的输出只有一列,如下所示:

有效的\u成员\u代码

00001

00007

00009

。。。等等,试试这个:

输出:

| MEMBERCODE |  STARTDATE |    ENDDATE | NEXTSTARTDATE | GAP |
--------------------------------------------------------------
|          1 | 2010-01-15 | 2010-01-20 |    2010-01-19 |  -1 |
|          1 | 2010-01-19 | 2010-01-22 |    2010-01-20 |  -2 |
|          1 | 2010-01-20 | 2010-01-25 |    2010-01-26 |   1 |
|          2 | 2010-01-20 | 2010-01-25 |    2010-01-30 |   5 |
|          2 | 2010-01-30 | 2010-02-05 |    2010-02-04 |  -1 |
| MEMBERCODE | COUNT | GAPLESS_COUNT |
--------------------------------------
|          1 |     3 |             3 |
|          2 |     2 |             1 |
| MEMBERCODE |
--------------
|          1 |
| MEMBERCODE |  STARTDATE |    ENDDATE | NEXTSTARTDATE |    GAP |
-----------------------------------------------------------------
|          1 | 2010-01-15 | 2010-01-20 |    2010-01-19 |     -1 |
|          1 | 2010-01-19 | 2010-01-22 |    2010-01-20 |     -2 |
|          1 | 2010-01-20 | 2010-01-25 |    2010-01-26 |      1 |
|          1 | 2010-01-26 | 2010-01-30 |        (null) | (null) |
|          2 | 2010-01-20 | 2010-01-25 |    2010-01-30 |      5 |
|          2 | 2010-01-30 | 2010-02-05 |    2010-02-04 |     -1 |
|          2 | 2010-02-04 | 2010-02-15 |        (null) | (null) |
|          3 | 2010-02-15 | 2010-03-02 |        (null) | (null) |
| MEMBERCODE |
--------------
|          1 |
|          3 |
然后检查一个成员是否有相同的索赔数量,且其索赔总额没有差距:

with s as
(
  select *, row_number() over(partition by membercode order by startdate) rn
  from tbl
)
,gaps as
(
select a.membercode, a.startdate, a.enddate, b.startdate as nextstartdate
  ,datediff(d, a.enddate, b.startdate) as gap
from s a
join s b on b.membercode = a.membercode and b.rn = a.rn + 1
)
select membercode, count(*) as count, sum(case when gap <= 1 then 1 end) as gapless_count
from gaps
group by membercode;
最后,过滤他们,在他们的声明中没有空白的成员:

with s as
(
  select *, row_number() over(partition by membercode order by startdate) rn
  from tbl
)
,gaps as
(
select a.membercode, a.startdate, a.enddate, b.startdate as nextstartdate
  ,datediff(d, a.enddate, b.startdate) as gap
from s a
join s b on b.membercode = a.membercode and b.rn = a.rn + 1
)
select membercode 
from gaps
group by membercode
having sum(case when gap <= 1 then 1 end) = count(*);
请注意,您不需要执行COUNT*>1来检测具有2个或更多声明的成员。我们使用的不是左连接,而是连接,这将自动丢弃尚未进行第二次声明的成员。如果您选择使用LEFT JOIN而不是与上面相同的输出,则版本更为持久:

with s as
(
select *, row_number() over(partition by membercode order by startdate) rn
from tbl
)
,gaps as
(
select a.membercode, a.startdate, a.enddate, b.startdate as nextstartdate
,datediff(d, a.enddate, b.startdate) as gap
from s a
left join s b on b.membercode = a.membercode and b.rn = a.rn + 1
)
select membercode 
from gaps
group by membercode
having sum(case when gap <= 1 then 1 end) = count(gap)
and count(*) > 1; -- members who have two ore more claims only
输出:

| MEMBERCODE |  STARTDATE |    ENDDATE | NEXTSTARTDATE | GAP |
--------------------------------------------------------------
|          1 | 2010-01-15 | 2010-01-20 |    2010-01-19 |  -1 |
|          1 | 2010-01-19 | 2010-01-22 |    2010-01-20 |  -2 |
|          1 | 2010-01-20 | 2010-01-25 |    2010-01-26 |   1 |
|          2 | 2010-01-20 | 2010-01-25 |    2010-01-30 |   5 |
|          2 | 2010-01-30 | 2010-02-05 |    2010-02-04 |  -1 |
| MEMBERCODE | COUNT | GAPLESS_COUNT |
--------------------------------------
|          1 |     3 |             3 |
|          2 |     2 |             1 |
| MEMBERCODE |
--------------
|          1 |
| MEMBERCODE |  STARTDATE |    ENDDATE | NEXTSTARTDATE |    GAP |
-----------------------------------------------------------------
|          1 | 2010-01-15 | 2010-01-20 |    2010-01-19 |     -1 |
|          1 | 2010-01-19 | 2010-01-22 |    2010-01-20 |     -2 |
|          1 | 2010-01-20 | 2010-01-25 |    2010-01-26 |      1 |
|          1 | 2010-01-26 | 2010-01-30 |        (null) | (null) |
|          2 | 2010-01-20 | 2010-01-25 |    2010-01-30 |      5 |
|          2 | 2010-01-30 | 2010-02-05 |    2010-02-04 |     -1 |
|          2 | 2010-02-04 | 2010-02-15 |        (null) | (null) |
|          3 | 2010-02-15 | 2010-03-02 |        (null) | (null) |
| MEMBERCODE |
--------------
|          1 |
|          3 |
编辑需求澄清:

在您的澄清中,您希望包括尚未提出第二次索赔的成员,请改为:

该技术是计算成员的下一个开始日期,如果他们没有下一个开始日期,例如。countnextstartdate=0则它们仅为单一索赔且也有效,然后只需附加以下或条件:

or count(nextstartdate) = 0; 
实际上,下面的条件也足够了,不过我想让查询更加自我记录,因此我建议使用成员的nextstartdate。以下是计算尚未获得第二次索赔的成员的另一个条件:

or count(*) = 1;
顺便说一句,我们还必须改变这个比较:

sum(case when gap <= 1 then 1 end) = count(*)

试试这个,它会按MemberCode对行进行分区,并给它们序号。然后将行与后续的num值进行比较,如果一行的结束日期和下一行的开始日期之间的差值大于一天,则为无效成员:

DECLARE @t TABLE (MemberCode  VARCHAR(100), ClaimID   
    INT,StartDate   DATETIME,EndDate DATETIME)
INSERT @t
VALUES
('00001'   ,       012345   ,        '2010-01-15'   ,       '2010-01-20')
,('00001'   ,       012350   ,       '2010-01-19'   ,       '2010-01-22')
,('00001'   ,       012352   ,       '2010-01-20'   ,       '2010-01-25')
,('00001'   ,       012355   ,       '2010-01-26'   ,       '2010-01-30')
,('00002'   ,       012357   ,       '2010-01-20'   ,       '2010-01-25')
,('00002'   ,       012359   ,       '2010-01-30'   ,       '2010-02-05')
,('00002'   ,       012360   ,       '2010-02-04'   ,       '2010-02-15')
,('00003'   ,       012365   ,       '2010-02-15'   ,       '2010-02-28')
,('00004'   ,       012366   ,       '2010-03-18'   ,       '2010-03-23')
,('00005'   ,       012367   ,       '2010-03-19'   ,       '2010-03-25')
,('00006'   ,       012368   ,       '2010-03-20'   ,       '2010-03-21')

;WITH tbl AS (

    SELECT  *,
            ROW_NUMBER() OVER (PARTITION BY MemberCode ORDER BY StartDate) 
                AS num
    FROM    @t
), invalid AS (

    SELECT  tbl.MemberCode
    FROM    tbl
    JOIN    tbl _tbl ON 
            tbl.num = _tbl.num - 1
    AND     tbl.MemberCode = _tbl.MemberCode
    WHERE   DATEDIFF(DAY, tbl.EndDate, _tbl.StartDate) > 1  
)

SELECT  MemberCode
FROM    tbl
EXCEPT
SELECT  MemberCode
FROM    invalid

我认为您的查询返回的是假阴性,因为它只检查连续行之间的时间间隔。在我看来,有可能通过前面的一条线路来补偿间隙。让我举一个例子:

l行:2010-01-01 | 2010-01-31 第2行:2010-01-10 | 2010-01-15 第3行:2010-01-20 | 2010-01-25

代码将报告第2行和第3行之间的间隙,而第1行正在填充该间隙。你的代码不会检测到这一点。 您应该在DATEDIFF函数中使用前面所有行的MAXEndDate

DECLARE @t TABLE (PersonID  VARCHAR(100), StartDate DATETIME, EndDate DATETIME)
INSERT @t VALUES('00001'   ,       '2010-01-01'   ,       '2010-01-17')
INSERT @t VALUES('00001'   ,       '2010-01-19'   ,       '2010-01-22')
INSERT @t VALUES('00001'   ,       '2010-01-20'   ,       '2010-01-25')
INSERT @t VALUES('00001'   ,       '2010-01-26'   ,       '2010-01-31')
INSERT @t VALUES('00002'   ,       '2010-01-20'   ,       '2010-01-25')
INSERT @t VALUES('00002'   ,       '2010-02-04'   ,       '2010-02-05')
INSERT @t VALUES('00002'   ,       '2010-02-04'   ,       '2010-02-15')
INSERT @t VALUES('00003'   ,       '2010-02-15'   ,       '2010-02-28')
INSERT @t VALUES('00004'   ,       '2010-03-18'   ,       '2010-03-23')
INSERT @t VALUES('00005'   ,       '2010-03-19'   ,       '2010-03-25')
INSERT @t VALUES('00006'   ,       '2010-01-01'   ,       '2010-04-20')
INSERT @t VALUES('00006'   ,       '2010-01-20'   ,       '2010-01-21')
INSERT @t VALUES('00006'   ,       '2010-01-25'   ,       '2010-01-26')

;WITH tbl AS (
    SELECT  
        *, ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY StartDate) AS num
    FROM    @t
), invalid AS (
    SELECT  tbl.PersonID 
    FROM    tbl
    JOIN    tbl _tbl ON 
            tbl.num = _tbl.num - 1 AND tbl.PersonID = _tbl.PersonID
    WHERE DATEDIFF(DAY, (SELECT MAX(tbl3.EndDate) FROM tbl tbl3 WHERE tbl3.num <= tbl.num AND tbl3.PersonID = tbl.PersonID), _tbl.StartDate) > 1  
)

SELECT  PersonID
FROM    tbl
EXCEPT
SELECT  PersonID
FROM    invalid

看看这里:这里讨论的类似主题可能重复:@Chris Gessler-如果下面的问题很幼稚,我很抱歉。然而,我的要求是针对每个不同的成员。如何使用链接中的步骤查找每个不同成员的日期范围?谢谢。@Vijay-如果您想查找无效的成员代码,您可以自行加入并查找t1.EndDate>t2.StartDate。要查找所有日期间隔,您必须对结果进行分区。您使用SQL Server是偶然的吗?非常感谢Michael!更新后的查询从总共167万个索赔中返回37052个有效成员。查询进程和您对步骤的解释非常有教育意义:-嗨,Michael,因为我还需要尚未拥有第二个声明的成员。换句话说,具有单个声明的成员在我的情况下是有效的,根据您在上面末尾的注释,我使用了左连接而不是连接。但是,我得到了0个结果。想知道我是否遗漏了什么吗?@Vijay回答根据您澄清的要求进行了修改ツ谢谢,迈克尔:-我一直在想为什么我得了0分。当我随机检查成员时,修改后的代码确实给了我想要的输出。另外,感谢您的详细解释和查询进度!谢谢Ivan,我已经在上面执行Michael代码的同一个表上执行了查询。然而,结果表明
我彻底失败了。当您的代码返回112077个不同成员的328256个声明时,Michael的代码返回37052个有效成员。感谢您的尝试,这是一个有趣的结果。如果您有任何查询未能排除无效成员的示例数据,请发布它,我很好奇它在哪里失败。@Vijay我相信我发现my和Michael的查询之间有一个区别,我的查询不排除某些行,因为我相信它们是有效的,这些行对于给定的MemberCode只有一次出现,我在示例输入数据中添加了两个;0004, 0005, 0006. 您认为这些行是有效的还是无效的?是的,只有一个声明的成员代码是一个有效的成员。在这种情况下,这个查询将返回这些成员,这可能是它返回更多成员的原因。
DECLARE @t TABLE (PersonID  VARCHAR(100), StartDate DATETIME, EndDate DATETIME)
INSERT @t VALUES('00001'   ,       '2010-01-01'   ,       '2010-01-17')
INSERT @t VALUES('00001'   ,       '2010-01-19'   ,       '2010-01-22')
INSERT @t VALUES('00001'   ,       '2010-01-20'   ,       '2010-01-25')
INSERT @t VALUES('00001'   ,       '2010-01-26'   ,       '2010-01-31')
INSERT @t VALUES('00002'   ,       '2010-01-20'   ,       '2010-01-25')
INSERT @t VALUES('00002'   ,       '2010-02-04'   ,       '2010-02-05')
INSERT @t VALUES('00002'   ,       '2010-02-04'   ,       '2010-02-15')
INSERT @t VALUES('00003'   ,       '2010-02-15'   ,       '2010-02-28')
INSERT @t VALUES('00004'   ,       '2010-03-18'   ,       '2010-03-23')
INSERT @t VALUES('00005'   ,       '2010-03-19'   ,       '2010-03-25')
INSERT @t VALUES('00006'   ,       '2010-01-01'   ,       '2010-04-20')
INSERT @t VALUES('00006'   ,       '2010-01-20'   ,       '2010-01-21')
INSERT @t VALUES('00006'   ,       '2010-01-25'   ,       '2010-01-26')

;WITH tbl AS (
    SELECT  
        *, ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY StartDate) AS num
    FROM    @t
), invalid AS (
    SELECT  tbl.PersonID 
    FROM    tbl
    JOIN    tbl _tbl ON 
            tbl.num = _tbl.num - 1 AND tbl.PersonID = _tbl.PersonID
    WHERE DATEDIFF(DAY, (SELECT MAX(tbl3.EndDate) FROM tbl tbl3 WHERE tbl3.num <= tbl.num AND tbl3.PersonID = tbl.PersonID), _tbl.StartDate) > 1  
)

SELECT  PersonID
FROM    tbl
EXCEPT
SELECT  PersonID
FROM    invalid