2017-11-08 1 views
0

대용량 MySQL 데이터베이스에서 데이터를 가져 오는 데 문제가 있습니다.대용량 MySQL 데이터베이스에서 데이터를 가져 오기 위해 SQL 쿼리 최적화

아래 코드를 사용하면 테스트 서버 인 10K 환자 및 5K 약속 목록을 얻는 것이 좋습니다.

그러나 라이브 서버의 경우 환자 수가 100K 이상이고 약속 수는 300K 이상이며 잠시 후 코드를 실행하면 500 오류가 발생합니다.

patient_treatment_status가 1 또는 3 인 환자 목록이 필요하며 마지막 약속으로부터 한 달 후에 약속이 없습니다. (아래 코드는 소량의 환자와 약속을 위해 작동합니다.)

foreach 루프에서 두 번째 데이터베이스 쿼리가 필요하지 않도록 첫 번째 데이터베이스 쿼리를 최적화하려면 어떻게해야합니까?

<?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

두 개의 쿼리, 특히 루프의 쿼리를 실행하면 안됩니다. –

+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을하고 임의적으로 하나 patient_id를 선택합니다. 원하는 결과를 얻으려면 GROUP BY patient_id해야합니다. 데이터가 나타나는 경우

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

보고서가이 수행하는 방법과 : 전체 질문에 대한

, 시도 다음 쿼리 :

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 

는 그렇지 않은 경우 이미 존재, 다음 인덱스를 추가 옳은. 성능과 관련하여 도움을 받으려면 SHOW CREATE TABLE patientSHOW CREATE TABLE appointments을 제공하십시오.

또한 사용중인 두 번째 쿼리와 함께 AND NOT EXISTS 절없이 위의 쿼리를 시도하십시오. 이 경우 두 쿼리를 함께 실행하면 쿼리를 실행하는 것이 더 빠를 수 있습니다.

최신 약속을 찾으려면 INNER JOIN을 사용했습니다. 이렇게하면 약속을 한 적이없는 모든 환자가 쿼리에 포함되지 않게됩니다. 추가 된 것들을 필요로한다면 UNION만으로는 약속을 한 적이없는 환자 중에서 선택하여 결과를 얻을 수 있습니다.

+0

@RickJames 파생 된 테이블에'GROUP BY patient_id'을 추가했다는 사실을 간과하지 않았습니까? 그렇지 않으면, 나는 당신과 동의 할 것입니다. –

+0

죄송합니다. 첫 번째 파생 테이블에 대한 수정 된 조언 : INDEX (appointment_status, patient_id, start_date). –

관련 문제