2011-08-28 84 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;