連続する各行の合計期間を見つける


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

実際のテーブルでは行は連続していませんが、患者ごとに1つの行からの退院タイムスタンプ==次の行の入院タイムスタンプです。

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

患者IDでグループ化できないことに注意してください。ICUの訪問ごとに個別のレコードを取得する必要があります。

より明確に言うと、患者がICUで時間を費やしてから退院してからそこに戻った場合、各ICU訪問で費やした合計時間(つまり2つのレコード)を取得する必要があります。


1
雄弁な質問の+1。複雑な(そして興味深い)問題を明確に説明します。SQLFiddleの追加ボーナスに2回投票できるとしたら、投票します。しかし、私の本能は、CTE(一般的なテーブル式)またはウィンドウ関数がないと、MySQLではこれができないということです。どの開発環境を使用していますか。つまり、コードを通じてこれを行う必要がある場合があります。
Vérace

@VéraceICUベッドに対応するすべての行を取得するコードを記述することを述べましたが、それらをPythonでグループ化しています。
pmav99 2015年

もちろん、これがSQLで比較的クリーンな方法で実行できる場合は、それを優先します。
pmav99 2015年

言語が進むにつれて、Pythonはかなりクリーンです。:-) MySQLにこだわっておらず、F / LOSSデータベースが必要な場合は、CTEとウィンドウ機能を備えたPostgreSQL(多くの点でMySQL IMHOよりはるかに優れている)をお勧めします。
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リストに追加する必要があります。


退院/入院日が患者ID 1と2で一致しなかったため、フィドルの挿入値を変更したことに注意してください。
ypercubeᵀᴹJan

2
畏怖の念-CTEの不足を考えると、それは不可能だと本当に思っていました。奇妙なことに、最初のクエリはSQLFiddleで実行されません-グリッチ?2つ目は行いましたが、誤解を招く可能性があるため、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 2015年

1
ご回答どうもありがとうございました。しかし、これは私の質問の答えにはなりません。おそらく私は私の説明で十分に明確ではなかった。取得したいのは、ICUでの各滞在に費やした合計時間です。患者別にグループ化したくない。患者がICUで時間を費やし、それから出て行ってそこに戻った場合、各訪問で費やした合計時間(つまり2つのレコード)を取得する必要があります。
pmav99

別のトピックについては、あなたの(元の)回答に対して2つのサブクエリ(つまり、テーブルAAA)を使用する必要はないと思います。そのうちの1つで十分だと思います。
pmav99 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.