Domanda

Sto cercando una singola query in grado di convertire le seguenti informazioni nella tabella

name:time    :state
a   :10:00 AM:login
b   :10:05 AM:login
a   :10:06 AM:chatting
a   :10:08 AM:Idle
b   :10:11 AM:chatting
a   :10:10 AM:Logout
b   :10:12 AM:Logout

a qualcosa del genere (dato l'intervallo di tempo dalle 10 alle 10:15 come periodo di interrogazione)

name: State    :Duration
a   : chatting :2 Minutes 
a   : Idle     :2 Minutes
b   : chatting :1 Minute

È possibile farlo SOLO usando SQL? Sto usando Informix versione 11.5

È stato utile?

Soluzione

Può essere fatto in una singola istruzione SQL. Ecco la prova.

Configurazione

CREATE TEMP TABLE eventtable
(
    name CHAR(3) NOT NULL,
    time DATETIME HOUR TO MINUTE NOT NULL,
    state CHAR(8) NOT NULL
);

INSERT INTO eventtable(name, time, state) VALUES('a', '10:00', 'login');
INSERT INTO eventtable(name, time, state) VALUES('b', '10:05', 'login');
INSERT INTO eventtable(name, time, state) VALUES('a', '10:06', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('a', '10:08', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('b', '10:11', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('a', '10:10', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('b', '10:12', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:01', 'login');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:02', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:03', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:04', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:05', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:06', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:07', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:08', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:09', 'login');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:11', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:12', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:13', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:14', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:15', 'Logout');

Query corretta

Nota le condizioni. La tabella dei risultati deve escludere i periodi tra 'login' e il primo altro evento; inoltre, deve escludere il periodo tra 'Logout' e il prossimo evento (presumibilmente un 'login'). Il self-join tra la tabella nella colonna name e quindi il join asimmetrico nella colonna time (usando '<') garantisce che gli eventi siano in ordine temporale. La sottoselezione NON ESISTE garantisce che vengano considerati solo gli eventi adiacenti. L'uso di TRA E nella sottoquery è un errore perché include i suoi punti finali ed è fondamentale che r1.time e r2.time siano esclusi dall'intervallo; mi ci sono voluti alcuni minuti per individuare quel bug (la query è stata eseguita ma non ha restituito righe, ma perché ?)!

SELECT r1.name, r1.state, r2.TIME - r1.TIME AS duration
    FROM eventtable r1, eventtable r2
    WHERE r1.name = r2.name
      AND r1.time < r2.time
      AND r1.state != 'login'
      AND r1.state != 'Logout'
      AND r1.time BETWEEN DATETIME(10:00) HOUR TO MINUTE
                      AND DATETIME(10:15) HOUR TO MINUTE
      AND r2.time BETWEEN DATETIME(10:00) HOUR TO MINUTE
                      AND DATETIME(10:15) HOUR TO MINUTE
      AND NOT EXISTS (SELECT 1 FROM eventtable r3
                            WHERE r3.time > r1.time AND r3.time < r2.time
                      AND r3.name = r1.name
                      AND r3.name = r2.name);

Questo produce la risposta:

name state      duration
a    chatting   0:02
a    Idle       0:02
b    chatting   0:01

c    chatting   0:01
c    Idle       0:01
c    Idle       0:01
c    Idle       0:01
c    chatting   0:01
c    Idle       0:01
c    chatting   0:01
c    Idle       0:01

Il valore 'duration' è un ORA DI INTERVALLO AL MINUTO; se vuoi un valore in pochi minuti, devi convertirlo con un cast (usando 4 per la precisione per consentire intervalli fino a 1440 minuti o 1 giorno; i dati sono ambigui per intervalli di tempo più lunghi):

(r2.time - r1.time)::INTERVAL MINUTE(4) TO MINUTE

o

CAST (r2.time - r1.time AS INTERVAL MINUTE(4) TO MINUTE)

IBM Informix Dynamic Server (IDS) ha notazioni molto dettagliate per le costanti di tempo. In SQL standard, è possibile utilizzare TIME come tipo e TIME '10: 00: 00 'come valore, ma i secondi sarebbero necessari in SQL standard rigoroso. IDS fornisce i tipi esatti che le persone desiderano, ad esempio DATETIME HOUR TO MINUTE. Scriveresti anche INTERVAL MINUTE (4) in SQL standard; il "MINUTO" dovrebbe essere facoltativo.

Query errata

Nel mio commento alla risposta di Ray Hidayat, ho sottolineato che la sottointerrogazione EXISTS è necessaria per garantire che gli eventi in esame siano contigui: non vi sono eventi intermedi. Ecco la stessa query con i tempi di inizio e fine aggiunti all'output e manca la clausola EXISTS (e 'duration' rinominata in 'lapse'):

SELECT r1.name, r1.state, r2.TIME - r1.TIME AS lapse,
       r1.time AS start, r2.time AS end
    FROM eventtable r1, eventtable r2
    WHERE r1.name = r2.name
      AND r1.time < r2.time
      AND r1.state != 'login'
      AND r1.state != 'Logout'
      AND r1.time BETWEEN DATETIME(10:00) HOUR TO MINUTE
                      AND DATETIME(10:15) HOUR TO MINUTE
      AND r2.time BETWEEN DATETIME(10:00) HOUR TO MINUTE
                      AND DATETIME(10:15) HOUR TO MINUTE;

Questo produce la risposta:

name state     lapse start end
a    chatting   0:04 10:06 10:10
a    chatting   0:02 10:06 10:08
a    Idle       0:02 10:08 10:10
b    chatting   0:01 10:11 10:12
c    chatting   0:13 10:02 10:15
c    chatting   0:12 10:02 10:14
c    chatting   0:11 10:02 10:13
c    chatting   0:10 10:02 10:12
c    chatting   0:09 10:02 10:11
c    chatting   0:07 10:02 10:09
c    chatting   0:06 10:02 10:08
c    chatting   0:05 10:02 10:07
c    chatting   0:04 10:02 10:06
c    chatting   0:03 10:02 10:05
c    chatting   0:02 10:02 10:04
c    chatting   0:01 10:02 10:03
c    Idle       0:12 10:03 10:15
c    Idle       0:11 10:03 10:14
c    Idle       0:10 10:03 10:13
c    Idle       0:09 10:03 10:12
c    Idle       0:08 10:03 10:11
c    Idle       0:06 10:03 10:09
c    Idle       0:05 10:03 10:08
c    Idle       0:04 10:03 10:07
c    Idle       0:03 10:03 10:06
c    Idle       0:02 10:03 10:05
c    Idle       0:01 10:03 10:04
c    Idle       0:10 10:05 10:15
c    Idle       0:09 10:05 10:14
c    Idle       0:08 10:05 10:13
c    Idle       0:07 10:05 10:12
c    Idle       0:06 10:05 10:11
c    Idle       0:04 10:05 10:09
c    Idle       0:03 10:05 10:08
c    Idle       0:02 10:05 10:07
c    Idle       0:01 10:05 10:06
c    Idle       0:08 10:07 10:15
c    Idle       0:07 10:07 10:14
c    Idle       0:06 10:07 10:13
c    Idle       0:05 10:07 10:12
c    Idle       0:04 10:07 10:11
c    Idle       0:02 10:07 10:09
c    Idle       0:01 10:07 10:08
c    chatting   0:04 10:11 10:15
c    chatting   0:03 10:11 10:14
c    chatting   0:02 10:11 10:13
c    chatting   0:01 10:11 10:12
c    Idle       0:03 10:12 10:15
c    Idle       0:02 10:12 10:14
c    Idle       0:01 10:12 10:13
c    chatting   0:02 10:13 10:15
c    chatting   0:01 10:13 10:14
c    Idle       0:01 10:14 10:15

Questo mostra come ogni riga iniziale ammissibile per l'utente 'c' è abbinata a ciascuna riga finale ammissibile, fornendo molte righe spurie di dati. La sottoquery NOT EXISTS è un tema comune quando si gestiscono query basate sul tempo. Puoi trovare informazioni su queste operazioni nelle & Quot di Snodgrass; Sviluppo di applicazioni orientate al tempo in SQL " (PDF disponibile online all'indirizzo URL) e in Date, Darwen e Lorentzos & Quot; Dati temporali e modello relazionale & Quot ;.

Altri suggerimenti

Sono abbastanza sicuro che può essere fatto usando solo SQL, mi ci vorrà un bel po 'di tempo per trovare una query per te, la modificherò quando avrò finito. I passi di base che penso sarebbero prima di calcolare il tempo impiegato da ciascuno (fatto prendendo ogni voce e unendola alla voce successiva e sottraendo per trovare la differenza di tempo), quindi un semplice gruppo per clausola con una somma otterrà facilmente nella forma che hai descritto.

Modifica: ecco cosa mi è venuto in mente

SELECT l.userid, l.state, SUM(t.minutes) AS duration
FROM Log l 
INNER JOIN (
    SELECT l1.id, (l2.time - l1.time) AS minutes
    FROM Log l1, Log l2
    WHERE l2.time == ( 
        -- find the next entry --
        SELECT TOP 1 ls.time
        FROM Log ls
        WHERE ls.Time > l1.Time && ls.userid = l1.userid
        ORDER BY ls.Time
    )
) t ON l.id == t.id
GROUP BY l.userid, l.state
ORDER BY l.userid

Questo è semi-pseudocodice, ho inventato tutti i nomi e le cose della tabella e non sarai in grado di sottrarre una volta dall'altra, probabilmente utilizzerai la funzione DATEDIFF. Oltre a ciò, penso che sia questo il punto. Penso che SQL sia uno dei linguaggi più sorprendenti, puoi fare quasi tutto con poco codice.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top