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.