2011-08-28 70 views
8

Không sử dụng MSSQL hoặc DB2 hoặc Oracle. Không CTE. Không có vị từ OVERLAP. Không có loại dữ liệu INTERVAL. Tình huống: trên một phương tiện để sửa chữa công việc không thể bắt đầu cho đến khi tất cả các bộ phận đặt hàng cho công việc đã được nhận. Các bộ phận có thể được đặt hàng nhiều lần trước khi bắt đầu sửa chữa. Chúng ta cần phải trích xuất các gian mà chiếc xe đang trên "phần giữ"SQL để tìm thời gian trôi qua từ nhiều khoảng thời gian chồng chéo

Vì vậy, cho một chiếc xe được xác định là id = 1 phần được lệnh (d1) và nhận (d2) trên 4 dịp khác nhau

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 

Như đã thấy ở trên, thời gian chờ để bắt đầu công việc (giả sử 8/1 là ngày mà từ đó xe có sẵn để đi làm) là 13 ngày. Thời gian thực tế chờ đợi cho các bộ phận là 11 ngày, đó là số chúng tôi cần phải lấy được từ dữ liệu. Dữ liệu datetime thực tế sẽ là dấu thời gian mà từ đó chúng tôi sẽ trích xuất giờ, chúng tôi đã sử dụng ngày trong dữ liệu mẫu này để đơn giản hóa bản trình bày. Chúng tôi đang đấu tranh để tạo ra một giải pháp dựa trên bộ (không phải psm, không udf, không phải con trỏ). TIA

+0

Tôi có một sự tham gia trái trên bảng lịch phụ trợ có thể hữu ích. –

+0

có thể trùng lặp của [Cách tốt nhất để tìm khoảng trống trong một tập hợp các ngày tháng là gì?] (Http://stackoverflow.com/questions/4765495/what-is-a-good-way-to-find-gaps-in -a-set-of-datepans) –

+0

@Brian, câu hỏi này rất khác nhau. OP, bạn có thể thêm (các) chế độ xem để hỗ trợ truy vấn không? –

Trả lời

4

SQL này dường như để có được những gì bạn muốn (t là tên bảng của bảng 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 

Truy vấn bên ngoài được thời gian của công việc sửa chữa. Truy vấn con phức tạp tính toán tổng số ngày không chờ đợi các bộ phận. Này được thực hiện bằng cách định vị ngày bắt đầu nơi chiếc xe được không chờ đợi cho các bộ phận, và sau đó đếm số ngày cho đến khi nó bắt đầu để chờ đợi cho các bộ phận một lần nữa:

// 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) Những ngày mà nó là xe không chờ đợi một phần là ngày từ các truy vấn trên cho đến khi chiếc xe được // chờ đợi phần nữa

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)) 

Kết hợp hai ở trên và tập hợp tất cả các giai đoạn như vậy cho số ngày mà chiếc xe không chờ đợi cho các bộ phận . Truy vấn cuối cùng thêm một điều kiện bổ sung để tính kết quả cho mỗi id từ truy vấn bên ngoài.

Điều này có thể không hiệu quả khủng khiếp trên bảng rất lớn với nhiều id. Nó sẽ tốt nếu id được giới hạn một hoặc chỉ một vài.

+0

Thật tuyệt vời! – jon

+0

Đã chỉnh sửa để khắc phục vấn đề với ngày bắt đầu trùng lặp của các khoảng thời gian "không giữ". –

+0

Alex: trong khối mã chính ở trên, thiếu dấu ngoặc đơn phải sau – jon

5

Tôi không thể truy vấn @Alex W để hoạt động. Nó không phải là tiêu chuẩn SQL, vì vậy nó yêu cầu rất nhiều viết lại để tương thích với SQL Server (mà tôi có thể kiểm tra). Nhưng nó đã cho tôi một số cảm hứng, mà tôi đã mở rộng.


Tìm tất cả khởi điểm của mỗi giai đoạn không bị gián đoạn chờ đợi:

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 

Và tương đương cho end-điểm:

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 là số ngày kể từ một số chung điểm trong thời gian. Điểm bắt đầu có giá trị âm và điểm cuối có giá trị dương. Điều này là để chúng tôi có thể thêm chúng lên để có được số ngày ở giữa.

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

Cuối cùng, chúng ta chỉ cần thêm những thứ lên:

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; 

Input bảng (Orders):

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 

Output:

ID hold_days 
1   11 
2   8 

Ngoài ra, bạn có thể thực hiện việc này bằng quy trình được lưu trữ.

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; 

Gọi nó với:

tuyên bố
EXEC CalculateHoldTimes 1; 
+0

Cảm ơn bạn MizardX, đây là chính xác những gì chúng tôi đang tìm kiếm – jon

+0

xin lỗi, tôi không có đủ điểm để nhấp vào câu trả lời của bạn lên – jon

+0

Bây giờ bạn có thể. :) Dù sao; Bạn vẫn có thể chấp nhận câu trả lời nếu bạn cho rằng câu trả lời đó là câu trả lời cho câu hỏi của bạn. Nhấp vào dấu kiểm bên dưới mũi tên bỏ phiếu. –

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; 
Các vấn đề liên quan