연속 된 각 행의 총 지속 시간 찾기


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에서 나가서 다시 돌아 오면, ICU를 방문 할 때마다 보낸 총 시간 (예 : 두 개의 레코드)을 검색해야합니다.


1
복잡하고 흥미로운 문제를 명확하게 설명하는 웅변적인 질문에 +1 SQLFiddle의 추가 보너스에 대해 두 번 투표 할 수 있다면 그렇게 할 것입니다. 그러나 내 본능은 CTE (공통 테이블 표현식) 또는 윈도우 기능이 없으면 MySQL에서는 불가능하다는 것입니다. 어떤 개발 환경을 사용하고 있습니까? 즉, 코드를 통해이를 수행해야 할 수도 있습니다.
Vérace

@ Vérace ICU 침대에 해당하는 모든 행을 검색하는 코드를 작성한다고 말했으며 파이썬으로 그룹화하고 있습니다.
pmav99

물론 이것이 SQL에서 비교적 깨끗한 방식으로 수행 될 수 있다면 선호합니다.
pmav99

언어가 갈수록 파이썬은 매우 깨끗합니다! :-) MySQL에 집착하지 않고 F / LOSS 데이터베이스가 필요한 경우 CTE 및 Windowing 기능이있는 PostgreSQL (MySQL IMHO보다 훨씬 우수)을 권장 할 수 있습니다.
Vérace 2016 년

답변:


4

SQLFiddle-1 에서 테스트 한 쿼리 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목록에 추가해야 합니다.


퇴원 / 입원 날짜가 환자 ID 1 및 2와 일치하지 않기 때문에 바이올린의 삽입 값을 수정했습니다.
ypercubeᵀᴹ

2
놀랍게도-CTE가 없기 때문에 불가능하다고 생각했습니다. 이상하게도 첫 번째 쿼리는 SQLFiddle에서 실행되지 않습니다-결함? 두 번째 방법은 있지만 st.bed_id가 잘못되어 제거 될 것을 제안 할 수 있습니다. 환자 1은 같은 침대에서 병동 1의 첫 번째 체류를 모두 소비하지 않았습니다.
Vérace

@ Vérace, thnx. 처음에는 재귀 CTE가 필요하다고 생각했습니다. patient_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에서 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

1
대답 해 주셔서 감사합니다. 이것이 내 질문에 대답하지 못하는 것이 두렵습니다. 아마도 내 설명에 충분하지 않았을 것입니다. 내가 검색하고 싶은 것은 ICU에 머무를 때마다 보낸 총 시간입니다. 환자별로 그룹화하고 싶지 않습니다. 환자가 중환자 실에서 시간을 보낸 후 환자가 퇴원 한 다음 다시 돌아 오면 방문 할 때마다 보낸 총 시간 (예 : 두 개의 레코드)을 검색해야합니다.
pmav99

다른 주제에서, (원래) 대답으로 두 개의 하위 쿼리를 사용하는 것이 실제로 필요하지 않다고 생각합니다 (예 : 테이블 AAA). 나는 그들 중 하나가 충분하다고 생각합니다.
pmav99
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.