У меня довольно большой и сложный запрос, генерируемый Yii. Чтобы сгенерировать этот запрос, мы используем CDbCritera::with
для активной загрузки нескольких связанных моделей, и мы используем несколько областей для ограничения возвращаемых записей. Сгенерированный запрос имеет длину около 700 строк, но выглядит примерно так:
SELECT `t`.`column1` as `t0_c0`,
`t`.`column2` as `t0_c1`,
`related1`.`column1` as `t1_c0`,
...
`related9`.`column5` as `t9_c4`
FROM `model` `t`
LEFT OUTER JOIN `other_model` `related1`
ON ( `t`.`other_model_id` = `related1`.`id` )
...
LEFT OUTER JOIN `more_models` `related9`
ON ( `t`.`more_models_id` = `related9`.`id` )
WHERE
...big long WHERE clause using all of related1 - related9 to filter model...
LIMIT 10
В нашей базе данных есть немалый объем данных, но он также непристойен. В этом случае таблица model
имеет около 126000 строк, каждая «связанная» модель имеет отношение BELONGS_TO
, а индекс t.XXX_id
имеет индекс, поэтому объединение довольно тривиально. Проблема заключается в сложности предложения WHERE, содержащего несколько предложений COALESCE
и IF
и CASE
. Выполнение фильтра для наших 126000 строк занимает 2,6 секунды - намного больше, чем хотелось бы для конечной точки API.
Предложение WHERE разделено на несколько различных разделов, например:
WHERE
( ... part 1 ... )
AND
( ... part 2 ... )
AND
( ... part 3 ... )
С каждой частью, соответствующей одной из областей действия, и с каждой частью, использующей одну или несколько связанных моделей
одна из областей применения фильтрует только на одну связанную модель, и при этом фильтрует нашу таблицу с 126000 строк до примерно 2000 строк. Я экспериментально (в MySQL Workbench) обнаружил, что могу получить наш запрос от 2,6 до 0,2 секунды, просто выполнив следующее:
SELECT `t`.`column1` as `t0_c0`,
`t`.`column2` as `t0_c1`,
`related1`.`column1` as `t1_c0`,
...
`related9`.`column5` as `t9_c4`
FROM
(
SELECT `model`.*
FROM `model`
LEFT OUTER JOIN `other_model`
ON ( `t`.`other_model_id` = `other_model`.`id` )
WHERE
( ... part 1 ... )
) `t`
LEFT OUTER JOIN `other_model` `related1`
ON ( `t`.`other_model_id` = `related1`.`id` )
...
LEFT OUTER JOIN `more_models` `related9`
ON ( `t`.`more_models_id` = `related9`.`id` )
WHERE
( ... part 2 ... )
AND
( ... part 3 ... )
LIMIT 10
Таким образом, вместо выполнения очень сложного предложения WHERE
для всех 126000 строк исходной таблицы model
, мы выполняем гораздо более простое (и с расширенным индексом) предложение WHERE
для этих 126000 строк, а затем выполняем сложное предложение WHERE
только для 2000 соответствующих строк. Результаты этих двух запросов идентичны, но использование подзапроса в предложении FROM
заставляет его работать в 13 раз быстрее.
Проблема в том, что я понятия не имею, как это сделать в Yii. Я знаю, что могу использовать CDbCommand
для построения запроса и даже передать raw SQL, но я вернусь к массиву «строк» - они не будут поняты Yii и должным образом преобразованы в правильные модели.
Есть ли у системы Yii ActiveRecord способ сказать что-то вроде следующего?
$criteria = new CDbCriteria;
$criteria->scopes = array("part1");
$subQuery = Model::model()->buildQuery($criteria);
$criteria = new CDbCriteria;
$criteria->scopes = array("part2", "part3");
$fullQuery = $subQuery->findAll($criteria);