Tìm tổng thời lượng của từng chuỗi hàng liên tiếp


11

Phiên bản MySQL

Mã sẽ chạy trong MySQL 5.5

Lý lịch

Tôi có một bảng như sau

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;

Bảng này nói về bệnh nhân trong bệnh viện và nó lưu trữ những chiếc giường nơi mỗi bệnh nhân dành một chút thời gian khi nằm viện.

Mỗi phường có thể có nhiều giường và mỗi bệnh nhân có thể chuyển sang một giường khác trong cùng một phường.

Mục tiêu

Điều tôi muốn làm là tìm ra mỗi bệnh nhân dành bao nhiêu thời gian ở một phường cụ thể mà không phải chuyển đến một phòng khác. Tức là tôi muốn tìm tổng thời gian liên tiếp anh ấy dành trong cùng một phường.

Trường hợp thử nghiệm

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

Trong bảng thực, các hàng không liên tiếp nhưng đối với mỗi bệnh nhân, dấu thời gian xuất viện từ một hàng == dấu thời gian nhập học của hàng tiếp theo.

SQLFiddle

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

Kết quả mong đợi

Tôi muốn viết một cái gì đó như sau:

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

Xin vui lòng lưu ý rằng chúng tôi không thể nhóm theo BN_id. Chúng tôi phải truy xuất một bản ghi riêng cho mỗi lần truy cập ICU.

Nói rõ hơn, nếu một bệnh nhân dành thời gian ở ICU, sau đó chuyển ra khỏi đó và sau đó quay trở lại đó, tôi cần lấy lại tổng thời gian anh ta dành cho mỗi lần khám ICU (tức là hai hồ sơ)


1
+1 cho một câu hỏi hùng hồn, giải thích rõ ràng một vấn đề phức tạp (và thú vị). Nếu tôi có thể bỏ phiếu hai lần cho phần thưởng thêm của SQLFiddle, tôi sẽ làm thế. Tuy nhiên, bản năng của tôi là không có CTE (biểu thức bảng chung) hoặc các hàm cửa sổ, điều này sẽ không thể thực hiện được trong MySQL. Bạn đang sử dụng môi trường dev nào, tức là bạn có thể bắt buộc phải làm điều này thông qua mã.
Vérace

@ Vérace Tôi đã tuyên bố viết mã lấy tất cả các hàng tương ứng với giường ICU và tôi đang nhóm chúng trong Python.
pmav99

Tất nhiên nếu điều này có thể được thực hiện theo cách tương đối sạch trong SQL thì tôi sẽ thích nó hơn.
pmav99

Khi ngôn ngữ đi, Python khá sạch sẽ! :-) Nếu bạn không bị mắc kẹt với MySQL và bạn yêu cầu cơ sở dữ liệu F / LOSS, tôi có thể giới thiệu PostgreQuery (về nhiều mặt vượt trội so với MySQL IMHO) có chức năng CTE và Windowing.
Vérace 16/1/2015

Câu trả lời:


4

Truy vấn 1, được thử nghiệm trong 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 ;

Truy vấn 2, giống như 1 nhưng không có bảng dẫn xuất. Điều này có thể sẽ có kế hoạch thực hiện tốt hơn, với các chỉ mục thích hợp. Kiểm tra trong 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 ;

Cả hai truy vấn đều cho rằng có một ràng buộc duy nhất trên (patient_id, admitted). Nếu máy chủ chạy với các cài đặt ANSI nghiêm ngặt, thì bed_idnên thêm vào GROUP BYdanh sách.


Lưu ý rằng tôi sửa đổi các giá trị chèn trong fiddle, vì thải / ngày của bạn thừa nhận không phù hợp cho bệnh nhân id 1 và 2.
ypercubeᵀᴹ

2
Trong sự sợ hãi - tôi thực sự nghĩ rằng không thể thiếu CTE. Kỳ lạ thay, truy vấn đầu tiên sẽ không chạy với tôi trong SQLFiddle - một trục trặc? Cái thứ hai đã làm, nhưng tôi có thể đề nghị st.bed_id được gỡ bỏ, vì nó gây hiểu nhầm. Bệnh nhân 1 đã không dành tất cả thời gian đầu tiên ở phường 1 trên cùng một giường.
Vérace

@ Vérace, thnx. Lúc đầu, tôi cũng nghĩ rằng chúng tôi cần một CTE đệ quy. Tôi đã sửa một liên kết bị thiếu trên BN_id (mà không ai nhận thấy;) và thêm quan điểm của bạn về chiếc giường.
ypercubeᵀᴹ

@ypercube Cảm ơn bạn rất nhiều vì câu trả lời của bạn! Điều này thực sự hữu ích. Tôi sẽ nghiên cứu chi tiết này :)
pmav99

0

ĐỀ CỬ

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;

Tôi đã tải cho bạn dữ liệu mẫu vào cơ sở dữ liệu cục bộ trên máy tính xách tay của tôi. Sau đó, tôi chạy truy vấn

ĐỀ NGHỊ NHANH CHÓNG

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>

ĐỀ NGHỊ NHANH CHÓNG

Trong truy vấn con AA, tôi tính số giây trôi qua bằng UNIX_TIMESTAMP () bằng cách trừ UNIX_TIMESTAMP(discharged)TỪ UNIX_TIMESTAMP(admitted). Nếu bệnh nhân vẫn còn nằm trên giường (như được chỉ định bằng cách xuất viện NULL), tôi chỉ định thời gian hiện tại NGAY BÂY GIỜ () . Sau đó, tôi làm phép trừ. Điều này sẽ cung cấp cho bạn thời lượng tối đa cho bất kỳ bệnh nhân nào vẫn còn trong phòng bệnh.

Sau đó, tôi tổng hợp các giây bằng patient_id. Cuối cùng, tôi dành vài giây cho mỗi bệnh nhân và sử dụng SEC_TO_TIME () để hiển thị giờ, phút và giây của bệnh nhân ở lại.

HÃY THỬ MỘT LẦN !!!


Để ghi lại, tôi đã chạy nó trong MySQL 5.6.22 trên máy tính xách tay Windows 7 của tôi. Nó đưa ra một lỗi trong SQL Fiddle.
RolandoMySQLDBA

1
Cảm ơn bạn rất nhiều vì câu trả lời của bạn. Tôi sợ rằng mặc dù điều này không trả lời câu hỏi của tôi; có lẽ tôi đã không đủ rõ ràng trong mô tả của tôi. Những gì tôi muốn lấy là tổng thời gian dành cho mỗi lần lưu trú tại ICU. Tôi không muốn nhóm theo bệnh nhân. Nếu một bệnh nhân dành thời gian trong ICU, sau đó di chuyển ra khỏi đó và sau đó quay trở lại đó, tôi cần lấy lại tổng thời gian anh ta dành cho mỗi lần khám (tức là hai hồ sơ).
pmav99

về một chủ đề khác, hãy viết câu trả lời (bản gốc) của bạn Tôi nghĩ rằng việc sử dụng hai truy vấn con là không thực sự cần thiết (ví dụ: bảng AAA). Tôi nghĩ rằng một trong số họ là đủ.
pmav99
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.