我需要摆脱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
答案 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 强>