ОБНОВЛЕНИЕ: я изменил свой ответ, чтобы разрешить проверку страницы.
Я прибыл с опозданием более чем на год, но хотел поделиться, что действительно возможно использовать SQL_CALC_FOUND_ROWS в SqlDataProvider в Yii2.
Вам необходимо расширить класс SqlDataProvider
до:
- получить общее количество от
SELECT FOUND_ROWS()
.
- изменить способ свойства validatePage объекта
pagination
объект работает.
В чем проблема?
Если вы посмотрите на последние строки метода prepareModels()
класса SqlDataProvider (Yii 2.0.10) ...
if ($pagination !== false) {
$pagination->totalCount = $this->getTotalCount();
$limit = $pagination->getLimit();
$offset = $pagination->getOffset();
}
$sql = $this->db->getQueryBuilder()->buildOrderByAndLimit($sql, $orders, $limit, $offset);
return $this->db->createCommand($sql, $this->params)->queryAll();
... вы увидите, что $this->getTotalCount()
вызывается перед выполнением запроса. Очевидно, это проблема, если вы хотите использовать SELECT FOUND_ROWS()
для общего подсчета.
Но зачем ему звонить заранее? В конце концов, на тот момент он еще не начал создавать пейджер. Итак, объекту pagination
требуется общий счет только для проверки индекса текущей страницы.
Вызов метода getOffset () getPage () для расчета. , который вызывает getQueryParam () чтобы получить текущую запрашиваемую страницу. После этого getPage()
вызывает setPage ($ page , правда). И здесь, когда необходимо общее количество: setPage()
вызовет getPageCount (), чтобы убедиться, что запрашиваемая страница находится в установленных границах.
Каково решение?
Чтобы расширить класс SqlDataProvider
, установите для свойства validatePage
объекта pagination
значение false
, пока мы не выполним наш запрос. Затем мы можем получить общее количество от SELECT FOUND_ROWS()
и включить пользовательскую проверку страницы.
Наш новый пользовательский поставщик данных может быть примерно таким:
use Yii;
use yii\data\SqlDataProvider;
class CustomDataProvider extends SqlDataProvider
{
protected function prepareModels()
{
// we set the validatePage property to false temporarily
$pagination = $this->getPagination();
$validatePage = $pagination->validatePage;
$pagination->validatePage = false;
// call parent method
$dataModels = parent::prepareModels();
// get total count
$count = Yii::$app->db->createCommand( 'SELECT FOUND_ROWS()' )->queryScalar();
// both the data provider and the pagination object need to know the total count
$this->setTotalCount( $count );
$pagination->totalCount = $count;
// custom page validation
$pagination->validatePage = $validatePage;
// getPage(true) returns a validated page index if $validatePage is also true
if ( $pagination->getPage(false) != $pagination->getPage(true) ) {
return $this->prepareModels();
// or if you favor performance over precision (and fear recursion) *maybe* is better:
//$this->sql = str_replace( 'SQL_CALC_FOUND_ROWS ', '', $this->sql);
//return parent::prepareModels();
}
return $dataModels;
}
}
И мы можем использовать это так:
$dataProvider = new CustomDataProvider([
'sql' => 'SELECT SQL_CALC_FOUND_ROWS ...';
//'totalCount' => $count, // this is not necessary any longer!
// more properties like 'pagination', 'sort', 'params', etc.
]);
Есть ли недостаток?
Что ж, наша новая настраиваемая проверка страницы менее эффективна: если страница не проходит проверку, ей потребуется один дополнительный запрос.
Как работает проверка страницы?
Представьте, что у вас есть 100 элементов и вы используете поставщика данных с pageSize => 20
для отображения данных в ListView
. Пейджер будет показывать ссылки для навигации по 5 страницам, но в некоторых случаях пользователь может попытаться получить доступ к странице 6: потому что он вручную изменяет URL-адрес, потому что количество записей изменяется с момента последней загрузки страницы (как в @ Brett's пример ) или потому что он перешел по старой ссылке.
Как поставщик данных управляет ситуацией? Если validatePage
установлен на ...
- false (независимо от поставщика): он попытается запросить страницу 6 с помощью SQL, например:
SELECT ... LIMIT 20 OFFSET 100
. Он получит пустой набор данных, а виджет выдаст сообщение «Результаты не найдены».
- true (
SqlDataProvider
): заранее обнаружит, что последней доступной страницей является номер 5, и запросит ее с SELECT ... LIMIT 20 OFFSET 80
.
- true (
CustomDataProvider
): он попытается запросить страницу 6, получит пустой набор данных, впоследствии поймет, что страница 6 не существует, и запросит еще раз < / strong> для страницы 5.
ИМО, это не имеет большого значения, потому что попадание на несуществующую страницу будет происходить очень редко.
Это действительно необходимо?
OP хотел, чтобы этот подход гарантировал, что и подсчет, и фактический запрос выполняются как можно ближе. Может быть, вам это нужно для выступления.
В любом случае, вы должны прочитать комментарии к вопросу от @AlexanderRavikovich и @ineersa. Это нелогично, но во многих случаях второй count(*)
запрос может быть быстрее, чем использование SQL_CALC_FOUND_ROWS
.
Об этом много написано, особо не беспокойтесь: это во многом зависит от вашего запроса и версии базы данных. Лучшее, что вы можете сделать, - это протестировать оба способа, прежде чем внедрять настраиваемый поставщик данных.
Заключительные примечания:
Если вы действительно заботитесь о точности, подумайте о этот сценарий:
- Если
count(*)
терпит неудачу, он обычно терпит неудачу для нескольких записей.
- Если
SELECT FOUND_ROWS()
терпит неудачу ... ну, это может быть грандиозный провал!
И если вы действительно заботитесь о производительности, есть несколько хороших предложений в ответах на этот другой вопрос (отнеситесь к нему с зерном соли, он очень старый), мне особенно нравится этот.
person
David
schedule
17.11.2016
COUNT(*)
был быстрее, а другие говорили, чтоSQL_CALC_FOUND_ROWS
быстрее; так что указание на то, что это зависит от приложения. - person Brett   schedule 20.04.2015