Znajdź całkowity czas trwania każdej kolejnej serii wierszy

11

Wersja MySQL

Kod będzie działał w MySQL 5.5

tło

Mam tabelę podobną do poniższej

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;

Ta tabela dotyczy pacjentów w szpitalu i zawiera łóżka, na których każdy pacjent spędził trochę czasu podczas hospitalizacji.

Każdy oddział może mieć wiele łóżek, a każdy pacjent może przenieść się do innego łóżka w tym samym oddziale.

Cel

Chcę ustalić, ile czasu każdy pacjent spędził na określonym oddziale, nie przeprowadzając się na inny oddział. Tj. Chcę znaleźć całkowity czas z rzędu, jaki spędził w obrębie tego samego totemu.

Przypadek testowy

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

W prawdziwej tabeli wiersze nie są następujące po sobie, ale dla każdego pacjenta znacznik czasu wypisu z jednego rzędu == znacznik czasu przyjęcia następnego rzędu.

SQLFiddle

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

Spodziewany wynik

Chciałbym napisać coś takiego:

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

Proszę pamiętać, że nie możemy grupować według ID_ pacjenta. Musimy pobrać osobny rekord dla każdej wizyty na OIOM.

Mówiąc prościej, jeśli pacjent spędza czas na OIOM, następnie się z niego wyprowadza, a następnie wraca z powrotem, muszę odzyskać całkowity czas spędzony podczas każdej wizyty na OIOM (tj. Dwa zapisy)

pmav99
źródło
1
+1 za elokwentne pytanie, jasno wyjaśniające złożony (i interesujący) problem. Gdybym mógł dwukrotnie zagłosować za dodatkową premią SQLFiddle, zrobiłbym to. Jednak moim instynktem jest to, że bez CTE (wspólnych wyrażeń tabelowych) lub funkcji okienkowania nie będzie to możliwe w MySQL. Z jakiego środowiska programistycznego korzystasz, tzn. Możesz być zobowiązany do wykonania tego za pomocą kodu.
Vérace
@ Vérace Powiedziałem, aby napisać kod, który pobiera wszystkie wiersze, które odpowiadają łóżkom OIOM, i grupuję je w Pythonie.
pmav99
Oczywiście, jeśli można to zrobić w stosunkowo czysty sposób w SQL, wolę to.
pmav99
Z biegiem języków Python jest całkiem czysty! :-) Jeśli nie utkniesz w MySQL i potrzebujesz bazy danych F / LOSS, czy mogę polecić PostgreSQL (pod wieloma względami znacznie lepszy od MySQL IMHO), który ma funkcje CTE i okienkowe.
Vérace

Odpowiedzi:

4

Zapytanie 1, przetestowane w 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 ;

Zapytanie 2, które jest takie samo jak 1, ale bez pochodnych tabel. Zapewne będzie to miał lepszy plan wykonania z odpowiednimi indeksami. Test w 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 ;

Oba zapytania zakładają, że istnieje wyjątkowe ograniczenie (patient_id, admitted). Jeśli serwer działa ze ścisłymi ustawieniami ANSI, bed_idnależy go dodać na GROUP BYliście.

ypercubeᵀᴹ
źródło
Zauważ, że zmodyfikowałem wartości wstawek w skrzypcach, ponieważ twoje daty zwolnienia / przyjęcia nie pasowały do ​​pacjentów o identyfikatorach 1 i 2.
ypercubeᵀᴹ
2
W zachwycie - naprawdę pomyślałem, że jest to niemożliwe, biorąc pod uwagę brak CTE. O dziwo, pierwsze zapytanie nie uruchomiłoby się dla mnie w SQLFiddle - usterka? Drugi jednak tak zrobił, ale mogę zasugerować usunięcie st.bed_id, ponieważ jest to mylące. Pacjent 1 nie spędził całego swojego pierwszego pobytu na oddziale 1 w tym samym łóżku.
Vérace,
@ Vérace, thnx. Na początku też myślałem, że potrzebujemy rekurencyjnego CTE. Poprawiłem brakujące sprzężenie na ID_ pacjenta (którego nikt nie zauważył;) i dodałem swój punkt widzenia na temat łóżka.
ypercubeᵀᴹ
@ypercube Dziękuję bardzo za odpowiedź! To jest naprawdę pomocne. Szczegółowo to przestudiuję :)
pmav99
0

PROPONOWANE ZAPYTANIE

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;

Załadowałem ci przykładowe dane do lokalnej bazy danych na moim laptopie. Następnie uruchomiłem zapytanie

PROPONOWANE WYKONANE ZAPYTANIE

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>

PROPONOWANE WYJAŚNIENIE

W podzapytaniu AA obliczam liczbę sekund, które upłynęły za pomocą UNIX_TIMESTAMP () , odejmując UNIX_TIMESTAMP(discharged)FROM UNIX_TIMESTAMP(admitted). Jeśli pacjent nadal leży w łóżku (jak wskazuje na to NULL, że jest wypisany ), przypisuję bieżący czas TERAZ () . Następnie odejmuję. To da ci maksymalny czas trwania dla każdego pacjenta nadal na oddziale.

Następnie sumuję sekundy według patient_id. Na koniec pobieram sekundy dla każdego pacjenta i używam SEC_TO_TIME () do wyświetlania godzin, minut i sekund pozostawania pacjenta.

SPRÓBUJ !!!

RolandoMySQLDBA
źródło
Dla przypomnienia uruchomiłem to w MySQL 5.6.22 na moim laptopie z systemem Windows 7. Daje błąd w SQL Fiddle.
RolandoMySQLDBA
1
bardzo dziękuję za odpowiedź. Obawiam się jednak, że to nie odpowiada na moje pytanie; prawdopodobnie nie byłem wystarczająco jasny w moim opisie. Chcę odzyskać całkowity czas spędzony na każdym pobycie na OIOM-ie. Nie chcę grupować według pacjentów. Jeśli pacjent spędza czas na OIOM, następnie się z niego wydostaje, a następnie wraca z powrotem, muszę odzyskać całkowity czas spędzony podczas każdej wizyty (tj. Dwa zapisy).
pmav99
na inny temat, napisz do swojej (oryginalnej) odpowiedzi. Myślę, że użycie dwóch podkwerend nie jest tak naprawdę konieczne (tj. tabela Ai AA). Myślę, że jeden z nich wystarczy.
pmav99