2016-08-12 2 views
8

У меня есть простой, но длинный запрос, который подсчитывает содержание результата, которое занимает около 14 секунд. сам счетчик на главной таблице занимает меньше секунды, но после многократных присоединиться к задержке слишком высока, как следоватьКак оптимизировать запрос со многими объединениями?

Select Count(Distinct visits.id) As Count_id 
    From visits 
    Left Join clients_locations ON visits.client_location_id = clients_locations.id 
    Left Join clients ON clients_locations.client_id = clients.id 
    Left Join locations ON clients_locations.location_id = locations.id 
    Left Join users ON visits.user_id = users.id 
    Left Join potentialities ON clients_locations.potentiality = potentialities.id 
    Left Join classes ON clients_locations.class = classes.id 
    Left Join professions ON clients.profession_id = professions.id 
    Inner Join specialties ON clients.specialty_id = specialties.id 
    Left Join districts ON locations.district_id = districts.id 
    Left Join provinces ON districts.province_id = provinces.id 
    Left Join locations_types ON locations.location_type_id = locations_types.id 
    Left Join areas ON clients_locations.area_id = areas.id 
    Left Join calls ON calls.visit_id = visits.id 

Выход объяснить,

+---+---+---+---+---+---+---+---+---+---+ 
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 
+---+---+---+---+---+---+---+---+---+---+ 
| 1 | SIMPLE | specialties | index | PRIMARY | specialty_name | 52 | NULL | 53 | Using index | 
| 1 | SIMPLE | clients | ref | PRIMARY,specialty | specialty | 4 | crm_db.specialties.id | 143 | | 
| 1 | SIMPLE | clients_locations | ref | PRIMARY,client_id | client_id | 4 | crm_db.clients.id | 1 | | 
| 1 | SIMPLE | locations | eq_ref | PRIMARY | PRIMARY | 4 | crm_db.clients_locations.location_id | 1 | | 
| 1 | SIMPLE | districts | eq_ref | PRIMARY | PRIMARY | 4 | crm_db.locations.district_id | 1 | Using where | 
| 1 | SIMPLE | visits | ref | unique_visit,client_location_id | unique_visit | 4 | crm_db.clients_locations.id | 4 | Using index | 
| 1 | SIMPLE | calls | ref | call_unique,visit_id | call_unique | 4 | crm_db.visits.id | 1 | Using index | 
+---+---+---+---+---+---+---+---+---+---+

Обновление 1 Этот запрос используется с динамикой, где оператор $sql = $sql . "Where ". $whereFilter, но я представил его в простой форме. Так не считают ответ только eleminate стыки :)

Update 2 Вот пример динамической фильтрации

$temp = $this->province_id; 
if ($temp != null) { 
     $whereFilter = $whereFilter . " and provinces.id In ($temp) "; 
    } 

Но в случае запуска, который в нашем случае не где оператор

+0

Зачем вам нужно присоединяться, если вы просто подсчитываете уникальный идентификатор во время посещений? – ebyrob

+1

У вас, похоже, нет фильтрации, так зачем вам нужно «count (distinct)». Я имею в виду, что * есть * «внутреннее соединение» или два, посыпанные среди «join', но кажется, что объединения не нужны. –

+0

Я использую позже этот запрос с динамическим, где оператор «Где $ whereFilter ' –

ответ

3

You похоже, не имеют (или много) преднамеренной фильтрации. Если вы хотите узнать количество посещений, упомянутых в calls, я хотел бы предложить:

select count(distinct c.visit_id) 
from calls c; 
+0

Фильтрация выполняется на основе динамических критериев. Я обновил вопрос, чтобы указать, что –

+0

Выполняет ли он «достаточно быстро», когда у вас есть фильтрация? –

+0

Да, поскольку используется больше фильтра, результат быстрее –

7

левый присоединяется всегда возвращает строку из первой таблицы, но может возвращать несколько строк, если есть несколько строк соответствия. Но поскольку вы подсчитываете разные строки посещения, левое соединение с другой таблицей, в то время как подсчет различных посещений совпадает с подсчетом строк посещений. Таким образом, единственными объединениями, которые влияют на результат, являются внутренние объединения, поэтому вы можете удалить все «полностью» левые таблицы, не влияя на результат.

То, что я подразумеваю под «полностью», состоит в том, что некоторые левые соединенные таблицы эффективно соединены между собой; внутреннее соединение с specialty требует объединения до clients, чтобы преуспеть и, следовательно, также являлось внутренним соединением, которое, в свою очередь, требует присоединения к clients_locations и, следовательно, также является внутренним соединением.

Ваш запрос (как вывешено) может быть уменьшен до:

Select Count(Distinct visits.id) As Count_id 
From visits 
Join clients_locations ON visits.client_location_id = clients_locations.id 
Join clients ON clients_locations.client_id = clients.id 
Join specialties ON clients.specialty_id = specialties.id 

Удаления всех тех, ненужных присоединяются, однако, будет значительно улучшить время выполнения вашего запроса, не только потому, что меньше присоединяется сделать, но и потому, что в результате чего размер набора строк может быть огромным, если учесть, что размер является продукт матчей во всех таблицах (не суммы.

Для достижения максимальной производительности, создание индексов покрытия на всех ид и-Ф.К. столбцов:

create index visits_id_client_location_id on visits(id, client_location_id); 
create index clients_locations_id_client_id on clients_locations(id, client_id); 
create index clients_id_specialty_id on clients(id, specialty_id); 

так что, если возможно, можно использовать только индексирование. Я предполагаю, что на столбцах PK есть индексы.

+0

Как насчет звонков. Должен ли я оставить его вместе с посещением? –

+0

@eslam нет, не присоединяйтесь к 'calls'. Левое соединение с другой таблицей всегда будет возвращать каждую строку из посещений. Он может возвращать * несколько * строк, если в левой объединенной таблице есть несколько совпадающих строк, но поскольку вы считаете только * различные * идентификаторы посещения, присоединение к нескольким строкам не даст вам более четких идентификаторов посещения. – Bohemian

3

, чтобы оптимизировать весь процесс, вы можете динамически построить pre-where SQL в соответствии с фильтрами, которые вы собираетесь применять.Как:

 

    // base select and left join 
    $preSQL = "Select Count(Distinct visits.id) As Count_id From visits "; 
    $preSQL .= "Left Join clients_locations ON visits.client_location_id = clients_locations.id "; 

    // filtering by province_id 
    $temp = $this->province_id; 
    if ($temp != null) { 
      $preSQL .= "Left Join locations ON clients_locations.location_id = locations.id "; 
      $preSQL .= "Left Join districts ON locations.district_id = districts.id "; 
      $preSQL .= "Left Join provinces ON districts.province_id = provinces.id "; 
      $whereFilter = "provinces.id In ($temp) "; 
     } 

    $sql = $preSQL . "Where ". $whereFilter; 
    // ... 

Если вы используете несколько фильтров вы можете поместить все внутренние/левые присоединиться строки в массиве, а затем после анализа запроса, вы можете построить свой $preSQL используя минимум соединяющий.

1

Используйте COUNT (CASE WHEN visit_id! = "" THEN 1 END) в качестве посещения.

Надеется, что это поможет

1

Это не просто:

SELECT COUNT(id) 
FROM visits 

, потому что все левые внешние соединения также возвращает visits.id когда Там нет подходящих клиентов, ..., звонки и идентификаторов должен быть уникальным?

Различные подсказки: одно внутреннее соединение также действует только тогда, когда клиент существует. Как правило, при необходимости внутренних соединений они должны быть поставлены как можно ближе/ближе к исходной таблице, поэтому в вашем примере это было бы лучше всего в строке после «левых кликов».

0

я не понял, слишком много вашей идеи, особенно ваш INNER JOIN, который tranform некоторые ЛЕВЫЙ в INNER JOIN и, кажется странным, но давайте попробуем решение:

Обычно ЛЕВЫЙ JOIN и имеет очень плохую производительность , и я думаю, что они понадобятся вам, только если вы будете использовать их в предложении WHERE, тогда вы можете включить их с INNER JOIN, только если вы их будете использовать. Например:

$query = "Select Count(Distinct visits.id) As Count_id From visits "; 

if($temp != null){ 
    $query .= " INNER JOIN clients_locations ON visits.client_location_id = clients_locations.id "; 
    $query .= " INNER JOIN locations ON clients_locations.location_id = locations.id "; 
    $query .= " INNER JOIN locations ON clients_locations.location_id = locations.id "; 
    $query .= " INNER JOIN districts ON locations.district_id = districts.id " 
    $query .= " INNER JOIN provinces ON districts.province_id = provinces.id "; 
    $whereFilter .= " and provinces.id In ($temp) "; 
} 

Я думаю, что это поможет вашей производительности и это работает, как вам нужно.

Смежные вопросы