#php #mysql
#php #mysql
Вопрос:
Я создал функцию поиска для своего приложения cake. Он построен из нескольких полей выбора, в которых вы можете выбирать данные, затем он циклически перебирает выбранные параметры и реализует их в синтаксисе SQL.
В основном так выглядит функция:
$selectedFilters = $this->data;
$selectSQL = 'SELECT
agencies.agency, agencies.website_url, agencies.status, agencies.size, agencies.id, OfficeData.id, ContactData.name, ContactData.surname, ContactData.job_title, ContactData.email,
ContactData.mobile, OfficeCountryData.country
FROM agencies
LEFT JOIN (SELECT agencies_industries.agency_id, agencies_industries.industry_id FROM agencies_industries) AS IndustryData ON agencies.id = IndustryData.agency_id
LEFT JOIN (SELECT agencies_professions.agency_id, agencies_professions.profession_id FROM agencies_professions) AS ProfessionData ON agencies.id = ProfessionData.agency_id
LEFT JOIN (SELECT agencies_sectors.agency_id, agencies_sectors.sector_id FROM agencies_sectors) AS SectorData ON agencies.id = SectorData.agency_id
LEFT JOIN (SELECT agencies_seniorities.agency_id, agencies_seniorities.seniority_id FROM agencies_seniorities) AS SeniorityData ON agencies.id = SeniorityData.agency_id
LEFT JOIN (SELECT agencies_zones.agency_id, agencies_zones.zone_id FROM agencies_zones) AS ZonesData ON agencies.id = ZonesData.agency_id
LEFT JOIN (SELECT agencies_countries.agency_id, agencies_countries.country_id FROM agencies_countries) AS CountryData ON agencies.id = CountryData.agency_id
LEFT JOIN (SELECT agencies_regions.agency_id, agencies_regions.region_id FROM agencies_regions) AS RegionData ON agencies.id = RegionData.agency_id
LEFT JOIN (SELECT agencies_cities.agency_id, agencies_cities.city_id FROM agencies_cities) AS CityData ON agencies.id = CityData.agency_id
LEFT JOIN (SELECT agencies_specialisms.agency_id, agencies_specialisms.specialism_id FROM agencies_specialisms) AS SpecialismData ON agencies.id = SpecialismData.agency_id
LEFT JOIN (SELECT offices.id, offices.agency_id, offices.hq FROM offices WHERE offices.hq = "1") AS OfficeData ON agencies.id = OfficeData.agency_id
LEFT JOIN (SELECT countries.id, countries.country FROM countries) AS OfficeCountryData ON OfficeData.hq = OfficeCountryData.id
LEFT JOIN (SELECT contacts.name, contacts.surname, contacts.agency_id, contacts.job_title, contacts.email, contacts.mobile FROM contacts) AS ContactData ON agencies.id = ContactData.agency_id
';
$whereSQL = ' WHERE 1 = 1 ';
foreach($selectedFilters as $key)
foreach($key as $name=>$value){
if(is_array($key))
foreach($key as $key=>$value){
$i = 0;
$connector = 'AND';
if(is_array($value)){
foreach($value as $value){
if($i > 0)
$connector = 'OR';
$i ;
switch($key){
case 'Profession': $whereSQL .= $connector.' ProfessionData.profession_id = ' . $value . ' ';
break;
case 'Specialism': $whereSQL .= $connector.' SpecialismData.specialism_id = ' . $value . ' ';
break;
case 'SubSpecialism': $whereSQL .= ''; //$whereSQL .= $connector.' SubData.sub_specialism_id = ' . $value . ' ';
break;
case 'Seniority': $whereSQL .= $connector.' SeniorityData.seniority_id = ' . $value . ' ';
break;
case 'Industry': $whereSQL .= $connector.' IndustryData.industry_id = ' . $value . ' ';
break;
case 'Zone': $whereSQL .= $connector.' ZonesData.zone_id = ' . $value . ' ';
break;
case 'Country': $whereSQL .= $connector.' CountryData.country_id = ' . $value . ' ';
break;
case 'Region': $whereSQL .= $connector.' RegionData.region_id = ' . $value . ' ';
break;
case 'City': $whereSQL .= $connector.' CityData.city_id = ' . $value . ' ';
break;
case 'Sector': $whereSQL .= $connector.' SectorData.sector_id = ' . $value . ' ';
break;
case 'status': $whereSQL .= $connector.' agencies.status = "' . $value . '" ';
break;
case 'size': $whereSQL .= $connector.' agencies.size = "' . $value . '" ';
break;
}
}
}
else
if(!isBlank($value) amp;amp; $key != 'Search')
$whereSQL .= $connector.' agencies.'.$key.' = "'.$value.'" ';
}
}
$groupBySQL = 'GROUP BY agencies.id ORDER BY agencies.id ASC';
$resultAgencies = $this->Agency->query($selectSQL . $whereSQL . $groupBySQL);
$this->set(compact('resultAgencies'));
Проблема, с которой я столкнулся при поиске, заключается в том, что он работает очень медленно. Это происходит из-за использования слишком большого количества LEFT JOIN
команд. Каждый LEFT JOIN
выбирает данные из разных таблиц и собирает их все, создавая другую таблицу. Затем отображаются данные.
Мне нужно, чтобы кто-нибудь дал мне подсказку, как это сделать, не используя так много LEFT JOINs
.
Приветствия.
Комментарии:
1. Не СОЕДИНЕНИЯ убивают ваш запрос, а количество подзапросов! Почему join выполняется в подзапросе, а не в таблице?
Ответ №1:
Попробуйте это:
$selectSQL = 'SELECT
agencies.agency, agencies.website_url, agencies.status, agencies.size, agencies.id, OfficeData.id, ContactData.name, ContactData.surname, ContactData.job_title, ContactData.email,
ContactData.mobile, OfficeCountryData.country
FROM agencies
LEFT JOIN agencies_industries AS IndustryData ON agencies.id = IndustryData.agency_id
LEFT JOIN agencies_professions AS ProfessionData ON agencies.id = ProfessionData.agency_id
LEFT JOIN agencies_sectors AS SectorData ON agencies.id = SectorData.agency_id
LEFT JOIN agencies_seniorities AS SeniorityData ON agencies.id = SeniorityData.agency_id
LEFT JOIN agencies_zones AS ZonesData ON agencies.id = ZonesData.agency_id
LEFT JOIN agencies_countries AS CountryData ON agencies.id = CountryData.agency_id
LEFT JOIN agencies_regions AS RegionData ON agencies.id = RegionData.agency_id
LEFT JOIN agencies_cities AS CityData ON agencies.id = CityData.agency_id
LEFT JOIN agencies_specialism AS SpecialismData ON agencies.id = SpecialismData.agency_id
LEFT JOIN offices AS OfficeData ON (agencies.id = OfficeData.agency_id AND OfficeData.hq = "1")
LEFT JOIN countries AS OfficeCountryData ON OfficeData.hq = OfficeCountryData.id
LEFT JOIN contacts AS ContactData ON agencies.id = ContactData.agency_id
';
Но даже тогда это может быть медленным, поскольку вы объединяете слишком много таблиц. Но трудно сказать, не зная ничего о ваших данных и количестве строк, которые вы вернете. Возможно, вам захочется переместить некоторые соединения в подзапрос (например, country), если вы возвращаете только несколько строк. Или вы можете добавить эту информацию в отдельный запрос.
Редактировать: не зная ваших данных и структуры базы данных, трудно сказать. Есть много вещей, которые влияют на скорость вашего запроса. Сначала перепишите свой запрос так, чтобы таблицы, которые не используются для вашего выбора (например, WHERE ) или поля, которые вы хотите отобразить, не использовались в вашем запросе. Поэтому, если вы не делаете выбора (emtpy $ selectedFilters), вам не нужно включать таблицы отраслей, профессий, секторов, пожилых людей и т. Д.:
$selectedFilters = $this->data;
$selectSQL = 'SELECT
agencies.agency, agencies.website_url, agencies.status, agencies.size, agencies.id, OfficeData.id, ContactData.name, ContactData.surname, ContactData.job_title, ContactData.email,
ContactData.mobile, OfficeCountryData.country
FROM agencies';
$sql2=' LEFT JOIN offices AS OfficeData ON (agencies.id = OfficeData.agency_id AND OfficeData.hq = "1")
LEFT JOIN countries AS OfficeCountryData ON OfficeData.hq = OfficeCountryData.id
LEFT JOIN contacts AS ContactData ON agencies.id = ContactData.agency_id
';
$whereSQL = ' WHERE 1 = 1 ';
foreach($selectedFilters as $key)
foreach($key as $name=>$value){
if(is_array($key))
foreach($key as $key=>$value){
$i = 0;
$connector = 'AND';
if(is_array($value)){
foreach($value as $value){
if($i > 0)
$connector = 'OR';
$i ;
switch($key){
case 'Profession': $whereSQL .= $connector.' ProfessionData.profession_id = ' . $value . ' ';
$sql2.=' LEFT JOIN agencies_professions AS ProfessionData ON agencies.id = ProfessionData.agency_id ';
break;
case 'Specialism': $whereSQL .= $connector.' SpecialismData.specialism_id = ' . $value . ' ';
$sql2.=' LEFT JOIN agencies_specialism AS SpecialismData ON agencies.id = SpecialismData.agency_id ';
break;
case 'SubSpecialism': $whereSQL .= ''; //$whereSQL .= $connector.' SubData.sub_specialism_id = ' . $value . ' ';
break;
case 'Seniority': $whereSQL .= $connector.' SeniorityData.seniority_id = ' . $value . ' ';
$sql2.=' LEFT JOIN agencies_seniorities AS SeniorityData ON agencies.id = SeniorityData.agency_id ';
break;
case 'Industry': $whereSQL .= $connector.' IndustryData.industry_id = ' . $value . ' ';
$sql2=' LEFT JOIN agencies_industries AS IndustryData ON agencies.id = IndustryData.agency_id ';
break;
case 'Zone': $whereSQL .= $connector.' ZonesData.zone_id = ' . $value . ' ';
$sql2.=' LEFT JOIN agencies_zones AS ZonesData ON agencies.id = ZonesData.agency_id ';
break;
case 'Country': $whereSQL .= $connector.' CountryData.country_id = ' . $value . ' ';
$sql2.=' LEFT JOIN agencies_countries AS CountryData ON agencies.id = CountryData.agency_id ';
break;
case 'Region': $whereSQL .= $connector.' RegionData.region_id = ' . $value . ' ';
$sql2.=' LEFT JOIN agencies_regions AS RegionData ON agencies.id = RegionData.agency_id ';
break;
case 'City': $whereSQL .= $connector.' CityData.city_id = ' . $value . ' ';
$sql2.=' LEFT JOIN agencies_cities AS CityData ON agencies.id = CityData.agency_id ';
break;
case 'Sector': $whereSQL .= $connector.' SectorData.sector_id = ' . $value . ' ';
$sql2.='LEFT JOIN agencies_sectors AS SectorData ON agencies.id = SectorData.agency_id ';
break;
case 'status': $whereSQL .= $connector.' agencies.status = "' . $value . '" ';
break;
case 'size': $whereSQL .= $connector.' agencies.size = "' . $value . '" ';
break;
}
}
}
else
if(!isBlank($value) amp;amp; $key != 'Search')
$whereSQL .= $connector.' agencies.'.$key.' = "'.$value.'" ';
}
}
$groupBySQL = 'GROUP BY agencies.id ORDER BY agencies.id ASC';
$resultAgencies = $this->Agency->query($selectSQL . $sql2 . $whereSQL . $groupBySQL);
$this->set(compact('resultAgencies'));
Во-вторых, внимательно посмотрите на свои индексы для каждой таблицы. Убедитесь, что у вас есть индекс для полей, которые вы используете в ОБЪЕДИНЕНИЯХ.
В-третьих, посмотрите на типы полей, которые вы используете. Не используйте INT, если SMALLINT достаточно большой.
В заключение: нормализация — это здорово, но иногда лучше объединить некоторые вещи, даже если это означает, что у вас дублирующиеся данные.
Комментарии:
1.
Unknown column 'offices.hq' in 'on clause'
(и да,hq
в таблице office есть имя столбца.2. измените office на OfficeData (также изменено в приведенном выше коде)
3. Все равно придется ждать результатов около минуты (даже без выбора каких-либо данных).
4. Внес некоторые изменения в мой ответ. Суть в том, что нет ни одного быстрого ответа, чтобы ускорить его.
5. Спасибо за код. Я проверю это через час и оставлю отзыв 🙂 Если это сработает, я соглашусь 🙂
Ответ №2:
Вы должны использовать объединения, а не подзапросы. Вам также могут не всегда понадобиться все эти левые соединения; Я вижу, что ваш оператор WHERE является динамическим, поэтому в рамках вашего оператора switch вы можете решить, к каким дополнительным таблицам вам нужно присоединиться.
Итак, начните с объединения только тех таблиц, из которых вам нужны столбцы;
$selectSQL = "
SELECT agencies.agency,
agencies.website_url,
agencies.status,
agencies.size,
agencies.id,
OfficeData.id,
ContactData.name,
ContactData.surname,
ContactData.job_title,
ContactData.email,
ContactData.mobile,
OfficeCountryData.country
FROM agencies
LEFT JOIN offices AS OfficeData ON ( agencies.id = OfficeData.agency_id )
LEFT JOIN contacts AS ContactData ON ( agencies.id = ContactData.agency_id )
LEFT JOIN countries AS OfficeCountryData ON ( OfficeData.hq = OfficeCountryData.id ) "
И затем, когда вы создаете свои операторы where, вы можете оценить, нужно ли вам присоединиться к таблице, чтобы это предложение было действительным.
$whereSQL = 'WHERE OfficeData.hq = "1"';
$joinSQL ='';
# Loop though your filter options and build up the where and joins
foreach(...){
switch($key){
case 'Profession':
$whereSQL .= $connector.' ProfessionData.profession_id = ' . $value . ' ';
$joinSQL .= 'LEFT JOIN agencies_professions AS ProfessionData ON (agencies.id = ProfessionData.agency_id)'
break;
....
}
}
А затем создайте свой окончательный запрос
$sql = $selectSQL.' '.$joinSQL.' '.$whereSQL;
Комментарии:
1. В этом случае при выборе всех возможных вариантов это не сработало бы.
2. Мне нужно было бы знать больше о том, как работают возможные варианты, чтобы понять это.
3. Это просто поля множественного выбора / выбора.
Ответ №3:
Научитесь использовать синтаксис EXPLAIN в MySQL. Отредактируйте свой вопрос и включите выходные данные вашего плана объяснения.
Среди других проблем, вы выполняете левые соединения во многих таблицах, из которых вы не выбираете. Попробуйте это.
SELECT agencies.agency, agencies.website_url, agencies.status, agencies.size,
agencies.id,
OfficeData.id,
ContactData.name, ContactData.surname, ContactData.job_title,
ContactData.email, ContactData.mobile, OfficeCountryData.country
FROM agencies
LEFT JOIN (SELECT offices.id, offices.agency_id, offices.hq
FROM offices
WHERE offices.hq = "1") AS OfficeData
ON agencies.id = OfficeData.agency_id
LEFT JOIN countries AS OfficeCountryData
ON OfficeData.hq = OfficeCountryData.id
LEFT JOIN contacts AS ContactData
ON agencies.id = ContactData.agency_id
Как это влияет на производительность?
Вероятно, нет веских причин идентифицировать страны, города и регионы с идентификационными номерами; они несут свою идентификацию в своем имени. Протестируйте замену идентификационного номера на правильные имена. (Идентификационные номера всегда требуют операции объединения для получения полезных данных обратно; естественные ключи часто исключают объединения.)
Вы отметили, что производительность без ненужных соединений хорошая, и что switch
утверждение не виновато. Если это так, то вам нужно уменьшить количество соединений. К счастью, сокращение объединений является простым и понятным.
Если вам необходимо «сообщить о юниверсе», вы можете попробовать разделить запрос и отправить несколько запросов асинхронно. Сначала верните и отобразите, скажем, данные агентства и контактные данные, и вы значительно увеличите кажущуюся скорость приложения. И СУБД может обрабатывать второй запрос, пока выполняется рендеринг первого. Часто кажущаяся скорость важнее фактической скорости.
Комментарии:
1. Мне нужно объединить все таблицы, потому что конечный результат является динамическим.
2. Производительность без объединений такая, какой она должна быть. Работает очень быстро.
3. Когда я комментирую оператор switch и циклы, для загрузки инструкции SQL требуется столько же времени.
4. @PiotrChabros: Когда вы говорите: «для загрузки инструкции SQL требуется столько же времени», означает ли это, что инструкция SQL выполняется быстро или медленно?
5. @PiotrChabros: обновленный ответ. Смотрите последние два абзаца.
Ответ №4:
Не зная, что вы на самом деле делаете, трудно сказать, можно ли упростить ваш запрос. Предполагая, что вам нужна информация из всех таблиц, а все идентификаторы являются первичными ключами, я бы проанализировал предложение WHERE — правильно ли определены индексы? При больших индексах базы данных это имеет огромное значение и может значительно повысить производительность.
Ответ №5:
Соединения выполняются медленно, попробуйте подзапросы, пишите более короткие запросы. они могут означать больше кодов, но если вы соберете свою временную метку до и после запросов, вы увидите огромную разницу.
Ответ №6:
Соединения могут быть медленными, но это не ваша проблема здесь. Одно быстрое решение: удалите эти подзапросы, зачем создавать подзапрос вместо всей таблицы? Это делает все намного медленнее.
Второе: убедитесь, что все ключи, которые вы используете для объединения, помечены как индексы, это может сделать все намного быстрее.
Комментарии:
1. Как мне удалить эти подзапросы?
2. Просто присоединитесь к полной таблице, а не к подзапросу.