如何标记最早的重叠记录?

时间:2018-06-19 14:52:51

标签: sql sql-server tsql

我有一个数据集,需要选择并保留没有重叠时间范围的记录,而对于那些有重叠时间的记录,则保留最早的记录。

我已经能够使用以下代码成功选择没有重叠时间范围的记录:

IF OBJECT_ID('tempdb..#overlaps') IS NOT NULL DROP Table #overlaps
SELECT 
     CASE WHEN EXISTS(SELECT 1 FROM #service r2 
                     WHERE r2.client_ID = r1.client_ID
                       AND r2.service_ID <> r1.service_ID
                       AND r1.service_start_date <= r2.service_end_date
                       AND r2.service_start_date <= r1.service_end_date) 
        THEN 1
        ELSE 0
   END AS Overlap
   ,*
  into #overlaps
  FROM #services r1

这将生成以下示例客户端:

Overlap client_ID   service_ID service_start_date service_end_date   
1        12345       123         27-Oct-2009         03-Jan-2013    
1        12345       124         27-Dec-2012         19-Mar-2013    
1        12345       125         18-Mar-2013         04-Jun-2014    
1        12345       126         29-Jun-2014         28-Apr-2017    
1        12345       127         23-Jun-2014         14-Aug-2014    
1        12345       128         27-Apr-2015         07-Nov-2015    
1        12345       129         01-Aug-2015         01-Dec-2015    
0        12345       132         01-Jul-2017         09-Dec-2017    
0        12345       133         02-Jan-2018         20-Jan-2018    
0        12345       134         03-May-2018         05-Jun-2018    

我想做的是在重叠= 1的情况下,如果该记录是重叠“集合”的第一条记录,则在标记上添加一列,就开始日期而言是第一条。 service_ID实际上不是顺序的,我只是将其替换为虚拟数据。

因此,在上述情况下,记录#1应标记为1,因为与重叠的服务记录#2相比,记录#1具有最早的服务开始,因此记录#2将标记为0,相同记录#3(即标记为0)。继续,记录#4应该标记为1,因为它与下面的记录重叠。

关于最终产品,我最终只想显示任何不重叠的期间,以及确实重叠的记录的最早/第一条记录,因此在上述情况下,记录#1、4、8、9,剩下的10个将被删除。不过,每条记录都应保留为自己的记录,不应将其“透视”为连续的记录。

换句话说,我需要标记的是最早的记录,该记录开始于并行发生多个活动服务的地方。

编辑:

例如,客户有4个服务:服务A从1月1日开始-7月31日,服务B从2月1日开始的8月1日结束,服务C从9月1日开始的10月1日结束,服务D从11月1日开始的12月1日结束...服务A应该标记为1,将在服务A仍处于活动状态时启动的服务B标记为0,将在没有任何服务处于活动状态的情况下启动服务C的标记为1,与服务D相同。

3 个答案:

答案 0 :(得分:0)

我认为标记是:

SELECT (CASE WHEN NOT EXISTS (SELECT 1
                              FROM #service r2 
                              WHERE r2.client_ID = r1.client_ID AND
                                    r2.service_ID <> r1.service_ID AND
                                    r1.service_start_date <= r2.service_end_date AND
                                    r2.service_start_date < r1.service_end_date
                             ) 
             THEN 1
             ELSE 0
        END) AS First_Overlap;

注意:

  • 这实际上并不检查重叠。我忽略了这一点,因为您可以使用overlaps标志进行检查,也可以包含exists查询。
  • 开始日期的重叠检查的唯一区别是<<=
  • 当重叠期有多个同时开始的记录时,这可能无法按您期望的方式工作。

此外,我怀疑您正在尝试解决差距与岛屿的问题。不需要使用多个临时表,也不需要使用所使用的逻辑。您可能要问有关您要解决的整个问题的另一个问题,而不是这个方面。

答案 1 :(得分:0)

UPDATE #overlaps SET IsFirst=1
FROM
(SELECT overlap, client_id client_id, service_start_date service_start_date, service_end_date service_end_date, min(service_id) service_id
 FROM #overlaps
 WHERE overlap=1
 group by overlap, client_id, service_start_date, service_end_date) a
 where #overlaps.client_id = a.client_id and #overlaps.service_id = a.service_id

编辑

@ marshymell0-我想我正在理解你想要的。将其写为查询非常棘手,所以我改用游标。在我有PRINT @service_start_date_prev行的部分中,您将更新标志列,该标志列确定记录是否是重叠集中的第一条记录。

DECLARE @overlap_prev int, @client_id_prev int, @service_id_prev int
DECLARE @overlap_next int, @client_id_next int, @service_id_next int
DECLARE @service_start_date_prev datetime, @service_end_date_prev datetime
DECLARE @service_start_date_next datetime, @service_end_date_next datetime
DECLARE @part_of_set int = 0
DECLARE o_cursor CURSOR  
FOR SELECT overlap, client_id, service_id, service_start_date, service_end_date 
FROM #overlaps where overlap=1
ORDER BY service_start_date
OPEN o_cursor  

FETCH NEXT FROM o_cursor
INTO @overlap_next, @client_id_next, @service_id_next, @service_start_date_next, @service_end_date_next  

WHILE @@FETCH_STATUS = 0  
BEGIN  

    IF (@service_start_date_prev IS NOT NULL)
    BEGIN
        IF (@part_of_set = 0 AND @service_start_date_prev <= @service_end_date_next AND @service_start_date_next <= @service_end_date_prev)
        BEGIN
            PRINT @service_start_date_prev
            SET @part_of_set = 1
        END
        ELSE
            SET @part_of_set = 0
    END

    SET @overlap_prev = @overlap_next
    SET @client_id_prev = @client_id_next
    SET @service_id_prev = @service_id_next
    SET @service_start_date_prev = @service_start_date_next
    SET @service_end_date_prev = @service_end_date_next

    FETCH NEXT FROM o_cursor
    INTO @overlap_next, @client_id_next, @service_id_next, @service_start_date_next, @service_end_date_next  
END

CLOSE o_cursor;  
DEALLOCATE o_cursor;  

答案 2 :(得分:0)

很难在此处阅读确切的目标,但是如果您希望基于service_start_date进行标记,则Overlap = 1即可。

;WITH CTE (Overlap, client_ID, service_ID, service_start_date) AS (
    SELECT * FROM (
        VALUES
            ('1','12345','123','10/27/2009'), 
            ('1','12345','124','12/27/2012'), 
            ('1','12345','125','3/18/2013'), 
            ('1','12345','126','6/29/2014'), 
            ('1','12345','127','6/23/2014'), 
            ('1','12345','128','4/27/2015'), 
            ('1','12345','129','8/1/2015'), 
            ('0','12345','132','7/1/2017'), 
            ('0','12345','133','1/2/2018'), 
            ('0','12345','134','5/3/2018')
    ) AS A (Overlap, client_ID, service_ID, service_start_date)
)

SELECT CTE.Overlap,
    CTE.client_ID,
    CTE.service_ID,
    CTE.service_start_date,
    t2.Result
FROM CTE
LEFT JOIN (
    SELECT '1' AS Result,
        t2.client_ID,
        MIN(t2.service_start_date) AS service_start_date
    FROM CTE t2
    WHERE t2.Overlap = '1'
    GROUP BY client_ID
    ) t2 ON CTE.client_ID = t2.client_ID
    AND CTE.service_start_date = t2.service_start_date
ORDER BY service_ID

除了用Overlap标记第一个service_start_date之外,这还不算什么。例如,如果您想将那些不是第一个标记为0的标记,则需要添加。