2017-11-08 6 views
0

大量のMySQLデータベースからデータを取得する際に問題が発生しています。大量のMySQLデータベースからデータを取得するためにSQLクエリを最適化する

以下のコードで、私たちのテストサーバーである10K患者と5K予定のリストを取得することは大丈夫です。

しかし私たちのライブサーバーでは、患者数は100Kを超え、予定数は300Kを超えています。しばらくしてからコードを実行すると、500のエラーが発生します。

patient_treatment_statusが1または3であり、最後の予約から1ヶ月後に予定がない患者のリストが必要です。 (以下のコードは、少量の患者と予定に対応しています)

foreachループで2番目のデータベースクエリが不要になるように、最初のデータベースクエリを最適化するにはどうすればよいですか?

<?php 
ini_set('memory_limit', '-1'); 
ini_set('max_execution_time', 0); 

require_once('Db.class.php'); 

$patients = $db->query(" 
SELECT 
    p.id, p.first_name, p.last_name, p.phone, p.mobile, 
    LatestApp.lastAppDate 
FROM 
    patients p 
LEFT JOIN (SELECT patient_id, MAX(start_date) AS lastAppDate FROM appointments WHERE appointment_status = 4) LatestApp ON p.id = LatestApp.patient_id 
WHERE 
    p.patient_treatment_status = 1 OR p.patient_treatment_status = 3 
ORDER BY 
    p.id 
"); 

foreach ($patients as $row) { 
    $one_month_after_the_last_appointment = date('Y-m-d', strtotime($row['lastAppDate'] . " +1 month")); 
    $appointment_check = $db->single("SELECT COUNT(id) FROM appointments WHERE patient_id = :pid AND appointment_status = :a0 AND (start_date >= :a1 AND start_date <= :a2)", array("pid"=>"{$row['id']}","a0"=>"1","a1"=>"{$row['lastAppDate']}","a2"=>"$one_month_after_the_last_appointment")); 

    if($appointment_check == 0){ 
     echo $patient_id = $row['id'].' - '.$row['lastAppDate'].' - '.$one_month_after_the_last_appointment. '<br>'; 
    } 
} 
?> 
+0

私の場合は、今はすべてのPHPを取り除き、その代わりにSQLに焦点を当てます。同意する場合は、https://meta.stackoverflow.com/questions/333952/why-should-i-provide-an-mcve-for-what-seems-to-me-to-be-a-very-simpleを参照してください。 -sql-query – Strawberry

+0

2つのクエリ、特にループ内のクエリを実行しないのが理想的です。 –

+0

私はスピードの問題がサブクエリとLEFT JOINに相対的だと思います。私はそれがあなたのサブクエリを削除し、通常の他のテーブルをジョインし、アポイントID(もしあれば)によってグループを使用し、最新のアポイントメントを取得するためにMAXを使用してクエリをスピードアップするためにINNER JOINを使用する方が良いと思います。 – Lucarnosky

答えて

1

まず、このサブクエリは、あなたが思うとは思わないでしょう。 GROUP BY句なし

SELECT patient_id, MAX(start_date) AS lastAppDate 
FROM appointments WHERE appointment_status = 4 

、そのサブクエリは単純appointment_status=4ですべての予定の最大start_dateを取り、その後、任意に1 patient_idを選択します。あなたが望む結果を得るには、GROUP BY patient_idにする必要があります。あなたの全体的な質問については

、次のクエリを試してみてください。

SELECT 
    p.id, p.first_name, p.last_name, p.phone, p.mobile, 
    LatestApp.lastAppDate 
FROM 
    patients p 
INNER JOIN (
    SELECT patient_id, 
    MAX(start_date) AS lastAppDate 
    FROM appointments 
    WHERE appointment_status = 4 
    GROUP BY patient_id 
) LatestApp ON p.id = LatestApp.patient_id 
WHERE 
    (p.patient_treatment_status = 1 
    OR p.patient_treatment_status = 3) 
    AND NOT EXISTS (
     SELECT 1 
     FROM appointments a 
     WHERE a.patient_id = p.patient_id 
     AND a.appointment_status = 1 
     AND a.start_date >= LatestApp.lastAppDate 
     AND a.start_date < DATE_ADD(LatestApp.lastAppDate,INTERVAL 1 MONTH) 
) 
ORDER BY 
    p.id 

が存在しない場合、次のインデックスを追加します。これは実行し、データが表示されたら、どのように

ALTER TABLE appointments 
ADD INDEX (`patient_id`, `appointment_status`, `start_date`) 

レポート正しい。パフォーマンスに関連してさらに支援するにはSHOW CREATE TABLE patientおよびSHOW CREATE TABLE appointmentsを提供してください。

また、使用する2番目のクエリと共にAND NOT EXISTS句を使用せずに上記のクエリを実行してください。この状況では、2つのクエリを実行すると、それらを一緒に実行しようとするよりも高速になる可能性があります。

最新の予定を見つけるにはINNER JOINを使用しました。これにより、決して予約されていないすべての患者がクエリに含まれなくなります。追加されたものが必要な場合は、予約をしたことのない患者から選択した結果をUNIONにします。

+0

@RickJames派生テーブルに 'GROUP BY patient_id'を追加したという事実を見落とさなかったのですか?そうでなければ、私はあなたに同意するでしょう。 –

+0

最初の派生テーブルの修正済みのアドバイス: 'INDEX(appointment_status、patient_id、start_date)'。 –

関連する問題