6

私は、人、病気、および薬に関する情報を含む次のデータベーステーブルを持っています。

PERSON_T              DISEASE_T               DRUG_T
=========             ==========              ========
PERSON_ID             DISEASE_ID              DRUG_ID
GENDER                PERSON_ID               PERSON_ID
NAME                  DISEASE_START_DATE      DRUG_START_DATE
                      DISEASE_END_DATE        DRUG_END_DATE

これらの表から、どの個人がどの薬を服用し、どの病気にかかったかについての統計を実行します。これから、どのパターンが私にとってさらに掘り下げて興味深いかを理解することができます。たとえば、以下は、病気52で見つかる可能性のあるブールパターンの簡略化された例です。

( (Drug 234 = false AND Drug 474 = true AND Drug 26 = false) OR 
  (Drug 395 = false AND Drug 791 = false AND Drug 371 = true) )

編集: 別の例を次に示します。

( (Drug 234 = true AND Drug 474 = true AND Drug 26 = false) OR 
      (Drug 395 = false AND Drug 791 = false AND Drug 371 = true) )

次に、このパターンをSQLクエリに変換して、このパターンに一致するすべての人を見つけたいと思います。
たとえば、PERSON_Tで、病気にかかっていて((症状が現れる前に薬234と26を服用しなかったが、症状が現れる前に薬474を服用した)または(症状が現れる前に薬371を服用した)すべての人を見つけたいと思います。 、しかし症状を示す前に薬物791および395ではない))

このパターンを元のクエリに戻すにはどうすればよいですか?

これが私の最初の試みですが、私は最初の用語で立ち往生しています:

SELECT * FROM PERSON_T, DRUG_T, DISEASE_T 
  WHERE DISEASE_ID = 52 AND 
    PERSON_T.PERSON_ID = DISEASE_T.PERSON_ID AND 
    PERSON_T.PERSON_ID = DRUG_T.PERSON_ID  AND 
    (DRUG_T.DRUG_ID=234 AND (DRUG_T.DRUG_START_DATE>DISEASE_T.END_DATE || ???)

PostgreSqlで機能するにはこれが必要ですが、特定の回答を特定のデータベースからPostgreSqlに変換できると思います。

コメントへの回答

  1. データベーステーブルのフォーマットを修正しました。ありがとうございました。
  2. 任意のブール式を取り、それをSQLに変換できる必要があります。実際に作成しているブールステートメントは、私が示した例よりもはるかに長くなっています。私が作成する新しいテーブルはすべて新しいデータベースにあり、元のテーブルと同じスキーマを持っている必要があります。このようにしてエンドユーザーは、新しいテーブルで同じコードを実行でき、元のテーブルで実行した場合と同じように機能します。これはお客様からの要求です。元のテーブルへの単なるクエリであるビューを作成できることを望んでいます。それが機能しない場合は、テーブルのコピーを作成し、新しいテーブルにコピーするときにデータをフィルタリングすることがあります。分析にはニューラルネットワークを使用していません。ニューラルネットワークよりもはるかに優れた拡張性を備えた独自のカスタムアルゴリズムを使用しています。
  3. Disease_Start_Dateは、人が病気にかかった日付であり、症状が現れ始めたときに発生する可能性があります。Disease_End_Dateは、人が回復したときであり、症状が消えたときに発生する可能性があります。
  4. Drug_start_dateは、その人が薬の服用を開始したときです。Drug_end_dateは、人が薬の服用をやめたときです。

編集 私は自分の答えを追加しました。誰かがもっと簡単な答えを思い付くことができますか?

4

10 に答える 10

4

私にとって、(醜い場合の)簡単な解決策は、 EXISTS 句と NOT EXISTS 句を使用することです。

SELECT *
FROM PERSON_T INNER JOIN DISEASE_T
     USING (PERSON_ID)
WHERE DISEASE_ID = 52
  AND EXISTS (SELECT 1 FROM DRUG_T
              WHERE DRUG_T.PERSON_ID = PERSON_T.PERSON_ID
                AND DRUG_ID = 474
                AND [time condition])
  AND NOT EXISTS (SELECT 1 FROM DRUG_T
              WHERE DRUG_T.PERSON_ID = PERSON_T.PERSON_ID
                AND DRUG_ID = 234
                AND [time condition])

...等々。この例では、ドラッグ 474 を服用しているが 234 を服用していない人を求めています。明らかに、必要に応じて AND と OR を使用して句をグループ化できます。

余談ですが、すべて大文字は読みにくいと思います。通常、SQL キーワードには大文字を使用し、テーブルと列の名前には小文字を使用します。

于 2010-07-09T18:54:47.073 に答える
1

これが大きなテーブルでどのように機能するかはわかりませんが(日付の比較は通常かなり高価なので、かなりお粗末になると思います)、これが機能するはずの方法です。比較的冗長ですが、さまざまなブールケースに合わせて変更するのは非常に簡単です。

例1:

SELECT dis.*
FROM disease_t dis
LEFT JOIN drug d1 ON d1.person_id = dis.person_id AND d1.drug_id = 234
LEFT JOIN drug d2 ON d2.person_id = dis.person_id AND d2.drug_id = 474
LEFT JOIN drug d3 ON d3.person_id = dis.person_id AND d3.drug_id = 26
LEFT JOIN drug d4 ON d4.person_id = dis.person_id AND d4.drug_id = 395
LEFT JOIN drug d5 ON d5.person_id = dis.person_id AND d5.drug_id = 791
LEFT JOIN drug d6 ON d6.person_id = dis.person_id AND d6.drug_id = 371
WHERE dis.disease_id = 52
AND (((d1.person_id IS NULL OR dis.startdate < d1.startdate) AND
      (d2.person_id IS NOT NULL AND d2.startdate < dis.startdate) AND
      (d3.person_id IS NULL OR dis.startdate < d3.startdate)) 
     OR
     ((d4.person_id IS NULL OR dis.startdate < d4.startdate) AND
      (d5.person_id IS NULL OR dis.startdate < d5.startdate) AND
      (d6.person_id IS NOT NULL AND d6.startdate < dis.startdate)))

例2:

SELECT dis.*
FROM disease_t dis
LEFT JOIN drug d1 ON d1.person_id = dis.person_id AND d1.drug_id = 234
LEFT JOIN drug d2 ON d2.person_id = dis.person_id AND d2.drug_id = 474
LEFT JOIN drug d3 ON d3.person_id = dis.person_id AND d3.drug_id = 26
LEFT JOIN drug d4 ON d4.person_id = dis.person_id AND d4.drug_id = 395
LEFT JOIN drug d5 ON d5.person_id = dis.person_id AND d5.drug_id = 791
LEFT JOIN drug d6 ON d6.person_id = dis.person_id AND d6.drug_id = 371
WHERE dis.disease_id = 52
AND (((d1.person_id IS NOT NULL AND d1.startdate < dis.startdate) AND
      (d2.person_id IS NOT NULL AND d2.startdate < dis.startdate) AND
      (d3.person_id IS NULL OR dis.startdate < d3.startdate)) 
     or
     ((d4.person_id IS NULL OR dis.startdate < d4.startdate) AND
      (d5.person_id IS NULL OR dis.startdate < d5.startdate) AND
      (d6.person_id IS NOT NULL AND d6.startdate < dis.startdate)))
于 2010-07-23T18:57:12.023 に答える
1

( (Drug 234 = true AND Drug 474 = true AND Drug 26 = false) OR (Drug 395 = false AND Drug 791 = false AND Drug 371 = true) )投稿したように、を処理するクエリを次に示します。

/*
-- AS DEFINED BY JOINS
-- All "person_id"'s match
-- Drug 1 is not Drug 2
-- Drug 1 is not Drug 3
-- Drug 2 is not Drug 3
-- All Drugs are optional as far as the SELECT statement is concerned (left join)
   -- Drug IDs will be defined in the WHERE clause
-- All Diseases for "person_id"

-- AS DEFINED IN WHERE STATEMENT
-- Disease IS 52
-- AND ONE OF THE FOLLOWING:
--   1) Disease started AFTER Drug 1
--      Disease started AFTER Drug 2
--      Drug 1 IS 234
--      Drug 2 IS 474
--      Drug 3 IS NOT 26 (AND NOT 234 or 474, as defined in JOINs)
--   2) Disease started AFTER Drug 3
--      Drug 1 IS NOT 395
--      Drug 2 IS NOT 791
--      Drug 3 IS 371
*/

SELECT p.person_id, p.gender FROM person_t as p
LEFT JOIN drug_t    AS dr1 ON (p.person_id = dr1.person_id)
LEFT JOIN drug_t    AS dr2 ON (p.person_id = dr2.person_id AND dr1.drug_id != dr2.drug_id)
LEFT JOIN drug_t    AS dr3 ON (p.person_id = dr3.person_id AND dr1.drug_id != dr3.drug_id AND dr2.drug_id != dr3.drug_id)
JOIN      disease_t AS ds  ON (p.person_id = ds.person_id)
WHERE ds.disease_id = 52
AND (   (    (dr1.drug_start_date < ds.disease_start_date AND dr2.drug_start_date < ds.disease_start_date)
        AND (dr1.drug_id = 234 AND dr2.drug_id = 474 AND dr3.drug_id != 26)
        )
    OR
        (    (dr3.drug_start_date < ds.disease_start_date)
        AND (dr1.drug_id != 395 AND dr2.drug_id != 791 AND dr3.drug_id = 371)
        )
    )
于 2010-07-23T19:37:53.990 に答える
0

私は問題を分解し、可能な限り論理的にフォローしようとしました。

まず、3つのテーブル(Person_T、Drugs_T、Disease_T)は、図1.0に示すように考えることができます。

人は複数の薬と複数の病気を持つことができます。それぞれの薬と病気には開始日と終了日があります。

したがって、最初に3つのテーブルを1つのテーブル(Table_dn)に非正規化します。

dnId | PersonId | DrugId | DiseaseId | DgSt | DgEn | DiSt | DiEn
----   --------   ------   ---------   ----   ----   ----   ----

この非正規化されたテーブルは、図2.0に示すようにTable_dnにすべてのグローバルデータセットが含まれるようになったにもかかわらず、必要に応じて一時テーブルにすることができます(Gで示されます)。

あなたの説明の私の理解から、私は本質的に2層のフィルターを見ることができます。

フィルター1

このフィルターは、質問の説明ですでに述べたように、単にブール値の薬の組み合わせのセットです。例えば:

(drug a = 1 & drug b = 0 & etc) OR (.....

フィルター2

このフィルターは最初のフィルターよりも少し複雑で、日付範囲の基準です。図3.0は、この日付範囲をで示しています。黄色は、複数の方法にまたがる記録日を表します。

  • RED期間前
  • RED期間後
  • RED期間の間
  • RED期間が終了する前に終了する
  • RED期間開始後から開始

現在、黄色の日付期間は、薬剤期間または疾患期間、あるいは両方の組み合わせである可能性があります。

このフィルターは、最初の結果から得られた一連の結果に適用する必要があります。

もちろん、正確な質問によっては、これら2つのフィルターを逆にする必要がある場合があります(たとえば、最初にf2、次にf1)。

SQL擬似コード:

Select sub.*
From    
      (select    * 
       from      Table_dn 
       where     [Filter 1]
      ) as sub

where [Filter 2]

代替テキスト

于 2010-07-23T22:48:17.687 に答える
0

私はおそらくこれに似た方向からこの問題にアプローチするでしょう。それはかなり柔軟です。

DRUG_DISEASE_CORRELATION_QUERY
===============================
DRUG_DISEASE_CORRELATION_QUERY_ID
DISEASE_ID
DESCRIPTION

(1, 52, 'What this query does.')
(2, 52, 'Add some more results.')

DRUG_DISEASE_CORRELATION_QUERY_INCLUDE_DRUG
===========================================
DRUG_DISEASE_CORRELATION_QUERY_ID
DRUG_ID

(1, 234)
(1, 474)
(2, 371)

DRUG_DISEASE_CORRELATION_QUERY_EXCLUDE_DRUG
===========================================
DRUG_DISEASE_CORRELATION_QUERY_ID
DRUG_ID

(1, 26)
(2, 395)
(2, 791)



CREATE VIEW DRUG_DISEASE_CORRELATION
AS
SELECT 
    p.*,
    q.DRUG_DISEASE_CORRELATION_QUERY_ID
FROM 
    DRUG_DISEASE_CORRELATION_QUERY q
    INNER JOIN DISEASE_T ds on ds.DISEASE_ID = q.DISEASE_ID
    INNER JOIN PERSON_T p ON p.PERSON_ID = ds.PERSON_ID
  WHERE 
    AND EXISTS (SELECT * FROM DRUG_T dr WHERE dr.PERSON_ID = p.PERSON_ID AND dr.DRUG_ID IN
        (SELECT qid.DRUG_ID FROM DRUG_DISEASE_CORRELATION_QUERY_INCLUDE_DRUG qid WHERE 
        qid.DRUG_DISEASE_CORRELATION_QUERY_ID = q.DRUG_DISEASE_CORRELATION_QUERY_ID)
        AND DRUG_START_DATE < ds.DISEASE_START_DATE)
   AND NOT EXISTS (SELECT * FROM DRUG_T dr WHERE dr.PERSON_ID = p.PERSON_ID AND dr.DRUG_ID IN
        (SELECT qed.DRUG_ID FROM DRUG_DISEASE_CORRELATION_QUERY_EXCLUDE_DRUG qed WHERE 
        qed.DRUG_DISEASE_CORRELATION_QUERY_ID = q.DRUG_DISEASE_CORRELATION_QUERY_ID)
        AND DRUG_START_DATE < ds.DISEASE_START_DATE)
GO


SELECT * FROM DRUG_DISEASE_CORRELATION WHERE DRUG_DISEASE_CORRELATION_QUERY_ID = 1
UNION
SELECT * FROM DRUG_DISEASE_CORRELATION WHERE DRUG_DISEASE_CORRELATION_QUERY_ID = 2
于 2010-07-23T20:22:45.583 に答える
0

私が正しければ、あなたは次のことをしたい:

  • それらの人物を選択します
  • 特定の疾病に感染したことがある方
  • 1つ以上の特定の薬で治療されている人
  • かつ、1つ以上の特定の他の薬物で治療されていない人

これは、「薬物要件」を何らかの形式の一時テーブルに変換することで簡素化できます。これにより、任意の数の「良い」薬と「悪い」薬を使用してクエリを実行できます。以下に示すものはストアド プロシージャとして実装できますが、それがオプションでない場合は、多くの複雑なオプションを利用できます。

ステップの内訳:

まず、希望する患者を選択する方法は次のとおりです。これを後でサブクエリとして使用します。

SELECT [PersonData]
 from DISEASE_T di
  inner join PERSON_T pe
   on pe.Person_Id = di.Person_Id
 where di.Disease_Id = [TargetDisease]
  and [TimeConstraints]

次に、AND で結合した「ターゲット」薬のセットごとに、一時テーブルを次のように設定します (これは SQL Server の構文であり、Postgres にも同様のものが必要です)。

CREATE TABLE #DrugSet
 (
   Drug_Id  [KeyDataType]
  ,Include  int   not null
 )

検討している薬ごとに 1 つの行を入力します。

  • Drug_Id = チェックしている薬物
  • Include = 薬を服用している場合は 1、服用していない場合は 0

2 つの値を計算します。

@GoodDrugs、患者に服用させたい薬
の数 @BadDrugs、患者に服用させたくない薬の数

ここで、次のクエリで上記のすべてをつなぎ合わせます。

SELECT pe.[PersonData]  --  All the desired columns from PERSON_T and elsewhere
 from DRUG_T dr
  --  Filter to only include "persons of interest"
  inner join (select [PersonData]
               from DISEASE_T di
                inner join PERSON_T pe
                 on pe.Person_Id = di.Person_Id
               where di.Disease_Id = [TargetDisease]
                and [TimeConstraints]) pe
   on pe.Person_Id = dr.Person_ID
 --  Join with any of the drugs we are intersted in
 left outer join #DrugSet ta  
  on ta.Drug_Id = dr.Drug_Id
 group by pe.[PersonData]  --  Same as in the SELECT clause
 having sum(case ta.Include
              when 1 then 1  --  This patient has been given a drug that we're looking to match
              else 0         --  This patient has not been given this drug (catches NULLs, too)
            end) = @GoodDrugs
  and  sum(case ta.Include
              when 0 then 1  --  This patient has been given this drug that we're NOT looking to match
              else 0         --  This patient has not been given this drug (catches NULLs, too)
            end) = @BadDrugs

詳細に触れなかったので、時間基準を意図的に無視しましたが、追加するのはかなり簡単なはずです(ただし、それが有名な最後の言葉ではないことを願っています)。さらなる最適化が可能な場合もありますが、データやその他の可能な基準に大きく依存します。

各「薬物セット」 (つまり、TRUE または FALSE 薬物の AND で結合されたセット) ごとにこれを 1 回実行し、パスごとにリストを連結する必要があります。おそらく #DrugSet を拡張して、チェックしている各ドラッグ セットを考慮に入れることができますが、それをテストするための重大なデータがなければ、それをコーディングしようとするのは気が進まないのです。

*/

于 2010-07-23T20:34:15.507 に答える
0
SELECT per.person_id, per.name, per.gender
FROM person_t per
INNER JOIN disease_t dis
USING (person_id)
INNER JOIN drug_t drug
USING (person_id)
WHERE dis.disease_id = 52 AND drug.drug_start_date < dis.disease_start_date AND ((drug.drug_id IN (234, 474) AND drug.drug_id NOT IN (26)) OR (drug.drug_id IN (371) AND drug.drug_id NOT IN (395, 791)));

これはあなたが求めていることを行います。最後の IN ステートメントは一目瞭然です。

于 2010-07-09T14:53:15.677 に答える
0

間違いを許してください。ただし、次のようなものがうまくいくと思います(T-SQLで):

SELECT col1, col2, col3...
FROM PERSON_T AS P, DRUG_T AS DR, DISEASE_T AS DI
WHERE disease_id = 52
AND P.person_id = DI.person_id
AND P.person_id = DR.person_id
AND drug_id NOT IN(234, 26)
AND drug_id = 474
AND disease_start_date < drug_start_date
UNION
SELECT col1, col2, col3...
FROM PERSON_T AS P, DRUG_T AS DR, DISEASE_T AS DI
WHERE disease_id = 52
AND P.person_id = DI.person_id
AND P.person_id = DR.person_id
AND drug_id NOT IN(791, 395)
AND drug_id = 371
AND disease_start_date < drug_start_date

UNION を使用する必要はありませんが、読みやすくするために、条件を考えるとこれが最も簡単だと思いました。多分これはあなたを正しい方向に導くでしょう。

于 2010-07-09T14:40:34.463 に答える
0

これを試すのに本当に便利なテストデータはありませんが、次のようなことができると思います:

SELECT *
FROM DISEASE_T D
INNER JOIN DRUG_T DR ON D.PERSON_ID = DR.PERSON_ID AND D.DRUG_ID=52
INNER JOIN PERSON_T P ON P.PERSON_ID = D.PERSON_ID
GROUP BY PERSON_ID
HAVING SUM(
    CASE WHEN DRUG_ID=234 AND DRUG_START_DATE<DISEASE_START_DATE THEN -1 
    WHEN DRUG_ID=474 AND DRUG_START_DATE<DISEASE_START_DATE THEN 1 
    WHEN DRUG_ID=26 AND DRUG_START_DATE<DISEASE_START_DATE THEN -1 
    ELSE 0 END) = 1
    OR
    SUM(
    CASE WHEN DRUG_ID=395 AND DRUG_START_DATE<DISEASE_START_DATE THEN -1 
    WHEN DRUG_ID=791 AND DRUG_START_DATE<DISEASE_START_DATE THEN -1 
    WHEN DRUG_ID=371 AND DRUG_START_DATE<DISEASE_START_DATE THEN 1 
    ELSE 0 END) = 1

私が知っているケースは、同じ人物と同じ薬/病気の複数のレコードが薬/病気テーブルにある場合です。その場合は、HAVING 句を次のように変更することもできます。

(SUM(CASE WHEN DRUG_ID=234 AND DRUG_START_DATE<DISEASE_START_DATE THEN 1 ELSE 0 END) = 0
AND SUM(CASE WHEN DRUG_ID=474 AND DRUG_START_DATE<DISEASE_START_DATE THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN DRUG_ID=26 AND DRUG_START_DATE<DISEASE_START_DATE THEN 1 ELSE 0 END) = 0)
OR
(SUM(CASE WHEN DRUG_ID=395 AND DRUG_START_DATE<DISEASE_START_DATE THEN 1 ELSE 0 END) = 0
AND SUM(CASE WHEN DRUG_ID=791 AND DRUG_START_DATE<DISEASE_START_DATE THEN 1 ELSE 0 END) = 0
AND SUM(CASE WHEN DRUG_ID=371 AND DRUG_START_DATE<DISEASE_START_DATE THEN 1 ELSE 0 END) > 0)
于 2010-07-23T20:07:51.457 に答える
0

与えられた答えはどれもうまくいかないようです。ここでも、実装したいパターンがあります: ( (Drug 234 = true AND Drug 474 = true AND Drug 26 = false) OR (Drug 395 = false AND Drug 791 = false AND Drug 371 = true) )

次のクエリは (Drug 234 = true AND Drug 474 = true AND Drug 26 = false) に対して機能すると思います。そのため、クエリの後半を追加するのは非常に簡単です。

SELECT  p.person_id, p.gender FROM person_t as p 
    join drug_t as dr on dr.person_id = p.person_id 
    join disease_t as ds on ds.person_id=p.person_id 
    WHERE dr.drug_start_date < ds.disease_start_date AND disease_id = 52 AND dr.drug_id=234
INTERSECT
SELECT  p.person_id, p.gender FROM person_t as p 
    join drug_t as dr on dr.person_id = p.person_id 
    join disease_t as ds on ds.person_id=p.person_id 
    WHERE dr.drug_start_date < ds.disease_start_date AND disease_id = 52 AND dr.drug_id=474
INTERSECT (
SELECT p.person_id, p.gender
    FROM person_t as p 
    JOIN disease_t as ds on ds.person_id = p.person_id 
    LEFT JOIN drug_t as dr ON dr.person_id = p.person_id  AND dr.drug_id = 26
    WHERE disease_id = 52 AND dr.person_id is null 
UNION 
SELECT p.person_id, p.gender
    FROM person_t as p 
    JOIN disease_t as ds on ds.person_id = p.person_id 
    JOIN drug_t as dr ON dr.person_id = p.person_id  AND dr.drug_id = 26
    WHERE disease_id = 52 AND dr.drug_start_date > ds.disease_start_date)

このクエリは機能しますが、かなり醜いです。また、1 億人規模の運用データベースを作成すると、非常に遅くなると思います。このクエリを簡素化/最適化するためにできることはありますか?

于 2010-07-23T17:56:03.007 に答える