2011-08-28 44 views
8

No utilizando MSSQL o DB2 u Oracle. No CTE. Sin predicado OVERLAP. Sin tipo de datos INTERVALO. La situación: en un vehículo por reparar, el trabajo no puede comenzar hasta que se hayan recibido todas las piezas pedidas para el trabajo. Las piezas se pueden pedir varias veces antes del comienzo de la reparación. Tenemos que extraer el tiempo durante el cual el vehículo estaba en "partes sostienen"SQL para encontrar el tiempo transcurrido desde múltiples intervalos solapados

Así que para un vehículo identificado como ID = 1 partes recibieron la orden (d1) y recibió (d2) en 4 ocasiones diferentes

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 

como se ve desde arriba, el tiempo de espera para empezar a trabajar (suponiendo 8/1 como la fecha de la que estaba disponible para el trabajo del vehículo) fue de 13 días. El tiempo real dedicado a esperar piezas fue de 11 días, que es el número que necesitamos derivar de los datos. Los datos de fecha y hora reales serán las marcas de tiempo de la que extraerá horas, utilizamos las fechas en esta muestra de datos para simplificar la presentación. Estamos luchando por generar una solución basada en conjunto (no psm, no udf, no cursor). TIA

+0

Creo que una combinación de la izquierda en una tabla de calendario auxiliar podría ayudar. –

+0

posible duplicado de [¿Cuál es una buena manera de encontrar espacios en un conjunto de fechas?] (Http://stackoverflow.com/questions/4765495/what-is-a-good-way-to-find-gaps-in -a-set-of-dimespans) –

+0

@Brian, esta pregunta es muy diferente. OP, ¿puede agregar una vista (s) para ayudar en la consulta? –

Respuesta

4

Esta SQL parece conseguir lo que quiere (t es el nombre de la tabla de la mesa 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 

La consulta externa obtiene la duración del trabajo de reparación. La subconsulta compleja calcula el número total de días que no esperan partes. Esto se hace mediante la localización de las fechas de inicio cuando el vehículo no está a la espera de piezas, y luego contar el número de días hasta que comience a esperar a que las partes de nuevo:

// 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) Los días en los que vehículo es sin esperar a que parte es la fecha a partir de la consulta anterior hasta que el vehículo se // espera de parte de nuevo

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

combinación de los dos arriba y agregación de todos esos períodos da el número de días que el vehículo no está a la espera de piezas . La consulta final agrega una condición adicional para calcular el resultado para cada id de la consulta externa.

Esto probablemente no sea terriblemente eficiente en una mesa muy grande con muchos identificadores. Debería estar bien si el ID está limitado a uno o solo a unos pocos.

+0

¡Guau, eso es hermoso! – jon

+0

Editado para solucionar el problema con la fecha de inicio duplicada de los períodos "sin retención". –

+0

Alex: en el bloque de código principal anterior, falta un paréntesis después de – jon

5

no pude conseguir consultas de @Alex W para trabajar. No es SQL estándar, por lo que requería mucha reescritura para ser compatible con SQL Server (que puedo probar). Pero sí me inspiró, y lo amplié.


Buscar todas las empresas de nueva puntos de cada período de espera ininterrumpida:

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 

y el equivalente para los puntos finales:

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 es el número de días desde algunos comunes punto en el tiempo. Los puntos de inicio tienen un valor negativo, y los puntos finales tienen un valor positivo. Esto es para que podamos simplemente sumarlos para obtener la cantidad de días intermedios.

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

Por último, sólo tenemos que añadir cosas:

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; 

mesa de entrada (órdenes):

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 

Salida:

ID hold_days 
1   11 
2   8 

Como alternativa, puede hacer esto con un procedimiento almacenado.

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; 

llamada con:

comunicado
EXEC CalculateHoldTimes 1; 
+0

Gracias MizardX, esto es exactamente lo que estábamos buscando – jon

+0

lo siento, no tengo suficientes puntos para hacer clic en su respuesta – jon

+0

Ahora puede. :) De todas formas; Aún puede aceptar una respuesta si cree que responde su pregunta. Haga clic en la marca de verificación debajo de las flechas de votación. –

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; 
Cuestiones relacionadas