ค้นหาระยะเวลารวมของแต่ละแถวที่ต่อเนื่องกัน


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

โปรดทราบว่าเราไม่สามารถจัดกลุ่มโดย patient_id เราต้องดึงข้อมูลบันทึกแยกต่างหากสำหรับการเยี่ยมชมห้องไอซียูแต่ละครั้ง

หากผู้ป่วยใช้เวลาในห้องไอซียูแล้วย้ายออกจากห้องแล้วกลับมาที่นั่นฉันต้องเรียกเวลาทั้งหมดที่เขาใช้ในการเยี่ยมชมห้องไอซียูแต่ละครั้ง (เช่นสองบันทึก)


1
+1 สำหรับคำถามฝีปากอธิบายอย่างชัดเจนถึงปัญหาที่ซับซ้อน (และน่าสนใจ) ถ้าฉันสามารถโหวตมันสองครั้งเพื่อรับโบนัสเพิ่มของ SQLFiddle ฉันก็จะ อย่างไรก็ตามสัญชาตญาณของฉันคือถ้าไม่มีฟังก์ชั่น CTE (นิพจน์ทั่วไป) หรือการเรียงหน้าต่างจะไม่สามารถทำได้ใน MySQL คุณกำลังใช้สภาพแวดล้อม dev อะไรเช่นคุณอาจจำเป็นต้องทำสิ่งนี้ผ่านโค้ด
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รายการ


โปรดทราบว่าฉันปรับเปลี่ยนค่าการแทรกในซอเพราะวันที่ปล่อย / ยอมรับของคุณไม่ตรงกับรหัสผู้ป่วย 1 และ 2
ypercubeᵀᴹ

2
ด้วยความหวาดกลัว - ฉันคิดจริงๆว่ามันเป็นไปไม่ได้เพราะขาด CTEs แปลกแล้วคำถามแรกจะไม่ทำงานสำหรับฉันใน SQLFiddle - ความผิดพลาดหรือไม่? แม้ว่าอันที่สองทำไปแล้วแต่ขอแนะนำให้ลบ st.bed_id ออกเพราะมันทำให้เข้าใจผิด ผู้ป่วย 1 ไม่ได้ใช้เวลาพักครั้งแรกทั้งหมดในหอผู้ป่วย 1 ในเตียงเดียวกัน
Vérace

@ Véraceขอบคุณ ตอนแรกฉันก็คิดเหมือนกันว่าเราต้องการ CTE แบบเรียกซ้ำ ฉันแก้ไขการเข้าร่วมผู้ป่วยที่หายไป (ซึ่งไม่มีใครสังเกตเห็น) และเพิ่มประเด็นของคุณเกี่ยวกับเตียง
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) UNIX_TIMESTAMP(admitted)ถ้าผู้ป่วยยังคงอยู่ในเตียง (ตามที่ระบุโดยถูกปลดออกNULL) ผมกำหนดเวลาปัจจุบันNOW () จากนั้นฉันก็ลบออก สิ่งนี้จะช่วยให้คุณมีช่วงเวลาล่าสุดสำหรับผู้ป่วยที่ยังอยู่ในวอร์ด

patient_idแล้วฉันรวมผลรวมของวินาทีโดย ในที่สุดฉันใช้เวลาเป็นวินาทีสำหรับผู้ป่วยแต่ละรายและใช้SEC_TO_TIME ()เพื่อแสดงชั่วโมงนาทีและวินาทีของการเข้าพักของผู้ป่วย

ให้มันลอง !!!


สำหรับการบันทึกฉันใช้งานสิ่งนี้ใน MySQL 5.6.22 บนแล็ปท็อป Windows 7 ของฉัน มันทำให้เกิดข้อผิดพลาดใน SQL Fiddle
RolandoMySQLDBA

1
ขอบคุณมากสำหรับคำตอบของคุณ. ฉันกลัวว่านี่จะไม่ตอบคำถามของฉัน ฉันอาจจะไม่ชัดเจนพอในคำอธิบายของฉัน สิ่งที่ฉันต้องการเรียกคืนคือเวลาทั้งหมดที่ใช้ในการเข้าพักแต่ละครั้งใน ICU ฉันไม่ต้องการที่จะจัดกลุ่มโดยผู้ป่วย หากผู้ป่วยใช้เวลาในห้องไอซียูให้ย้ายออกจากห้องแล้วกลับมาที่นั่นฉันต้องเรียกเวลาทั้งหมดที่เขาใช้ในการเยี่ยมแต่ละครั้ง (เช่นสองบันทึก)
pmav99

ในหัวข้อที่แตกต่าง wrt ไปยังคำตอบ (ต้นฉบับ) ของคุณฉันคิดว่าการใช้สองแบบสอบถามย่อยไม่จำเป็นจริงๆ (เช่นตารางAและAA) ฉันคิดว่าหนึ่งในนั้นเพียงพอ
pmav99
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.