2016-12-15 5 views
0

У меня есть некоторые представления MySQL, которые определяют количество дополнительных столбцов на основе некоторых относительно простых подзапросов. База данных также многонаселена, поэтому каждая строка имеет идентификатор компании.Летитете оценку MySQL

Проблема, которую я имею, это мои взгляды оцениваются для каждой строки, прежде чем фильтроваться идентификатором компании, что дает огромные проблемы с производительностью. Есть ли способ лениво оценить представление, поэтому предложение «где» во внешнем запросе применяется к подзапросам в представлении. Или есть нечто похожее на представления, которые я могу использовать для добавления дополнительных полей. Я хочу рассчитать их в SQL, чтобы вычисленные поля могли использоваться для фильтрации/поиска/сортировки/разбивки на страницы.

Я ознакомился с документами MySQL, которые объясняют доступные алгоритмы, и я знаю, что представления не могут быть реализованы как «слияние», поскольку они содержат подзапросы.

вид

create view companies_view as 
select *, 
(
    select count(id) from company_user where company_user.company_id = companies.id 
) as user_count, 
(
    select count(company_user.user_id) 
    from company_user join users on company_user.user_id = users.id 
    where company_user.company_id = companies.id 
     and users.active = 1 
) as active_user_count, 
(
    select count(company_user.user_id) 
    from company_user join users on company_user.user_id = users.id 
    where company_user.company_id = companies.id 
     and users.active = 0 
as inactive_user_count 
from companies; 

запрос

select * from companies_view where company_id = 123; 

Я хочу подзапросов в представлении должны быть оценены после применения 'где company_id = 123' из основного объема запроса. Я не могу жестко скопировать идентификатор компании в представление, так как я хочу, чтобы представление было пригодно для использования для любого идентификатора компании.

ответ

2

Вы не можете изменить порядок оценки, заданный сервером MySQL.

Однако в данном конкретном случае можно переписать все инструкции SQL, чтобы использовать соединение и условные отсчеты вместо подзапросов:

select c.*, 
     count(u.id) as user_count, 
     count(if(u.active=1, 1, null)) as active_user_count, 
     count(if(u.active=0, 1, null)) as inactive_user_count 
from companies c 
left join company_user cu on c.id=cu.company_id 
left join users u on cu.user_id = u.id 
group by c.company_id, ... 

Если у вас есть MySQL v5.7, то вы не можете добавить любого дальнейшие поля для предложения group by, поскольку другие поля в таблице companies будут функционально зависимыми от company_id. В более ранних версиях вам может потребоваться перечислить все поля в таблице companies (в зависимости от настроек режима sql).

Другим способом оптимизации такого запроса будет использование денормализации. Ваша таблица users и company_user, вероятно, имеет намного больше записей, чем ваша таблица companies. Вы можете добавить user_count, active_user_count и поле inactive_user_count в таблицу companies, добавить после триггеров вставки/обновления/удаления в таблицу company_user и после обновления в таблицу users и обновить эти 2 поля. Таким образом, вам не нужно будет делать соединения и условные подсчеты в представлении.

1

Это можно убедить оптимизатор обрабатывать представление с скалярных подзапросов с использованием алгоритма MERGE ... вы просто должны бить оптимизатор в своей собственной игре.

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

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

CREATE FUNCTION user_count (_cid INT) RETURNS INT 
DETERMINISTIC 
READS SQL DATA 
RETURN (SELECT count(id) FROM company_user WHERE company_user.company_id = _cid); 

Обратите внимание, что хранимая функция с одним оператором не требуется BEGIN/END или изменение DELIMITER.

Тогда в представлении, заменить подзапрос с:

user_count(id) AS user_count, 

И повторить этот процесс для каждого подзапроса.

Оптимизатор затем обрабатывает представление как вид MERGE, выбирает одну подходящую строку из таблицы компаний на основе внешнего WHERE, вызывается функции и ... проблема решена.