Знайдіть загальну тривалість кожного наступного ряду рядків


11

Версія MySQL

Код буде працювати в MySQL 5.5

Фон

У мене є таблиця на зразок наступної

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;

Ця таблиця про пацієнтів у лікарні, і вона зберігає ліжка, де кожен пацієнт провів деякий час, госпіталізуючись.

У кожному відділенні може бути кілька ліжок, і кожен пацієнт може перейти на інше ліжко в межах однієї палати.

Об'єктивна

Що я хочу зробити - це знайти скільки часу кожен пацієнт провів у конкретній палаті, не переїжджаючи в інше відділення. Тобто я хочу знайти загальну тривалість часу поспіль, який він провів в одній палаті.

Тестовий випадок

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

У реальній таблиці рядки не є послідовними, але для кожного пацієнта відмітка часу відмітки з одного рядка == часова мітка прийому наступного ряду.

SQLFiddle

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

Очікуваний результат

Я хотів би написати щось таке:

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

Зверніть увагу, що ми не можемо згрупуватися за пацієнтом. Ми повинні отримати окремий запис про кожне відвідування ICU.

Якщо говорити простіше, якщо пацієнт проводить час у відділенні інтенсивної терапії, потім виходить з нього, а потім повертається туди, мені потрібно отримати загальний час, який він провів у кожному візиті у відділення інтенсивної терапії (тобто два записи)


1
+1 для красномовного запитання, чітко пояснюючи складну (і цікаву) проблему. Якби я міг два рази проголосувати за додатковий бонус SQLFiddle, я би. Однак мій інстинкт полягає в тому, що без CTE (загальні вирази таблиці) або віконних функцій це неможливо в MySQL. Яке середовище розробників ви використовуєте, тобто вас можуть зобов’язати це робити за допомогою коду.
Vérace

@ Vérace Я заявляв, що потрібно написати код, який отримує всі рядки, що відповідають ложам ICU, і я групую їх у Python.
pmav99

Звичайно, якщо це можна зробити порівняно чисто в SQL, я віддаю перевагу.
pmav99

Що стосується мов, Python досить чистий! :-) Якщо ви не зачепилися за MySQL і вам потрібна база даних F / LOSS, я можу порекомендувати PostgreSQL (багато в чому набагато перевершує MySQL IMHO), який має функції CTE та Windowing.
Vérace

Відповіді:


4

Запит 1, перевірений у 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 ;

Запит 2, який такий же, як 1, але без похідних таблиць. Ймовірно, це матиме кращий план виконання із належними індексами. Тест у 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 ;

Обидва запити передбачають, що існує унікальне обмеження (patient_id, admitted). Якщо сервер працює із суворими налаштуваннями ANSI, його bed_idслід додати до GROUP BYсписку.


Зауважте, що я змінив значення вставки у скрипці, оскільки ваші дати
виписання

2
З побоюванням - я справді думав, що це неможливо, враховуючи відсутність CTE. Як не дивно, перший запит не працював для мене в SQLFiddle - глюк? Хоча і другий, але я можу запропонувати видалити st.bed_id, оскільки він вводить в оману. Пацієнт 1 не провів усе своє перше перебування в палаті 1 в одному ліжку.
Vérace

@ Vérace, thnx. Спочатку я теж думав, що нам потрібен рекурсивний CTE. Я виправив пропущене з'єднання на pati_id (цього ніхто не помітив;) і додав вашу думку про ліжко.
ypercubeᵀᴹ

@ypercube Дякую вам за вашу відповідь! Це справді корисно. Я буду детально це вивчати :)
pmav99

0

ЗАПИТАНО ЗАПИТАННЯ

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;

Я завантажив вам вибіркові дані в локальну базу даних на моєму ноутбуці. Потім я запустив запит

ЗАПРОШЕНО ЗАПИТАННЯ ВИКОНАННЯ

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>

ЗАПРОШЕНО ПИТАННЯ, ПОЯСНЕНО

У підзапиті AA я обчислював кількість секунд, що минули за допомогою UNIX_TIMESTAMP () , віднімаючи UNIX_TIMESTAMP(discharged)FROM UNIX_TIMESTAMP(admitted). Якщо пацієнт все ще знаходиться в ліжку (на що вказує виписана людина NULL), я призначаю поточний час ЗАРАЗ () . Потім я роблю віднімання. Це дасть вам тривалість поточної хвилини для будь-якого пацієнта, який ще перебуває в палаті.

Потім, я агрегую суму секунд на patient_id. Нарешті, я беру секунди для кожного пацієнта і використовую SEC_TO_TIME () для відображення годин, хвилин та секунд перебування пацієнта.

СПРОБУВАТИ !!!


Для запису я запустив це в MySQL 5.6.22 на своєму ноутбуці Windows 7. Це дає помилку в SQL Fiddle.
RolandoMySQLDBA

1
дуже дякую за вашу відповідь. Я боюся, що це не відповідає на моє запитання; напевно, я не був досить зрозумілий у своєму описі. Я хочу отримати загальний час, витрачений на кожне перебування в диспансері. Я не хочу групуватися за пацієнтом. Якщо пацієнт проводить час у відділенні інтенсивної терапії, потім виходить з нього, а потім повертається туди, мені потрібно отримати загальний час, який він провів за кожне відвідування (тобто два записи).
pmav99

на іншу тему, wrt до вашої (оригінальної) відповіді, я думаю, що використання двох підзапитів насправді не потрібно (тобто таблиця Aта AA). Я думаю, що одного з них достатньо.
pmav99
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.