Trova la durata totale di ogni serie consecutiva di righe


11

Versione MySQL

Il codice verrà eseguito in MySQL 5.5

sfondo

Ho un tavolo come il seguente

CREATE TABLE t
( id INT NOT NULL AUTO_INCREMENT
, patient_id INT NOT NULL
, bed_id INT NOT NULL
, ward_id INT NOT NULL
, admitted DATETIME NOT NULL
, discharged DATETIME
, PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

Questa tabella riguarda i pazienti in un ospedale e memorizza i letti in cui ogni paziente ha trascorso un po 'di tempo durante il ricovero in ospedale.

Ogni reparto può avere più letti e ogni paziente può spostarsi in un letto diverso all'interno dello stesso reparto.

Obbiettivo

Quello che voglio fare è scoprire quanto tempo ogni paziente ha trascorso in un reparto specifico senza essersi trasferito in un reparto diverso. Vale a dire che voglio trovare la durata totale del tempo consecutivo trascorso nello stesso reparto.

Caso di prova

-- Let's assume that ward_id = 1 corresponds to ICU (Intensive Care Unit)
INSERT INTO t
  (patient_id, bed_id, ward_id, admitted, discharged)
VALUES

-- Patient 1 is in ICU, changes some beds, then he is moved 
-- out of ICU, back in and finally he is out.
(1, 1, 1, '2015-01-06 06:05:00', '2015-01-07 06:04:00'),
(1, 2, 1, '2015-01-07 06:04:00', '2015-01-07 07:08:00'),
(1, 1, 1, '2015-01-07 07:08:00', '2015-01-08 08:11:00'),
(1, 4, 2, '2015-01-08 08:11:00', '2015-01-08 09:11:00'),
(1, 1, 1, '2015-01-08 09:11:00', '2015-01-08 10:11:00'),
(1, 3, 1, '2015-01-08 10:11:00', '2015-01-08 11:11:00'),
(1, 1, 2, '2015-01-08 11:11:00', '2015-01-08 12:11:00'),

-- Patient 2 is out of ICU, he gets inserted in ICU, 
-- changes some beds and he is back out
(2, 1, 2, '2015-01-06 06:00:00', '2015-01-07 06:04:00'),
(2, 1, 1, '2015-01-07 06:04:00', '2015-01-07 07:08:00'),
(2, 3, 1, '2015-01-07 07:08:00', '2015-01-08 08:11:00'),
(2, 1, 2, '2015-01-08 08:11:00', '2015-01-08 09:11:00'),

-- Patient 3 is not inserted in ICU
(3, 1, 2, '2015-01-08 08:10:00', '2015-01-09 09:00:00'),
(3, 2, 2, '2015-01-09 09:00:00', '2015-01-10 10:01:00'),
(3, 3, 2, '2015-01-10 10:01:00', '2015-01-11 12:34:00'),
(3, 4, 2, '2015-01-11 12:34:00', NULL),

-- Patient 4 is out of ICU, he gets inserted in ICU without changing any beds
-- and goes back out.
(4, 1, 2, '2015-01-06 06:00:00', '2015-01-07 06:04:00'),
(4, 2, 1, '2015-01-07 06:04:00', '2015-01-07 07:08:00'),
(4, 1, 2, '2015-01-07 07:08:00', '2015-01-08 09:11:00'),

-- Patient 5 is out of ICU, he gets inserted in ICU without changing any beds
-- and he gets dismissed.
(5, 1, 2, '2015-01-06 06:00:00', '2015-01-07 06:04:00'),
(5, 3, 2, '2015-01-07 06:04:00', '2015-01-07 07:08:00'),
(5, 1, 1, '2015-01-07 07:08:00', '2015-01-08 09:11:00'),

-- Patient 6 is inserted in ICU and he is still there
(6, 1, 1, '2015-01-11 12:34:00', NULL);

Nella tabella reale le righe non sono consecutive ma per ogni paziente il timestamp di scarico da una riga == il timestamp di ammissione della riga successiva.

SQLFiddle

http://sqlfiddle.com/#!2/b5fe5

Risultato atteso

Vorrei scrivere qualcosa di simile al seguente:

SELECT pid, ward_id, admitted, discharged
FROM  (....)
WHERE ward_id = 1;

(1, 1, '2015-01-06 06:05:00', '2015-01-08 08:11:00'),
(1, 1, '2015-01-08 09:11:00', '2015-01-09 11:11:00'),
(2, 1, '2015-01-07 06:04:00', '2015-01-08 08:11:00'),
(4, 1, '2015-01-07 06:04:00', '2015-01-07 07:08:00'),
(5, 1, '2015-01-07 07:08:00', '2015-01-08 09:11:00'),
(6, 1, '2015-01-11 12:34:00', NULL);

Tieni presente che non possiamo raggruppare per patient_id. Dobbiamo recuperare un record separato per ogni visita in terapia intensiva.

Per dirla più chiaramente, se un paziente trascorre del tempo in terapia intensiva, poi si allontana da esso e poi ritorna lì, ho bisogno di recuperare il tempo totale che ha trascorso in ogni visita in terapia intensiva (cioè due record)


1
+1 per una domanda eloquente, che spiega chiaramente un problema complesso (e interessante). Se potessi votare due volte per il bonus aggiuntivo di un SQLFiddle, lo farei. Tuttavia, il mio istinto è che senza CTE (espressioni di tabella comuni) o funzioni di windowing, ciò non sarà possibile in MySQL. Quale ambiente di sviluppo stai utilizzando, ad esempio potresti essere obbligato a farlo tramite il codice.
Vérace,

@Vérace Ho dichiarato di scrivere il codice che recupera tutte le righe corrispondenti ai letti ICU e le sto raggruppando in Python.
pmav99,

Naturalmente se lo posso fare in modo relativamente pulito in SQL lo preferirò.
pmav99,

Come le lingue vanno, Python è abbastanza pulito! :-) Se non sei bloccato su MySQL e hai bisogno di un database F / LOSS, posso raccomandare PostgreSQL (per molti versi notevolmente superiore a MySQL IMHO) che ha funzioni CTE e Windowing.
Vérace,

Risposte:


4

Query 1, testata in SQLFiddle-1

SET @ward_id_to_check = 1 ;

SELECT
    st.patient_id,
    st.bed_id AS starting_bed_id,          -- the first bed a patient uses
                                           -- can be omitted
    st.admitted,
    MIN(en.discharged) AS discharged
FROM
  ( SELECT patient_id, bed_id, admitted, discharged
    FROM t 
    WHERE t.ward_id = @ward_id_to_check
      AND NOT EXISTS
          ( SELECT * 
            FROM t AS prev 
            WHERE prev.ward_id = @ward_id_to_check
              AND prev.patient_id = t.patient_id
              AND prev.discharged = t.admitted
          )
  ) AS st
JOIN
  ( SELECT patient_id, admitted, discharged
    FROM t 
    WHERE t.ward_id = @ward_id_to_check
      AND NOT EXISTS
          ( SELECT * 
            FROM t AS next 
            WHERE next.ward_id = @ward_id_to_check
              AND next.patient_id = t.patient_id
              AND next.admitted = t.discharged
          )
  ) AS en
    ON  st.patient_id = en.patient_id
    AND st.admitted <= en.admitted
GROUP BY
    st.patient_id,
    st.admitted ;

Query 2, che è uguale a 1 ma senza le tabelle derivate. Questo probabilmente avrà un piano di esecuzione migliore, con indici adeguati. Test in SQLFiddle-2 :

SET @ward_id_to_check = 1 ;

SELECT
    st.patient_id,
    st.bed_id AS starting_bed_id,
    st.admitted,
    MIN(en.discharged) AS discharged
FROM
    t AS st    -- starting period
  JOIN
    t AS en    -- ending period
      ON  en.ward_id = @ward_id_to_check
      AND st.patient_id = en.patient_id
      AND NOT EXISTS
          ( SELECT * 
            FROM t AS next 
            WHERE next.ward_id = @ward_id_to_check
              AND next.patient_id = en.patient_id
              AND next.admitted = en.discharged
          )
      AND st.admitted <= en.admitted
WHERE 
      st.ward_id = @ward_id_to_check
  AND NOT EXISTS
      ( SELECT * 
        FROM t AS prev 
        WHERE prev.ward_id = @ward_id_to_check
          AND prev.patient_id = st.patient_id
          AND prev.discharged = st.admitted
      )
GROUP BY
    st.patient_id,
    st.admitted ;

Entrambe le query presuppongono l'esistenza di un vincolo univoco (patient_id, admitted). Se il server funziona con rigide impostazioni ANSI, è bed_idnecessario aggiungerlo GROUP BYnell'elenco.


Si noti che ho modificato i valori di inserimento nel violino, perché le date scaricate / ammesse non corrispondevano per gli ID paziente 1 e 2.
ypercubeᵀᴹ

2
In soggezione - Pensavo davvero che fosse impossibile data la mancanza di CTE. Stranamente, la prima query non verrebbe eseguita per me in SQLFiddle - un problema tecnico? Il secondo invece lo ha fatto, ma posso suggerire che st.bed_id venga rimosso, poiché è fuorviante. Il paziente 1 non ha trascorso tutto il suo primo soggiorno nel reparto 1 nello stesso letto.
Vérace,

@ Vérace, thnx. All'inizio, ho pensato anche che avevamo bisogno di un CTE ricorsivo. Ho corretto un join mancante su patient_id (che nessuno ha notato;) e aggiunto il tuo punto sul letto.
ypercubeᵀᴹ

@ypercube Grazie mille per la tua risposta! Questo è davvero utile. Lo
studierò

0

RICHIESTA PROPOSTA

SELECT patient_id,SEC_TO_TIME(SUM(elapsed_time)) elapsed
FROM (SELECT * FROM (SELECT patient_id,
UNIX_TIMESTAMP(IFNULL(discharged,NOW())) -
UNIX_TIMESTAMP(admitted) elapsed_time
FROM t WHERE ward_id = 1) AA) A
GROUP BY patient_id;

Ti ho caricato i dati di esempio in un database locale sul mio laptop. Quindi, ho eseguito la query

INTERROGAZIONE PROPOSTA ESEGUITA

mysql> SELECT patient_id,SEC_TO_TIME(SUM(elapsed_time)) elapsed
    -> FROM (SELECT * FROM (SELECT patient_id,
    -> UNIX_TIMESTAMP(IFNULL(discharged,NOW())) -
    -> UNIX_TIMESTAMP(admitted) elapsed_time
    -> FROM t WHERE ward_id = 1) AA) A
    -> GROUP BY patient_id;
+------------+-----------+
| patient_id | elapsed   |
+------------+-----------+
|          1 | 76:06:00  |
|          2 | 26:07:00  |
|          4 | 01:04:00  |
|          5 | 26:03:00  |
|          6 | 118:55:48 |
+------------+-----------+
5 rows in set (0.00 sec)

mysql>

DOMANDA PROPOSTA SPIEGATA

Nella subquery AA, calcolo il numero di secondi trascorsi usando UNIX_TIMESTAMP () sottraendo UNIX_TIMESTAMP(discharged)DA UNIX_TIMESTAMP(admitted). Se il paziente è ancora nel letto (come indicato dall'essere dimesso NULL), assegnerò l'ora corrente ORA () . Quindi faccio il sottrazione. Questo ti darà una durata dell'ultimo minuto per ogni paziente ancora nel reparto.

Quindi, aggrego la somma dei secondi di patient_id. Infine, prendo i secondi per ciascun paziente e utilizzo SEC_TO_TIME () per visualizzare ore, minuti e secondi di permanenza del paziente.

PROVACI !!!


Per la cronaca, ho eseguito questo in MySQL 5.6.22 sul mio laptop Windows 7. Fornisce un errore in SQL Fiddle.
RolandoMySQLDBA

1
grazie mille per la tua risposta. Temo tuttavia che ciò non risponda alla mia domanda; probabilmente non ero abbastanza chiaro nella mia descrizione. Quello che voglio recuperare è il tempo totale trascorso per ogni soggiorno in terapia intensiva. Non voglio raggruppare per paziente. Se un paziente trascorre del tempo in terapia intensiva, poi si allontana da esso e poi ritorna lì, devo recuperare il tempo totale trascorso in ogni visita (cioè due record).
pmav99,

su un argomento diverso, scritto alla tua risposta (originale) penso che non sia veramente necessario usare due sottoquery (cioè table Ae AA). Penso che uno di questi sia abbastanza.
pmav99,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.