web-dev-qa-db-ja.com

行を列に変換してMysqlでクエリする方法は?

私は3つのテーブルPatientsに患者の名前が含まれていますControls病気ごとに描画できるUIコントロールを表しますControlsValues =各患者に対して送信されたコントロールの値を含むテーブル

いくつかのデータを取得しましょうPatientsテーブル

|ID  | Name |
|-----------|
| 1  | Ara  |
| 2  | Sada |

Controlsテーブル

|ID  | Text | Type     |
|-----------|----------|
| 1  | age  | textbox  |
| 2  |alergy| checkbox |

次に、controlsValuesテーブルで、クエリを実行する場所です

|ID  | contrlId | value    | patientId |
|---------------|----------|-----------|
| 1  | 1        | 23       | 1         |
| 2  | 2        | true     | 1         |
| 3  | 1        | 26       | 2         |
| 4  | 2        | false    | 2         |

ここで、(controlId=1 AND value=23)および(controlId=2 AND value=true)が含まれているControlsValuesテーブルからその患者を返したいときに問題が発生しますこの場合、条件は2列ではなく2列であり、不可能です。そのため、controlIdに応じて行を列に変更することを検討しましたが、方法がわからず、2日間検索して見ました。たくさんのサンプルがありますが、どれも私の問題を解決するのに役立ちませんでした

2
rabar kareem

次のような「解決策」を試すことができます。

http://sqlfiddle.com/#!9/d33a95/18

これは「ピボットテーブル」の解決策ではありませんが、おそらくあなたの問題を解決します-私がそれを正しく理解していれば。

使用されるテーブル/データは次のとおりです。

CREATE TABLE `patients` (
`ID` INT NOT NULL AUTO_INCREMENT ,
`Name` VARCHAR( 50 ) NOT NULL ,
PRIMARY KEY ( `ID` )
);
CREATE TABLE `controls` (
`ID` INT NOT NULL AUTO_INCREMENT ,
`Text` VARCHAR( 50 ) NOT NULL ,
`Type` VARCHAR( 50 ) NOT NULL ,
PRIMARY KEY ( `ID` )
); 
CREATE TABLE `controlvalues` (
`ID` INT NOT NULL AUTO_INCREMENT ,
`ControlID` INT NOT NULL ,
`Value` VARCHAR( 50 ) NOT NULL ,
`PatientID` INT NOT NULL ,
PRIMARY KEY ( `ID` )
);
INSERT INTO `patients` (`Name`) VALUES ('Ara'), ('Sada'), ('Pada'), ('Lada');
INSERT INTO `controls` (`Text`, `Type`) VALUES ('age', 'textbox'), ('alergy', 'checkbox');
INSERT INTO `controlvalues` (`ControlID`, `Value`, `PatientID`) VALUES 
  (1, '23', 1), 
  (2, 'true', 1),
  (1, '25', 2),
  (2, 'false', 2),
  (1, '23', 3),
  (2, 'false', 3),
  (1, '23', 4), 
  (2, 'true', 4);

そしてクエリ:

SELECT PatientID, COUNT(PatientID) AS PatCount, p.Name
FROM controlvalues cv
LEFT JOIN patients p ON p.ID = cv.PatientID
WHERE ((ControlID = 1 AND Value = '23')
   OR (ControlID = 2 AND Value = 'true'))
GROUP BY PatientID
HAVING PatCount = 2
;
1
Billy G

存在する

データをピボットする必要なく、答えを得ることができます。 (私の観点から)最も直接的なアプローチは、EXISTSを使用することです。これは、ステートメントを直接反映します。

SELECT
     p.ID, p.Name
FROM
     patients p
WHERE
    EXISTS 
    (
        SELECT 1
        FROM controlvalues cv1
        WHERE p.ID = cv1.PatientID
              AND cv1.controlId = 1
              AND cv1.value = '23'
    )
    AND
    EXISTS
    (
        SELECT 1
        FROM controlvalues cv2
        WHERE p.ID = cv2.PatientID
              AND cv2.controlId = 2
              AND cv2.value = 'true'
    ) ;

これは単に戻ります:

 ID |名前
-:| :--- 
 1 |あら

参加する

別の方法として、EXISTSJOINsに変更できます。

SELECT
     p.ID, p.Name
FROM
     patients p
     JOIN controlvalues cv1 
         ON cv1.PatientID = p.ID AND cv1.controlId = 1 AND cv1.value = '23'
     JOIN controlvalues cv2 
         ON cv2.PatientID = p.ID AND cv2.controlId = 2 AND cv2.value = 'true' ;

同じ結果を返します。クエリはより簡潔ですが、私の意見では、少し読みにくくなっています。

実行計画(MariaDB 10.2以降)は、正しいインデックス(または私の場合は主キー)が定義されている限り、同じです。


ピボット

データをピボットしたい場合は、次のようにします。

SELECT
    p.ID, p.Name,
    max(case when controlId = 1 then value end) AS controlId1 /* age */,
    max(case when controlId = 2 then value end) AS controlId2 /* allergy */
FROM
    patients p
    JOIN controlvalues cv ON cv.PatientId = p.ID 
GROUP BY
    p.ID ;

これはピボットテーブルです。

 ID |名前| controlId1 | controlId2 
-:| : :--------- | :--------- 
 1 |あら| 23 |真
 2 |サダ| 25 | false 

...そしてそれはあなたがそれを使う方法です:

SELECT
    pp.ID, pp.Name
FROM
    (
    SELECT
        p.ID, p.Name,
        max(case when controlId = 1 then value end) AS controlId1,
        max(case when controlId = 2 then value end) AS controlId2
    FROM
        patients p
        JOIN controlvalues cv ON cv.PatientId = p.ID 
    GROUP BY
        p.ID 
    ) AS pp
WHERE
    pp.controlId1 = '23' AND pp.controlId2 = 'true';
 ID |名前
-:| :--- 
 1 |あら

ただし、pivotedテーブルの実行プランは、おそらくもっと悪い可能性があります(特に大きなテーブルでは)。

dbfiddleですべてをチェックできます---(ここ

1
joanolo