Temukan total durasi setiap seri baris berturut-turut

11

Versi MySQL

Kode akan berjalan di MySQL 5.5

Latar Belakang

Saya punya tabel seperti yang berikut ini

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;

Tabel ini adalah tentang pasien di rumah sakit dan menyimpan tempat tidur di mana setiap pasien menghabiskan beberapa waktu saat dirawat di rumah sakit.

Setiap bangsal dapat memiliki beberapa tempat tidur dan setiap pasien dapat pindah ke ranjang yang berbeda dalam bangsal yang sama.

Objektif

Yang ingin saya lakukan adalah menemukan berapa banyak waktu yang dihabiskan setiap pasien di bangsal tertentu tanpa harus pindah ke bangsal lain. Yaitu saya ingin menemukan total durasi dari waktu yang ia habiskan bersama dalam lingkungan yang sama.

Kasus cobaan

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

Dalam tabel sebenarnya, baris tidak berturut-turut tetapi untuk setiap pasien cap waktu keluar dari satu baris == cap waktu penerimaan baris berikutnya.

SQLFiddle

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

Hasil yang diharapkan

Saya ingin menulis sesuatu seperti ini:

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

Harap perhatikan bahwa kami tidak dapat mengelompokkan berdasarkan patient_id. Kami harus mengambil catatan terpisah untuk setiap kunjungan ICU.

Untuk lebih jelasnya, jika seorang pasien menghabiskan waktu di ICU, kemudian pindah dari sana dan kemudian kembali ke sana, saya perlu mengambil total waktu yang dihabiskan dalam setiap kunjungan ICU (yaitu dua catatan)

pmav99
sumber
1
+1 untuk pertanyaan yang fasih, dengan jelas menjelaskan masalah yang kompleks (dan menarik). Jika saya bisa memilih dua kali untuk bonus tambahan dari SQLFiddle, saya akan melakukannya. Namun, insting saya adalah bahwa tanpa CTE (ekspresi tabel umum) atau fungsi windowing, ini tidak akan mungkin di MySQL. Lingkungan pengembang apa yang Anda gunakan, yaitu Anda mungkin harus melakukan ini melalui kode.
Vérace
@ Vérace Saya telah menyatakan untuk menulis kode yang mengambil semua baris yang berhubungan dengan ICU beds dan saya mengelompokkannya dalam Python.
pmav99
Tentu saja jika ini dapat dilakukan dengan cara yang relatif bersih dalam SQL, saya akan lebih menyukainya.
pmav99
Seperti bahasa, Python cukup bersih! :-) Jika Anda tidak terjebak pada MySQL dan Anda memerlukan database F / LOSS, mungkin saya merekomendasikan PostgreSQL (dalam banyak hal jauh lebih unggul dari MySQL IMHO) yang memang memiliki fungsi CTE dan Windowing.
Vérace

Jawaban:

4

Kueri 1, diuji dalam 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 ;

Kueri 2, yang sama dengan 1 tetapi tanpa tabel turunan. Ini mungkin akan memiliki rencana eksekusi yang lebih baik, dengan indeks yang tepat. Uji dalam 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 ;

Kedua pertanyaan mengasumsikan bahwa ada batasan unik (patient_id, admitted). Jika server berjalan dengan pengaturan ANSI yang ketat, bed_idharus ditambahkan dalam GROUP BYdaftar.

ypercubeᵀᴹ
sumber
Perhatikan bahwa saya memodifikasi nilai sisipan di biola, karena tanggal keluar / masuk Anda tidak cocok dengan id pasien 1 dan 2.
ypercubeᵀᴹ
2
Dalam kekaguman - saya benar-benar berpikir bahwa itu tidak mungkin mengingat kurangnya CTE. Anehnya, kueri pertama tidak akan berjalan untuk saya di SQLFiddle - kesalahan? Yang kedua memang, tetapi mungkin saya menyarankan bahwa st.bed_id dihapus, karena menyesatkan. Pasien 1 tidak menghabiskan semua tinggal pertamanya di bangsal 1 di tempat tidur yang sama.
Vérace
@ Vérace, thnx. Pada awalnya, saya pikir juga, bahwa kami membutuhkan CTE rekursif. Saya telah mengoreksi gabungan yang hilang pada patient_id (yang tidak diketahui oleh siapa pun;) dan menambahkan poin Anda tentang ranjang.
ypercubeᵀᴹ
@ ypercube Terima kasih banyak atas jawaban Anda! Ini sangat membantu. Saya akan mempelajari ini secara terperinci :)
pmav99
0

QUERY YANG DIUSULKAN

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;

Saya memuat Anda sampel data ke basis data lokal di laptop saya. Lalu, saya menjalankan kueri

SEGERA DIUSULKAN DIAJARKAN

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>

USULAN QUERY DIJELASKAN

Dalam subquery AA, saya menghitung jumlah detik yang berlalu menggunakan UNIX_TIMESTAMP () dengan mengurangi UNIX_TIMESTAMP(discharged)FROM UNIX_TIMESTAMP(admitted). Jika pasien masih di tempat tidur (seperti yang ditunjukkan oleh makhluk yang keluar NULL), saya menetapkan waktu saat ini SEKARANG () . Lalu, saya lakukan pengurangan. Ini akan memberi Anda durasi terkini untuk setiap pasien yang masih di bangsal.

Kemudian, saya menjumlahkan jumlah detik dengan patient_id. Akhirnya, saya mengambil detik untuk setiap pasien dan menggunakan SEC_TO_TIME () untuk menampilkan jam, menit, dan detik pasien tinggal.

COBALAH !!!

RolandoMySQLDBA
sumber
Sebagai catatan, saya menjalankan ini di MySQL 5.6.22 di laptop Windows 7 saya. Ini memberikan kesalahan dalam SQL Fiddle.
RolandoMySQLDBA
1
terimakasih banyak atas jawaban Anda. Namun saya khawatir ini tidak menjawab pertanyaan saya; mungkin saya tidak cukup jelas dalam uraian saya. Apa yang ingin saya ambil adalah total waktu yang dihabiskan untuk setiap tinggal di ICU. Saya tidak ingin dikelompokkan berdasarkan pasien. Jika seorang pasien menghabiskan waktu di ICU, kemudian pindah dari sana dan kemudian kembali ke sana, saya perlu mengambil total waktu yang dihabiskannya dalam setiap kunjungan (yaitu dua catatan).
pmav99
pada topik yang berbeda, wrt ke jawaban Anda (asli) Saya pikir menggunakan dua subquery tidak terlalu diperlukan (yaitu tabel Adan AA). Saya pikir salah satu dari mereka sudah cukup.
pmav99