2011-12-16 19 views
5

Tengo una tabla que tiene las siguientes columnas.Necesita ayuda de SQL Server Query, Newbie

id, 
compid(used to identify a piece of equipment), 
startalarmdate, 
endalarmdate 

cuando una pieza de equipo entra en alarma, inserto el compid y startalarmdate (con la hora actual) en la mesa y cuando el equipo sale de alarma cumplimento el nulo en la misma fila de la columna de endalarmdate con la hora actual.

por lo que terminan con algo como esto en la tabla

417,6,Sun Oct 30 18:48:17 CDT 2011,Mon Oct 31 09:49:21 CDT 2011 
422,6,Mon Oct 31 10:19:19 CDT 2011,Mon Oct 31 12:19:22 CDT 2011 
427,6,Mon Oct 31 20:19:56 CDT 2011,Tue Nov 01 09:50:59 CDT 2011 
429,6,Tue Nov 01 21:51:41 CDT 2011,Wed Nov 02 09:52:37 CDT 2011 
432,6,Wed Nov 02 21:23:23 CDT 2011,Fri Nov 04 16:26:29 CDT 2011 

yo era capaz de construir una consulta que me da un tiempo total de inactividad de horas para cada evento, pero lo que me gustaría hacer ahora es crear una consulta que me proporcione un total de horas de inactividad por cada día del mes. Me gustaría tener el compid todo el camino a la izquierda, luego tener cada día del mes a la derecha del compid en una columna en la misma fila. Me gustaría que los días sin tiempo de inactividad sean nulos. ¿Es posible hacer eso con la forma en que está configurada esta tabla?

+0

Pregunta: para un día de dar, creo que es la fecha de inicio que importa incluso si la resolución es después de la medianoche, ¿verdad? – fge

+3

Ciertamente es posible. ¿Qué has intentado? Debería mirar la documentación de DATEDIFF para comenzar. Además, deberá decidir qué hacer con el tiempo de inactividad que cruza de un día para otro. Eso hace las cosas más complicadas, pero ciertamente es factible. – paulbailey

Respuesta

1

Paso 1: configure una tabla temporal que contenga los "bloques de tiempo" deseados para los que desea totalizar. Estos bloques pueden ser para cualquier rango de tiempo; en su ejemplo, sería una entrada para cada día (período de 24 horas) en el mes.

CREATE TABLE #TimeRanges 
(
    RangeStart datetime not null 
    ,RangeEnd datetime not null 
) 

Izquierda-exterior-unirse a esta tabla de los datos garantiza la obtención de al menos una fila por cada bloque de tiempo (días), aunque no hubo alarmas que ocurren ese día:

SELECT 
    tr.RangeStart -- Use start of each time block to identify the block 
    ,md.CompId  -- With left outer join, will be 0 or more rows for each block 
    ,sum(datediff(hh 
       ,case 
        when tr.RangeStart > md.StartAlarmDate then tr.RangeStart 
        else md.StartAlarmDate 
       end 
       ,case 
        when tr.RangeEnd > md.EndAlarmDate then tr.RangeEnd 
        else md.EndAlarmDate 
       end)) HoursInRange 
from #TimeRanges tr 
    left outer join MyData md 
    on md.StartAlarmDate < tr.RangeEnd 
    and md.EndAlarmDate > tr.From 
group by 
    tr.RangeStart 
,md.CompId 

(I no puede probar este código, es posible que se requiera cierta depuración, pero el concepto es sólido. Dejaré que te preocupes por redondear las horas parciales, y si quieres> y <, o> = y < = (las cosas pueden volverse complicadas si una alarma se inicia y/o finaliza exactamente en el mismo momento que un límite de bloque).


Editar/Addenda

Aquí está una manera bastante básico para configurar la tabla temporal utilizado en la rutina (este código, he probado):

-- Set up and initialize some variables 
DECLARE 
    @FirstDay  datetime 
,@NumberOfDays int 

SET @FirstDay = 'Oct 1, 2011' -- Without time, this makes it "midnight the morning of" that day 
SET @NumberOfDays = 91 -- Through Dec 31 


-- Creates a temporary table that will persist until it is dropped or the connection is closed 
CREATE TABLE #TimeRanges 
    (
    RangeStart datetime not null 
    ,RangeEnd datetime not null 
) 

-- The order in which you add rows to the table is irrelevant. By adding from last to first, I 
-- only have to fuss around with one variable, instead of two (the counter and the end-point) 

WHILE @NumberOfDays >= 0 
BEGIN 
    INSERT #TimeRanges (RangeStart, RangeEnd) 
    values (dateadd(dd, @NumberOfDays, @FirstDay)  -- Start of day 
      ,dateadd(dd, @NumberOfDays + 1, @FirstDay)) -- Start of the next day 


    SET @NumberOfDays = @NumberOfDays - 1 
END 


-- Review results 
SELECT * 
from #TimeRanges 
order by RangeStart 


-- Not necessary, but doesn't hurt, especially when testing code 
DROP TABLE #TimeRanges 

Tenga en cuenta que al hacer rangeEnd la a partir del día siguiente, debe tener cuidado con sus greaterthans y lessthans. Los detalles pueden ser muy quisquillosos y quisquillosos allí, y querrás hacer muchas pruebas de caso extremo (¿qué ocurre si la alarma comienza o termina exactamente el 16 de diciembre de 2011 00: 00.000)? Me gustaría ir con eso, porque en general es más fácil de codificar que para basura como '16 de diciembre de 2011 23: 59.997'

+2

Para su información, la forma en que resuelvo problemas como este es con papel y lápiz. Dibuje segmentos de línea para todos los periodos de tiempo posibles que se superpongan y que no se superpongan, y resuelva la lógica en consecuencia. –

+0

¡Nunca antes había usado una tabla de temperatura! ¿Cómo le digo qué rango usar? gracias por la ayuda en esto. Puedo hacer cosas básicas de SQL pero estas cosas avanzadas están muy por encima de mi cabeza. –

+0

Se agregó la sección en la tabla de temperatura –

1

Según lo mencionado por @paulbailey, desea utilizar la función DATEDIFF para obtener la cantidad de tiempo de inactividad .

Para extraer la fecha y período de parada (que estoy añadiendo un poco más columnas que pueda necesitar) ..

SELECT compid, 
     YEAR(startalarmdate) AS [Year], 
     MONTH(startalarmdate) AS [Month], 
     DAY(startalarmdate) AS [Day], 
     DATEDIFF(ss, startalarmdate, endalarmdate) AS DowntimeInSeconds --You will need to convert thid later to the format you wish to use 
FROM YourTable 
/* WHERE CLAUSE - Most probably a date range */ 

Ahora bien, esto le da tiempo de inactividad en segundos por cada día que tenían un tiempo de inactividad.

Para obtener la cantidad de tiempo de inactividad por día es fácil como agrupar por día y Sumando los tiempos de inactividad (agregando de nuevo más columnas que pueda necesitar).

SELECT compid, 
     [Year], 
     [Month], 
     [Day], 
     SUM(DowntimeInSeconds) AS TotalDowntimeInSeconds 
FROM (SELECT compid, 
      YEAR(startalarmdate) AS [Year], 
      MONTH(startalarmdate) AS [Month], 
      DAY(startalarmdate) AS [Day], 
      DATEDIFF(ss, startalarmdate, endalarmdate) AS DowntimeInSeconds --You will need to convert thid later to the format you wish to use 
     FROM YourTable 
     /* WHERE CLAUSE - Most probably a date range */) AS GetDowntimes 
GROUP BY compid, [Year], [Month], [Day] 
ORDER BY [Year], [Month], [Day], compid 

Y creo que esto debería ayudarlo a llegar a donde quiere.

Editar: Para tener los días sin tiempo de inactividad incluidos en este resultado, primero debe tener una lista de TODOS los días presentes en un mes. Tomas esta lista y dejaste OUTER JOIN el resultado de la consulta anterior (primero tendrás que eliminar el ORDER BY).

1

La declaración del caso en la respuesta de Philip Kelley no funciona, aunque el principal principal de llenar una tabla temporal con fechas y la unión izquierda es cierto. Para mi versión, he usado la misma variable para comenzar: una fecha de entrada y la cantidad de días para informar.

DECLARE @StartDate DATETIME, @Days INT 
SELECT @StartDate = GETDATE(), 
     @Days = 5 

-- REMOVE ANY TIME FROM THE STARTDATE 
SET @StartDate = DATEADD(DAY, 0, DATEDIFF(DAY, 0, @StartDate)) 

-- CREATE THE TEMP TABLE OF DATES USING THE SAME METHODOLOGY 
DECLARE @Dates TABLE (AlarmDate SMALLDATETIME NOT NULL PRIMARY KEY) 
WHILE (@Days > 0) 
BEGIN 
    INSERT @Dates VALUES (DATEADD(DAY, @Days, @StartDate)) 
    SET @Days = @Days - 1 
END 

-- NOW SELECT THE DATA 
SELECT AlarmDate, 
     CompID, 
     CONVERT(DECIMAL(10, 2), ISNULL(SUM(DownTime), 0)/3600.0) [DownTime] 
FROM @Dates 
     LEFT JOIN 
     ( SELECT DATEADD(DAY, 0, DATEDIFF(DAY, 0, StartAlarmDate)) [StartAlarmDate], 
        CompID, 
        DATEDIFF(SECOND, StartAlarmDate, CASE WHEN EndAlarmDate >= DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate)) THEN DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate)) ELSE EndAlarmDate END) [DownTime] 
      FROM [yourTable] 
      UNION ALL 
      SELECT DATEADD(DAY, 0, DATEDIFF(DAY, 0, EndAlarmDate)) [Date], 
        CompID, 
        DATEDIFF(SECOND, DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate)), EndAlarmDate) [DownTime] 
      FROM [yourTable] 
      WHERE EndAlarmDate >= DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate)) 
     ) data 
      ON StartAlarmDate = AlarmDate 
GROUP BY AlarmDate, CompID 

que tienen segundos utilizados para las diferencias de fecha y dividido por 3600,0 después de que los segundos se han resumido como 60 filas cada una con una diferencia de un minuto se suma a 0 cuando se utiliza para una hora datediff.