2011-08-28 83 views
8

不使用MSSQL或DB2或Oracle。 没有CTE。 无OVERLAP谓词。 无INTERVAL数据类型。 情况:在需要修理的车辆上工作不能开始,直到 所有订购的工件都已收到。 零件在修理开始之前可以订购多次。 我们需要提取该车辆是在“部分持有”SQL查找多个重叠时间间隔所用的时间

因此,对于确定为ID = 1个 部分车辆被勒令(d1)和接收(D2)在4个不同的场合

ID  d1  d2 
    1  8/1 8/8 
    1  8/2 8/6 
    1  8/12 8/14 
    1  8/3 8/10 

8/1        8/8 
    d1        d2 
    |-------------------------------| 
     8/2    8/6     8/12  8/14     
     d1    d2      d1  d2  
      |---------------|      |----------|  
        8/3     8/10 
        d1     d2 
        |---------------------| 
8/1              8/14 
    |---------------------------------------------------------| = 13 days 
             8/10 8/12 
    |--------------------------------------| + |----------| = parts hold = 11 days 
时间

从上面可以看出,开始工作的等待时间(假设8/1为可用于工作的日期为 )为13天。 等待零件的实际时间为11天,这是我们需要从数据中得出的数字 。 实际的日期时间数据将是我们将提取小时的时间戳, 我们在此示例数据中使用了日期以简化演示。 我们正在努力生成一套(而不是psm,而不是udf,不是光标)的解决方案。 TIA

+0

虽然左辅助日历表上的加入可能会有帮助。 –

+0

可能的重复[什么是一个很好的方法来查找一组datepans中的空白?](http://stackoverflow.com/questions/4765495/what-is-a-good-way-to-find-gaps-in -a-set-dates-pans) –

+0

@Brian,这个问题有很大的不同。 OP,你能添加一个视图来协助查询吗? –

回答

4

此SQL语句似乎得到你想要的(t是SAMPE表的表名):

SELECT 
    d.id, 
    d.duration, 
    d.duration - 
    IFNULL(
     (SELECT Sum(timestampdiff(SQL_TSI_DAY, 
            no_hold.d2, 
            (SELECT min(d1) FROM t t4 
            WHERE t4.id = no_hold.id and t4.d1 > no_hold.d2))) 
     FROM (SELECT DISTINCT id, d2 FROM t t1 
       WHERE (SELECT sum(IIF(t1.d2 between t2.d1 and t2.d2, 1, 0)) 
         FROM t t2 WHERE t2.id = t1.id and t2.d2 <> t1.d2) = 0 
      And d2 <> (select max(d2) from t t3 where t3.id = t1.id)) no_hold 
     WHERE no_hold.id = d.id), 
     0) "parts hold" 
FROM 
    (SELECT id, timestampdiff(SQL_TSI_DAY, min(d1), max(d2)) duration 
    FROM t GROUP BY id) d 

外查询获取修理工作的持续时间。复杂子查询计算不等待零件的总天数。它车辆

// 1) The query for finding the starting dates when the vehicle is not waiting for parts, 
// i.e. finding all d2 that is not within any date range where the vehicle is waiting for part. 
// The DISTINCT is needed to removed duplicate starting "no hold" period. 

SELECT DISTINCT id, d2 
FROM t t1 
WHERE (SELECT sum(IIF(t1.d2 between t2.d1 and t2.d2, 1, 0)) from t t2 
     WHERE t2.id = t1.id and t2.d2 <> t1.d2) = 0 AND 
     d2 <> (SELECT max(d2) FROM t t3 WHERE t3.id = t1.id)) 

// 2)天:这是由定位在车辆没有等待零件的开始日期完成,再算上天数,直到它开始再次等待零件不能等待的部分是从上述查询,直到车辆的日期//等待再次部分

timestampdiff(SQL_TSI_DAY, no_hold.d2, (SELECT min(d1) FROM t t4 WHERE t4.id = no_hold.id and t4.d1 > no_hold.d2)) 

结合上面的两个聚集所有这些时期给天车辆不等待零件数量。最后的查询添加了一个额外的条件来计算来自外部查询的每个id的结果。

这可能不是非常有效的非常大的桌子上有很多ID。如果ID限制为一个或少数几个,它应该没问题。

+0

哇,这是美丽的! – jon

+0

已编辑修复重复开始日期为“禁止”期间的问题。 –

+0

Alex:在上面的主代码块中,在 – jon

5

我无法让@Alex W的查询工作。这不是标准的SQL,所以它需要很多重写才能与SQL Server兼容(我可以测试)。但它给了我一些启发,我已经扩展了。


找到不间断等待每一段都开始点:

SELECT DISTINCT 
    t1.ID, 
    t1.d1 AS date, 
    -DATEDIFF(DAY, (SELECT MIN(d1) FROM Orders), t1.d1) AS n 
FROM Orders t1 
LEFT JOIN Orders t2     -- Join for any events occurring while this 
    ON t2.ID = t1.ID     -- is starting. If this is a start point, 
    AND t2.d1 <> t1.d1    -- it won't match anything, which is what 
    AND t1.d1 BETWEEN t2.d1 AND t2.d2 -- we want. 
GROUP BY t1.ID, t1.d1, t1.d2 
HAVING COUNT(t2.ID) = 0 

以及等效的终点:

SELECT DISTINCT 
    t1.ID, 
    t1.d2 AS date, 
    DATEDIFF(DAY, (SELECT MIN(d1) FROM Orders), t1.d2) AS n 
FROM Orders t1 
LEFT JOIN Orders t2 
    ON t2.ID = t1.ID 
    AND t2.d2 <> t1.d2 
    AND t1.d2 BETWEEN t2.d1 AND t2.d2 
GROUP BY t1.ID, t1.d1, t1.d2 
HAVING COUNT(t2.ID) = 0 

n是因为一些常见的天数时间点。起点具有负值,终点具有正值。这样我们可以将它们加起来以获得中间的天数。

span = end - start 
span = end + (-start) 
span1 + span2 = end1 + (-start1) + end2 + (-start2) 

最后,我们只需要添加东西:

SELECT ID, SUM(n) AS hold_days 
FROM (
    SELECT DISTINCT 
     t1.id, 
     t1.d1 AS date, 
     -DATEDIFF(DAY, (SELECT MIN(d1) FROM Orders), t1.d1) AS n 
    FROM Orders t1 
    LEFT JOIN Orders t2 
     ON t2.ID = t1.ID 
     AND t2.d1 <> t1.d1 
     AND t1.d1 BETWEEN t2.d1 AND t2.d2 
    GROUP BY t1.ID, t1.d1, t1.d2 
    HAVING COUNT(t2.ID) = 0 
    UNION ALL 
    SELECT DISTINCT 
     t1.id, 
     t1.d2 AS date, 
     DATEDIFF(DAY, (SELECT MIN(d1) FROM Orders), t1.d2) AS n 
    FROM Orders t1 
    LEFT JOIN Orders t2 
     ON t2.ID = t1.ID 
     AND t2.d2 <> t1.d2 
     AND t1.d2 BETWEEN t2.d1 AND t2.d2 
    GROUP BY t1.ID, t1.d1, t1.d2 
    HAVING COUNT(t2.ID) = 0 
    ORDER BY ID, date 
) s 
GROUP BY ID; 

输入表(订单):

ID d1   d2 
1 2011-08-01 2011-08-08 
1 2011-08-02 2011-08-06 
1 2011-08-03 2011-08-10 
1 2011-08-12 2011-08-14 
2 2011-08-01 2011-08-03 
2 2011-08-02 2011-08-06 
2 2011-08-05 2011-08-09 

输出:

ID hold_days 
1   11 
2   8 

或者,您可以使用存储过程执行此操作。

CREATE PROCEDURE CalculateHoldTimes 
    @ID int = 0 
AS 
BEGIN 
    DECLARE Events CURSOR FOR 
    SELECT * 
    FROM (
     SELECT d1 AS date, 1 AS diff 
     FROM Orders 
     WHERE ID = @ID 
     UNION ALL 
     SELECT d2 AS date, -1 AS diff 
     FROM Orders 
     WHERE ID = @ID 
    ) s 
    ORDER BY date; 

    DECLARE @Events_date date, 
      @Events_diff int, 
      @Period_start date, 
      @Period_accum int, 
      @Total_start date, 
      @Total_count int; 

    OPEN Events; 

    FETCH NEXT FROM Events 
    INTO @Events_date, @Events_diff; 

    SET @Period_start = @Events_date; 
    SET @Period_accum = 0; 
    SET @Total_start = @Events_date; 
    SET @Total_count = 0; 

    WHILE @@FETCH_STATUS = 0 
    BEGIN 
     SET @Period_accum = @Period_accum + @Events_diff; 

     IF @Period_accum = 1 AND @Events_diff = 1 
      -- Start of period 
      SET @Period_start = @Events_date; 
     ELSE IF @Period_accum = 0 AND @Events_diff = -1 
      -- End of period 
      SET @Total_count = @Total_count + 
       DATEDIFF(day, @Period_start, @Events_date); 

     FETCH NEXT FROM Events 
     INTO @Events_date, @Events_diff; 
    END; 

    SELECT 
     @Total_start AS d1, 
     @Events_date AS d2, 
     @Total_count AS hold_time; 
END; 

与调用它:

EXEC CalculateHoldTimes 1; 
+0

谢谢MizardX,这正是我们正在寻找的 – jon

+0

对不起,我没有足够的点击你的答案 – jon

+0

现在你可以。 :)无论如何;如果您认为它能回答您的问题,您仍然可以接受答案。点击投票箭头下方的复选标记。 –

0
USE [DnnMasterShoraSystem] 
GO 
/****** Object: StoredProcedure [dbo].[CalculateHoldTimes] Script Date: 12/8/2014 1:36:12 PM ******/ 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 

ALTER PROCEDURE [dbo].[CalculateHoldTimes] 
    @PID int  
AS 
BEGIN  
CREATE TABLE #tblTemp(
    [ID] [int] NOT NULL, 
    [PID] [int] NOT NULL, 
    [BID] [int] NOT NULL, 
    [Active] [bit] NULL, 
    [WorkStartDate] [nvarchar](10) NULL, 
    [WorkEndDate] [nvarchar](10) NULL, 
    [jobStateID] [int] NULL, 
    [RegisterType] [int] NULL, 
    [RegisterState] [int] NULL, 
    [En_time] [datetime] NULL, 
    [Fa_time] [nvarchar](40) NULL, 
    [Status] [nvarchar](100) NULL, 
    [PortalId] [int] NULL, 
    [ModuleId] [int] NULL, 
    [UserId] [int] NULL, 
    [BrName] [nvarchar](150) NULL, 
    [BrCode] [nvarchar](20) NULL, 
    [WorkEndDate_New] [nvarchar](10) NULL 
) ON [PRIMARY] 

insert into #tblTemp 
select * from [dbo].[Shora.Personel_Branch_Copy] 
     where WorkStartDate is not null 
     --and [dbo].[ShamsiToMiladi](WorkStartDate) <GETDATE() 
     --and [dbo].[ShamsiToMiladi](WorkEndDate) <GETDATE() 
     and [email protected] 
     --and [dbo].[ShamsiToMiladi](WorkEndDate)<[dbo].[ShamsiToMiladi](@NewDate) 
     order by WorkStartDate 

DECLARE Events CURSOR FOR 
    SELECT [dbo].[ShamsiToMiladi](WorkStartDate) AS StartDate,[dbo].[ShamsiToMiladi](WorkEndDate) AS EndDate 
     FROM #tblTemp   
    ORDER BY StartDate; 

--drop table #tblTemp 

    DECLARE @SDate date, 
      @EDate date, 
      @Period_Start date, 
      @Period_End date, 
      @Total int, 
      @OldSDate date, 
      @OldEDate date 


    OPEN Events; 

    FETCH NEXT FROM Events 
    INTO @SDate, @EDate; 

    set @Total=0 
    SET @Period_Start [email protected] 
    set @[email protected] 

    WHILE @@FETCH_STATUS = 0 
    BEGIN  
    if @OldSDate>@Period_End 
     begin 
      set @[email protected]    

      if @Period_End>[email protected]_Start 
      set @Total+=DATEDIFF(DAY,@Period_Start,@Period_End) 
     end 
    else if @SDate<@Period_End 
     begin  
     set @[email protected]_Start  
      set @Total=DATEDIFF(DAY,@Period_Start,@Period_End) 
     end 

     set @[email protected] 
     set @[email protected] 

     FETCH NEXT FROM Events 
     INTO @SDate, @EDate; 

     if @Period_End<@EDate 
     set @[email protected] 

    END; 

INSERT INTO [dbo].[PersonelDays] 
      (PID 
      ,[Total_Start] 
      ,[Total_End] 
      ,[Total_count]) 
    VALUES 
      (@PID,   
      @Period_Start, 
      @Period_End, 
      @Total 
      ) 

drop table #tblTemp 
CLOSE Events 
DEALLOCATE Events 
END;