查询以删除连续的资格记录

时间:2014-04-16 12:01:28

标签: sql sql-server

我需要摆脱memid的另一条记录中已经存在资格的记录。在下面的例子中,我需要输出只有我提到Y的行。表有memid,effdate,termdate。我只是在Y前面加上我需要的记录作为输出。我们怎么能这样做。谢谢。

    MEMID     EFFDATE     TERMDATE
Y   A1      2012-01-01    2078-12-31              
    A1      2012-02-01    2078-12-31

Y   B1      2007-05-01    2008-12-31              
Y   B1      2009-10-01    2010-04-30

Y   A2      1999-01-01    2078-12-31                 
    A2      2006-01-01    2011-04-28 

    B2      1999-01-01    1999-10-01              
Y   B2      1999-01-01    2000-09-30 
Y   B2      2006-01-01    2006-01-01 
Y   B2      2009-08-01    2078-12-31 

Y   A3      2000-03-01    2009-01-31             
    A3      2002-04-01    2009-01-31 
    A3      2003-01-01    2006-06-30 
    A3      2006-01-01    2009-01-31 
Y   A3      2009-10-01    2010-07-31 
Y   A3      2011-06-01    2012-09-30 
    A3      2011-09-01    2012-09-30 
Y   A3      2013-06-01    2078-12-31 
    A3      2013-07-01    2078-12-31

    B3      1999-01-01    2008-11-30              
Y   B3      1999-01-01    2078-12-31 
    B3      2006-01-01    2008-11-30

2 个答案:

答案 0 :(得分:1)

使用NOT EXISTS选择没有覆盖更大范围的所有范围。然后使用DISTINCT删除重复项。

select distinct memid, effdate, termdate
from mytable
where not exists
(
  select *
  from mytable bigger
  where bigger.memid = mytable.memid
  and
  (
    (bigger.effdate <= mytable.effdate and bigger.termdate > mytable.termdate)
    or
    (bigger.effdate < mytable.effdate and bigger.termdate >= mytable.termdate)
  )
);

答案 1 :(得分:0)

这是Gaps and Islands问题的变体。既然您已经标记了MySQL和SQL-Server,并且没有回答我的问题,要求您澄清哪个,我将为两者提供解决方案。

您的第一步是通过加入数字表将所有范围扩展为连续数据。对于范围中的每个日期,这会将单行变为一行:

SQL Server

SELECT  t.memid, Date = DATEADD(DAY, n.Number, t.EffDate)
FROM    YourTable t
        INNER JOIN Numbers n
            ON n.Number BETWEEN 0 AND DATEDIFF(DAY, t.EffDate, t.TermDate);

<强>的MySQL

SELECT  t.memid, DATE_ADD(t.EffDate, INTERVAL n.Number DAY) AS Date
FROM    YourTable t
        INNER JOIN Numbers n
            ON n.Number BETWEEN 0 AND DATEDIFF(t.EffDate, t.TermDate);

这将改变这一行:

memid   EFFDATE     TERMDATE
A3      2009-10-01  2009-10-05

进入

memid   Date        
A3      2009-10-01  
A3      2009-10-02
A3      2009-10-03
A3      2009-10-04
A3      2009-10-05

如果您没有数字表,then you should probably create one。 (在下面的每个SQL Fiddle中我创建了一个数字表,所以你可以在那里找到这样做的方法。

现在您拥有连续范围,您可以应用适当的间隙和岛屿解决方案。

如果您使用的是SQL Server,则可以使用排名功能来解决此问题:

WITH ContinuousRange AS
(   SELECT  t.memid, 
            d.Date,
            GroupingSet = DATEADD(DAY, -DENSE_RANK() OVER(PARTITION BY memid 
                                                            ORDER BY d.Date), d.Date)
    FROM    T
            INNER JOIN Numbers n
                ON n.Number BETWEEN 0 AND DATEDIFF(DAY, t.EffDate, t.TermDate)
            OUTER APPLY (SELECT Date = DATEADD(DAY, n.Number, t.EffDate)) d
)
SELECT  cr.MemID,
        EffDate = MIN(cr.Date),
        TermDate = MAX(cr.Date)
FROM    ContinuousRange cr
GROUP BY cr.MemID, cr.GroupingSet
ORDER BY cr.MemID, cr.GroupingSet;

<强> Simplified Example on SQL Fiddle

这是基于序列中的末尾数减去序列中的顺序将给出连续范围的常数,例如:

Sequence | OrderInSequence | (Sequence - OrderInSequence)
---------+-----------------+------------------------------
    1    |      1          |            0
    2    |      2          |            0
    3    |      3          |            0
    5    |      4          |            1
    6    |      5          |            1

如您所见,如果序列中存在间隙(3到5之间),则第3列中的值会发生变化,这就是计算列GroupingSet的方式:

GroupingSet = DATEADD(DAY, -DENSE_RANK() OVER(PARTITION BY memid ORDER BY d.Date), d.Date)

然后,当你可以使用thisc列来获得每个连续序列(或岛)的最小值和最大值。

由于MySQL没有排名功能,您需要使用用户定义的变量来模拟它们:

SELECT  MemID, 
        MIN(Date) AS EffDate,
        MAX(Date) AS TermDate
FROM    (   SELECT  t.memid, 
                    @i:= CASE WHEN t.MemID = @m 
                                AND DATE_ADD(t.EffDate, INTERVAL n.Number DAY) 
                                        <= DATE_ADD(@d, INTERVAL 1 DAY) 
                            THEN @i
                            ELSE @i + 1
                        END AS GroupingSet,
                    @m:= t.memid,
                    @d:= DATE_ADD(t.EffDate, INTERVAL n.Number DAY) AS Date
            FROM    t
                    INNER JOIN Numbers n
                        ON n.Number BETWEEN 0 AND DATEDIFF(t.TermDate, t.EffDate)
                    CROSS JOIN (SELECT @M:= '', @i:= 0, @d:= NULL) i
            ORDER BY t.memid, DATE_ADD(t.EffDate, INTERVAL n.Number DAY)
        ) t
GROUP BY MemID, GroupingSet;

<强> Simplified Example on SQL Fiddle

在此处获取分组集是一个更加迭代的过程。数据按MemID和日期排序,然后在每一行,日期值和memid分别存储在变量@d和@m中。如果新行中的memid与@m相同(即仍然在同一组memid中),并且新行中的日期提前1天,或者与@d相同,则分组集不会增加,如果它是一个新的memid,或者该日期比前一个日期提前1天,则它是一个新的“孤岛”,并且分组集会递增。


修改

为了解决内存问题,您可以采用不同的方式处理不同的情况。第一步是删除完全包含在另一个内的任何记录,例如

MemID   EffDate     TermDate
A1      2012-01-01  2078-12-31
A1      2012-02-01  2078-12-31

使用这两个,第二行不是必需的,因为它的日期范围完全包含在第一行中。所以这可以删除(这在CTE中完成,在下面的查询中称为 Filtered )。

第二种方法是删除不需要的范围扩展,所以在上面的例子中我们只剩下:

MemID   EffDate     TermDate
A1      2012-01-01  2078-12-31

这是A1的唯一行,因此没有必要将其扩展到EffDate和TermDate之间的所有日期,然后采取最小值和最大值,我们可以简单地使用EffDate和TermDate就是这样。这是下面查询中UNION ALL下面的查询。

WITH Filtered AS
(   SELECT  MemID, EffDate, TermDate
    FROM    T
    WHERE   NOT EXISTS
            (   SELECT  1
                FROM    T T2
                WHERE   T.MemID = T2.MemID
                AND     T2.EffDate < T.EffDate
                AND     T2.TermDate >= T.TermDate
            )
), ContinuousRange AS
(   SELECT  t.memid, 
            d.Date,
            GroupingSet = DATEADD(DAY, -DENSE_RANK() OVER(PARTITION BY memid 
                                                            ORDER BY d.Date), d.Date)
    FROM    Filtered T
            INNER JOIN Numbers n
                ON n.Number BETWEEN 0 AND DATEDIFF(DAY, t.EffDate, t.TermDate)
            OUTER APPLY (SELECT Date = DATEADD(DAY, n.Number, t.EffDate)) d
    WHERE   EXISTS
            (   SELECT  1
                FROM    Filtered T2
                WHERE   T.MemID = T2.MemID
                AND (   (T2.EffDate > T.EffDate AND T2.EffDate < T.TermDate)
                    OR  (T2.TermDate > T.EffDate AND T2.TermDate < T.TermDate)
                    )
            )
)
SELECT  cr.MemID,
        EffDate = MIN(cr.Date),
        TermDate = MAX(cr.Date), 1
FROM    ContinuousRange cr
GROUP BY cr.MemID, cr.GroupingSet
UNION ALL
SELECT  T.MemID, T.EffDate, T.TermDate, 0
FROM    Filtered T
WHERE   NOT EXISTS
        (   SELECT  1
            FROM    Filtered T2
            WHERE   T.MemID = T2.MemID
            AND (   (T2.EffDate > T.EffDate AND T2.EffDate < T.TermDate)
                OR  (T2.TermDate > T.EffDate AND T2.TermDate < T.TermDate)
                )
        )
ORDER BY MemID, EffDate;

<强> Example on SQL Fiddle