SQL, чтобы найти время, прошедшее с нескольких перекрывающихся интервалов - PullRequest
8 голосов
/ 29 августа 2011

Не используется MSSQL, DB2 или Oracle. Нет CTE. Предикат OVERLAP отсутствует. Нет ИНТЕРВАЛЬНОГО типа данных. Ситуация: на ремонтируемом транспортном средстве работа не может начаться до все заказанные детали для работы получены. Детали можно заказывать несколько раз до начала ремонта. Нам нужно извлечь время, в течение которого транспортное средство находилось в «состоянии ожидания»

Таким образом, для транспортного средства, идентифицированного как 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, не курсор). ТИА

Ответы [ 3 ]

6 голосов
/ 29 августа 2011

Я не смог заставить работать запросы @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;
4 голосов
/ 31 августа 2011

Этот оператор 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 ) )

Объединение двух вышеупомянутых и объединение всех таких периодов дает количество дней, в течение которых транспортное средствоне жду запчасти.Последний запрос добавляет дополнительное условие для вычисления результата для каждого идентификатора из внешнего запроса.

Это, вероятно, не очень эффективно для очень большой таблицы со многими идентификаторами.Хорошо, если идентификатор ограничен одним или несколькими.

0 голосов
/ 08 декабря 2014
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 PID=@PID
        --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 =@SDate
    set @Period_End=@EDate

    WHILE @@FETCH_STATUS = 0
    BEGIN      
    if @OldSDate>@Period_End
        begin
            set @Period_Start=@SDate            

            if @Period_End>=@Period_Start
            set @Total+=DATEDIFF(DAY,@Period_Start,@Period_End)
        end
    else if @SDate<@Period_End
        begin       
        set @Period_Start=@Period_Start     
            set @Total=DATEDIFF(DAY,@Period_Start,@Period_End)
        end

        set @OldSDate=@SDate 
        set @OldEDate=@EDate

        FETCH NEXT FROM Events
        INTO @SDate, @EDate;

        if  @Period_End<@EDate
        set @Period_End=@EDate

    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;
...