查找每个连续系列行的总持续时间


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分组。我们必须为每次ICU访问检索单独的记录。

简而言之,如果患者在ICU中花费时间,然后移出它,然后返回那里,那么我需要检索他在每次ICU访问中花费的总时间(即两条记录)


1
+1为一个雄辩的问题,清楚地说明了一个复杂(有趣)的问题。如果我能为SQLFiddle的额外奖励投票两次,我会的。但是,我的直觉是,没有CTE(公用表表达式)或窗口函数,在MySQL中将无法实现。您正在使用什么开发环境,即您可能必须通过代码来完成此工作。
Vérace

@Vérace我已经说过要编写代码,以检索与ICU床相对应的所有行,并在Python中对它们进行分组。
pmav99 2015年

当然,如果可以在SQL中以相​​对干净的方式完成此操作,我会更喜欢它。
pmav99

随着语言的发展,Python很干净!:-)如果您不拘泥于MySQL并且需要F / LOSS数据库,我是否可以推荐具有CTE和Windowing功能的PostgreSQL(在很多方面都大大优于MySQL IMHO)。
Vérace

Answers:


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ᵀᴹ2015年

2
令人敬畏的是-我真的认为,鉴于缺少CTE,这是不可能的。奇怪的是,第一个查询不会在SQLFiddle中为我运行-小故障?尽管第二个确实做到了,但是我建议删除st.bed_id,因为这会引起误解。患者1并没有将他的所有第一次住宿都花在同一张病床上。
Vérace

@Vérace,thnx。一开始,我也认为我们需要递归CTE。我已更正了对Patient_id的缺失连接(没有人注意到;),并添加了您关于床的观点。
ypercubeᵀᴹ

@ypercube非常感谢您的回答!这真的很有帮助。我将对此进行详细研究:)
pmav99 2015年

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中,我通过减去FROM 来计算使用UNIX_TIMESTAMP()所经过的秒数。如果患者仍在床上(如出院时间所示),则指定当前时间NOW()。然后,我做减法。对于仍在病房中的任何患者,这将为您提供最新的持续时间。UNIX_TIMESTAMP(discharged)UNIX_TIMESTAMP(admitted)NULL

然后,我将秒的总和乘以patient_id。最后,我为每个患者花费几秒钟,并使用SEC_TO_TIME()显示患者住院的小时,分​​钟和秒钟。

试试看 !!!


作为记录,我在Windows 7笔记本电脑上的MySQL 5.6.22中运行了它。它在SQL Fiddle中给出错误。
RolandoMySQLDBA 2015年

1
非常感谢您的回答。恐怕这不能回答我的问题;可能我的描述不够清楚。我要检索的是每次入住ICU的总时间。我不想按病人分组。如果患者在ICU中花费时间,然后移出它,然后返回那里,我需要检索他每次就诊所花费的总时间(即两条记录)。
pmav99

在另一个主题上,请回答您的(原始)答案,我认为实际上没有必要使用两个子查询(即table AAA)。我认为其中之一就足够了。
pmav99
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.