Trouver la durée totale de chaque série consécutive de lignes

11

Version MySQL

Le code s'exécutera dans MySQL 5.5

Contexte

J'ai une table comme la suivante

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;

Ce tableau concerne les patients d'un hôpital et il stocke les lits où chaque patient a passé un certain temps pendant son hospitalisation.

Chaque salle peut avoir plusieurs lits et chaque patient peut se déplacer vers un lit différent dans la même salle.

Objectif

Ce que je veux faire, c'est trouver combien de temps chaque patient a passé dans une salle spécifique sans avoir déménagé dans une autre salle. C'est-à-dire que je veux trouver la durée totale du temps consécutif qu'il a passé dans la même salle.

Cas de test

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

Dans le tableau réel, les lignes ne sont pas consécutives, mais pour chaque patient, l'horodatage de sortie d'une ligne == l'horodatage d'admission de la ligne suivante.

SQLFiddle

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

résultat attendu

Je voudrais écrire quelque chose comme ceci:

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

Veuillez noter que nous ne pouvons pas regrouper par patient_id. Nous devons récupérer un dossier distinct pour chaque visite aux soins intensifs.

Pour le dire plus clairement, si un patient passe du temps à l'USI, puis en sort et y retourne, j'ai besoin de récupérer le temps total qu'il a passé à chaque visite en USI (c.-à-d. Deux enregistrements)

pmav99
la source
1
+1 pour une question éloquente, expliquant clairement un problème complexe (et intéressant). Si je pouvais voter deux fois pour le bonus supplémentaire d'un SQLFiddle, je le ferais. Cependant, mon instinct est que sans les CTE (expressions de table communes) ou les fonctions de fenêtrage, cela ne sera pas possible dans MySQL. Quel environnement de développement utilisez-vous, c'est-à-dire que vous pourriez être obligé de le faire via du code.
Vérace
@ Vérace J'ai dit d'écrire du code qui récupère toutes les lignes qui correspondent aux lits ICU et je les regroupe en Python.
pmav99
Bien sûr, si cela peut être fait de manière relativement propre en SQL, je le préférerai.
pmav99
Au fur et à mesure que les langues disparaissent, Python est assez propre! :-) Si vous n'êtes pas coincé avec MySQL et que vous avez besoin d'une base de données F / LOSS, puis-je recommander PostgreSQL (à bien des égards largement supérieur à MySQL IMHO) qui a des CTE et des fonctions de fenêtrage.
Vérace

Réponses:

4

Requête 1, testée dans 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 ;

Requête 2, qui est identique à 1 mais sans les tables dérivées. Cela aura probablement un meilleur plan d'exécution, avec des index appropriés. Test dans 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 ;

Les deux requêtes supposent qu'il existe une contrainte unique sur (patient_id, admitted). Si le serveur fonctionne avec des paramètres ANSI stricts, le bed_iddoit être ajouté dans la GROUP BYliste.

ypercubeᵀᴹ
la source
Notez que j'ai modifié les valeurs d'insertion dans le violon, car vos dates de sortie / d'admission ne correspondaient pas aux identifiants de patient 1 et 2.
ypercubeᵀᴹ
2
Dans la crainte - j'ai vraiment pensé que c'était impossible étant donné le manque de CTE. Étrangement, la première requête ne s'exécuterait pas pour moi dans SQLFiddle - un problème? Le deuxième l'a fait, mais puis-je suggérer que le st.bed_id soit supprimé, car il est trompeur. Le patient 1 n'a pas passé la totalité de son premier séjour dans le service 1 dans le même lit.
Vérace
@ Vérace, thnx. Au début, je pensais aussi que nous avions besoin d'un CTE récursif. J'ai corrigé une jointure manquante sur patient_id (que personne n'a remarqué;) et ajouté votre point sur le lit.
ypercubeᵀᴹ
@ypercube Merci beaucoup pour votre réponse! C'est vraiment utile. Je vais étudier cela en détail :)
pmav99
0

DEMANDE PROPOSÉE

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;

Je vous ai chargé des échantillons de données dans une base de données locale sur mon ordinateur portable. Ensuite, j'ai exécuté la requête

REQUÊTE PROPOSÉE EXÉCUTÉE

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>

REQUÊTE PROPOSÉE EXPLIQUÉE

Dans la sous-requête AA, je calcule le nombre de secondes écoulées à l'aide d' UNIX_TIMESTAMP () en soustrayant UNIX_TIMESTAMP(discharged)FROM UNIX_TIMESTAMP(admitted). Si le patient est toujours dans le lit (comme indiqué par le congé NULL), j'attribue l'heure actuelle MAINTENANT () . Ensuite, je fais la soustraction. Cela vous donnera une durée à la minute pour tout patient encore dans la salle.

Ensuite, j'agrège la somme des secondes par patient_id. Enfin, je prends les secondes pour chaque patient et j'utilise SEC_TO_TIME () pour afficher les heures, les minutes et les secondes du séjour du patient.

ESSAIE !!!

RolandoMySQLDBA
la source
Pour mémoire, j'ai exécuté ceci dans MySQL 5.6.22 sur mon ordinateur portable Windows 7. Il donne une erreur dans SQL Fiddle.
RolandoMySQLDBA
1
Merci beaucoup pour votre réponse. Je crains cependant que cela ne réponde pas à ma question; je n'étais probablement pas assez clair dans ma description. Ce que je veux récupérer, c'est le temps total passé sur chaque séjour en USI. Je ne veux pas grouper par patient. Si un patient passe du temps à l'USI, puis en sort et y retourne, j'ai besoin de récupérer le temps total qu'il a passé à chaque visite (c'est-à-dire deux enregistrements).
pmav99
sur un sujet différent, par rapport à votre réponse (originale), je pense que l'utilisation de deux sous-requêtes n'est pas vraiment nécessaire (ie table Aet AA). Je pense que l'un d'eux suffit.
pmav99