2011-12-16 9 views
5

Ho una tabella con le seguenti colonne.Richiesta di query SQL Server, Newbie

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

quando un pezzo di apparecchiatura va in allarme, inserisco il compid e startalarmdate (con l'ora corrente) nella tabella e quando l'apparecchiatura esce allarme compilo nel nulla nella stessa riga della colonna endalarmdate con l'ora corrente.

così finisco con qualcosa di simile nella tabella

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 

sono stato in grado di costruire una query che mi dà un tempo di inattività totale di ore per ogni evento, ma quello che vorrei fare ora è costruire una query che mi dà un totale di ore in downtime per ogni giorno di un mese. Mi piacerebbe avere il compid tutto a sinistra, quindi avere ogni giorno del mese a destra del compid in una colonna sulla stessa riga. Id come i giorni senza tempi di inattività per essere nulli. È possibile farlo con il modo in cui questa tabella è impostata?

+0

Domanda: per un giorno dare, immagino che sia la data di inizio che conta anche se la risoluzione è dopo la mezzanotte, giusto? – fge

+3

È certamente possibile. Che cosa hai provato? Si dovrebbe guardare la documentazione di DATEDIFF per iniziare. Inoltre, dovrai decidere cosa fare del tempo di inattività che attraversa da un giorno all'altro. Ciò rende le cose più complicate, ma è certamente fattibile. – paulbailey

risposta

1

Passaggio 1: impostare una tabella temporanea contenente i "blocchi temporali" desiderati per i quali si desidera effettuare il totale. Questi blocchi potrebbero essere per qualsiasi intervallo di tempo; nel tuo esempio, sarebbe una voce per sempre giorno (periodo di 24 ore) nel mese.

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

sinistra-esterno-partecipare a questo tavolo sulla vostri dati vi permette di ricevere almeno una riga per ogni fascia oraria (giorno), anche se non ci fossero gli allarmi che si verificano in quel giorno:

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 non è possibile testare questo codice, potrebbe essere necessario eseguire il debug, ma il concetto è solido. Ti lascerò preoccupare di arrotondare le ore parziali e se vuoi> e < o> = e < = (le cose potrebbero essere complicate se un allarme inizia e/o termina allo stesso esatto punto temporale del limite di un blocco)


Modifica/Addenda

Ecco un modo abbastanza semplice per impostare la tabella temporanea utilizzata nella routine (questo codice, ho provato):

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

Nota che facendo rangeEnd la l'inizio del giorno dopo, devi stare attento con i tuoi grandi e minori. I dettagli possono essere molto schizzinosi e pignoli lì, e ti consigliamo di fare un sacco di test di bordo (cosa succede se l'allarme inizia, o termina, esattamente al 16 dicembre 2011 00: 00.000). Mi piacerebbe andare con quello, perché nel complesso è più semplice da codificare che per junk come '16 dic 2011 23: 59.997'

+2

FYI, il modo in cui risolvo problemi come questo è con carta e matita. Disegna i segmenti di linea per tutti i possibili intervalli di tempo sovrapposti e non sovrapposti e calcola di conseguenza la logica. –

+0

Non ho mai usato un tavolo temporaneo prima! Come posso dirgli quale intervallo usare? grazie per l'aiuto su questo Posso fare cose SQL di base, ma questa roba avanzata mi è passata per la testa! –

+0

Aggiunta la sezione sulla tabella temporanea –

1

Come menzionato da @paulbailey, si desidera utilizzare la funzione DATEDIFF per ottenere la quantità di tempo di inattività .

Per estrarre le date e il periodo di inattività (io sono l'aggiunta di un po 'più colonne che potrebbe essere necessario) ..

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 */ 

Ora, questo ti dà il tempo di inattività in secondi per ogni giorni che avevano un tempo di inattività.

Per ottenere la quantità di tempo di inattività al giorno è facile come raggruppare per giorno e SUMING up i tempi di inattività (ancora una volta l'aggiunta di più colonne che potrebbe essere necessario) ..

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 

E credo che questo dovrebbe aiutarti a ottenere dove vuoi.

Modifica: Per avere i giorni che non hanno tempi di fermo inclusi in questo risultato, è necessario prima avere un elenco di TUTTI i giorni presenti in un mese. Prendi questa lista e LASCIUI ESCLUSIVAMENTE ENTRO il risultato della query precedente (dovrai prima rimuovere ORDER BY).

1

La dichiarazione del caso nella risposta di Philip Kelley non funziona, anche se il principio principale di riempire una tabella temporanea con date e join di sinistra è vero. Per la mia versione ho usato la stessa variabile per iniziare - una data di input e il numero di giorni da segnalare.

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 

devo secondi usati per la data diff e diviso per 3600.0 dopo i secondi sono state riassunte come 60 righe ognuna con una differenza di un minuto sarebbe sommare a 0 quando si utilizza ore per un datediff.

Problemi correlati