Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Закрыто
rommcr
Сообщения: 121
Зарегистрирован: 2014.12.24, 16:35

Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение rommcr » 2017.08.23, 03:36

(Абстрактно)
Есть 2 модели:

User (user_id, country_id, user_name)
Country(country_id, name)

В модели User есть связь с моделью Country:

Код: Выделить всё

    public function getCountry()
    {
        return $this->hasOne(\app\models\Country::className(), ['country_id' => 'country_id']);
    }
и геттер для имени страны

Код: Выделить всё

    public function getCountry_name()
    {
        return $this->Country->name;
    }
В dataProvider при извлечении данных исполбьзуем Join:

Код: Выделить всё

$query = User::find()->leftJoin('country', 'user.country_id = country.country_id')->select('user.*, country.name as country_name');
Так вот, в чем вопрос. Если в ячейке GridView задать атрибут country_name, фреймворк начнет ломиться в базу с запросами вида
SELECT * FROM country WHERE country_id = xx
Можно ли это обойти и заставить его использовать данные, которые приехали в запросе? Ведь таблица и так была подключена, данные были выбраны. Нет никакого смысла выгребать их еще раз.

P.S. не нужно мне говорить, что данные отдает Getter, а там связь. Я сам это всё знаю. Вопрос - как приучить GridView не делать бессмысленных операций.

Nerf
Сообщения: 780
Зарегистрирован: 2015.01.29, 00:37

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение Nerf » 2017.08.23, 03:41

User::find()->with(['country']) без всяких join. Гайд и документацию сложно почитать?

rommcr
Сообщения: 121
Зарегистрирован: 2014.12.24, 16:35

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение rommcr » 2017.08.23, 04:33

Nerf, не сложно. Это я тоже пробовал. Всё равно связанные объекты в таком случае отдельно выгребаются из базы - идут отдельные (избыточные для моего случая) запросы.

Прежде чем так безапеляционно что-то заявлять, попробуй. И загляни после этого в дебагер.

rommcr
Сообщения: 121
Зарегистрирован: 2014.12.24, 16:35

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение rommcr » 2017.08.23, 04:35

На данный момент решил обойтись вьюхой с новой моделью, объединяющей все необходимые поля.

Единственный ли это вариант?


rommcr
Сообщения: 121
Зарегистрирован: 2014.12.24, 16:35

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение rommcr » 2017.08.23, 12:38

chungachguk писал(а):
2017.08.23, 06:50
User::find()->joinWith(['country'])
Те же ..., только в левой руке.

Кто-нибудь может ответить, не угадывая, а зная наверняка?

Loveorigami
Сообщения: 965
Зарегистрирован: 2014.08.27, 21:54

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение Loveorigami » 2017.08.23, 12:54

Меня смущает эта запись. Должно быть с маленькой буквы

Код: Выделить всё

    public function getCountry_name()
    {
        return $this->сountry->name;
    }
И в запросе,

Код: Выделить всё

User::find()->innerJoinWith(['country'])
хотя достаточно и joinWith.

Loveorigami
Сообщения: 965
Зарегистрирован: 2014.08.27, 21:54

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение Loveorigami » 2017.08.23, 13:01

Стоп.
Во первых:
Вы же выбираете, как country_name

Код: Выделить всё

$query = User::find()->leftJoin('country', 'user.country_id = country.country_id')->select('user.*, country.name as country_name');
Тогда в модели User достаточно прописать

Код: Выделить всё

$public country_name;
вместо

Код: Выделить всё

    public function getCountry_name()
    {
        return $this->сountry->name;
    }
---------------

Во вторых - а зачем Вы делаете select?
В гриде понимаются записи вида `country.name`,
если данные достали, как User::find()->joinWith('country');

rommcr
Сообщения: 121
Зарегистрирован: 2014.12.24, 16:35

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение rommcr » 2017.08.23, 14:19

Loveorigami писал(а):
2017.08.23, 13:01
Во вторых - а зачем Вы делаете select?
В гриде понимаются записи вида `country.name`,
если данные достали, как User::find()->joinWith('country');
Только что смоделировал ситуацию в три файла.

Данные:
u_id name c_id
111 user name 123
c_id name
123 Country name
Если данные доставать без Select

Код: Выделить всё

$query = U::find()->joinWith('c');
И в GridView использовать c.name, то в дебагере видим:
SELECT COUNT(*) FROM `u` LEFT JOIN `c` ON `u`.`c_id` = `c`.`c_id`
SELECT `u`.* FROM `u` LEFT JOIN `c` ON `u`.`c_id` = `c`.`c_id` LIMIT 20
SELECT * FROM `c` WHERE `c_id`=123
Если же использовать Select

Код: Выделить всё

$query = U::find()->select(['u.*', 'c_name' => 'c.name'])->joinWith('c');
указать в модеи U свойство c_name и не использовать геттер, то ... барабанная дробь .....
COUNT(*) FROM `u` LEFT JOIN `c` ON `u`.`c_id` = `c`.`c_id`
SELECT `u`.*, `c`.`name` AS `c_name` FROM `u` LEFT JOIN `c` ON `u`.`c_id` = `c`.`c_id` LIMIT 20
SELECT * FROM `c` WHERE `c_id`=123
Результата удалось добиться моим первоначальным способом, но без использования геттера для c_name, а лишь объявив public $c_name в классе U. Способ не самый изящный, т.к. заполняться значением это поле будет только при выборке в классе SearchU, в остальных случаях оно будет пустым, и из-за того, что оно public, getter уже не использовать.

Код: Выделить всё

$query = U::find()->select(['u.*', 'c_name' => 'c.name'])->leftJoin('c', 'c.c_id = u.c_id');
Для частного случая сойдет, но всё равно не особо красиво.

И в итоге я нашел, как мне кажется, идеальный вариант.

Класс U:

Код: Выделить всё

    private $c_name;

   ....

    public function getC()
    {
        return $this->hasOne(C::className(), ['c_id' => 'c_id']);
    }

    function getC_name()
    {
        return is_null($this->c_name) ? ($this->c ? $this->c->name : null) : $this->c_name;
    }

    function setC_name($c_name)
    {
        $this->c_name = $c_name;
    }
В этом случае при выборке с джойном из базы, срабатывает сеттер, заполняя приватную переменную c_name. Когда GridView выгребает данные - срабатывает getter, который возвращает название страны, если оно было заполнено ранее. В противном случае, для совместимости - выгребает данные через связь.

Лишний запрос может быть в том случае, если нет связанной модели (тогда c_name будет = null) и getter пойдет по связи, но это можно обойти добавив флаг признака выборки данных например:

U:

Код: Выделить всё

   public $dataEquired = false;
...
    function getC_name()
    {
        return $this->dataEquired ? $this->c_name : ($this->c ? $this->c->name : null);
    }

SearchU:

Код: Выделить всё

$query = U::find()->select(['u.*', 'c_name' => 'c.name', 'dataEquired' => new \yii\db\Expression(1)])->leftJoin('c', 'c.c_id = u.c_id');
Последний раз редактировалось rommcr 2017.08.23, 15:58, всего редактировалось 1 раз.

Loveorigami
Сообщения: 965
Зарегистрирован: 2014.08.27, 21:54

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение Loveorigami » 2017.08.23, 15:31

rommcr писал(а):
2017.08.23, 14:19

И в GridView использовать c.name, то в дебагере видим:

SELECT COUNT(*) FROM `u` LEFT JOIN `c` ON `u`.`c_id` = `c`.`c_id`
SELECT `u`.* FROM `u` LEFT JOIN `c` ON `u`.`c_id` = `c`.`c_id` LIMIT 20
SELECT * FROM `c` WHERE `c_id`=123
У меня тоже три запроса, только на весь грид, вида
SELECT COUNT(*) FROM `mx_slider`
SELECT * FROM `mx_slider` ORDER BY `pos` LIMIT 20
SELECT * FROM `mx_object` WHERE `id` IN (392, 56, 159, 4, 31, 3, 147, 105, 61, 208, 8) // запрос по звязи
Судя по всему, у Вас тоже, только по связи у Вас выбирается одна запись ( WHERE `c_id`=123), т.к. в вашем гриде она единственная.
У меня же в гриде несколько связанных, поэтому запрос идет через ( WHERE `id` IN )

rommcr
Сообщения: 121
Зарегистрирован: 2014.12.24, 16:35

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение rommcr » 2017.08.23, 15:53

Loveorigami писал(а):
2017.08.23, 15:31
У меня тоже три запроса, только на весь грид, вида
SELECT COUNT(*) FROM `mx_slider`
SELECT * FROM `mx_slider` ORDER BY `pos` LIMIT 20
SELECT * FROM `mx_object` WHERE `id` IN (392, 56, 159, 4, 31, 3, 147, 105, 61, 208, 8) // запрос по звязи
Это понятно. Но в моей модели, из-за которой весь сыр-бор, ВОСЕМЬ связей используется. А это уже на восемь запросов больше, чем нужно. Поэтому и возник вопрос.

В общем, я нашел два решения. Или через VIEW, или с множеством getter'ов и setter'ов и leftJoin. Может, кому пригодится.

Loveorigami
Сообщения: 965
Зарегистрирован: 2014.08.27, 21:54

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение Loveorigami » 2017.08.23, 16:17

У меня больше ))) + несколько связей many-to-many.
Все эти дополнительный запросы занимают .... 15 ms. После кеширования - итого меньше.
Стоит ли на этом экономить, усложняя себе код, который, в вашем случае, нужно копипастить 8 раз.

Тем более, не факт, что один запрос с 8 джойнами будет менее производителен, чем 8 простых.
Последний раз редактировалось Loveorigami 2017.08.23, 16:20, всего редактировалось 1 раз.

rommcr
Сообщения: 121
Зарегистрирован: 2014.12.24, 16:35

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение rommcr » 2017.08.23, 16:19

Иногда речь идет об объемах данных в сотни миллионов записей и довольно высокой нагрузкой. Поэтому хрен с ним, разок напрягусь :)

rommcr
Сообщения: 121
Зарегистрирован: 2014.12.24, 16:35

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение rommcr » 2017.08.23, 16:24

И еще одно: если связи по двум условиям, то Yii рисует запрос вида

WHERE (a,b) in ((1,2), (3,4), (x,y)) - для всех пар значений, попавших в выборку.

и эта конструкция напрочь отказывается использовать ключи. Запрос выполняется вечность. Поэтому подобная "оптимизация" мне и даром не надо.

Пример:

Код: Выделить всё

select * from structure where (cd_id, idnumber) in ((1, 'CSC000000001'), (2, 'CSC000000002'));
2 rows in set (23.23 sec)

Код: Выделить всё

select * from structure where (cd_id =1 AND idnumber='CSC000000001') OR (cd_id =2 AND idnumber='CSC000000002');
2 rows in set (0.02 sec)

Привет оптимизатору мускула.

Nerf
Сообщения: 780
Зарегистрирован: 2015.01.29, 00:37

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение Nerf » 2017.08.24, 00:30

rommcr писал(а):
2017.08.23, 04:33
Nerf, не сложно. Это я тоже пробовал. Всё равно связанные объекты в таком случае отдельно выгребаются из базы - идут отдельные (избыточные для моего случая) запросы.

Прежде чем так безапеляционно что-то заявлять, попробуй. И загляни после этого в дебагер.
Это 100% рабочий вариант. Да будет 1 запрос на каждую связь (+парочка на схему, если не кешируется).
Попробуйте в следующий раз еще разобраться после прочтения.
rommcr писал(а):
2017.08.23, 16:19
Иногда речь идет об объемах данных в сотни миллионов записей и довольно высокой нагрузкой. Поэтому хрен с ним, разок напрягусь :)
Да с чего вы взяли, что делать join и писать говнокод будет быстрее 1 доп. запроса по индексам?

rommcr
Сообщения: 121
Зарегистрирован: 2014.12.24, 16:35

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение rommcr » 2017.08.24, 05:07

Да с чего вы взяли, что делать join и писать говнокод будет быстрее 1 доп. запроса по индексам?
Видимо, читать по диагонали - стойкая привычка.
Во-первых, вопрос не стоял, как сделать то, что и так понятно. Речь шла о минимизации кол-ва запросов.
Во-вторых, я привел конкретный пример с тем, как ведет себя фреймворк при связях по двум полям. Можете потестировать на досуге.

Увы, кроме чистоты кода иногда играет роль и быстродействие.

Nerf
Сообщения: 780
Зарегистрирован: 2015.01.29, 00:37

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение Nerf » 2017.08.24, 05:27

rommcr писал(а):
2017.08.24, 05:07
Да с чего вы взяли, что делать join и писать говнокод будет быстрее 1 доп. запроса по индексам?
Видимо, читать по диагонали - стойкая привычка.
Во-первых, вопрос не стоял, как сделать то, что и так понятно. Речь шла о минимизации кол-ва запросов.
Во-вторых, я привел конкретный пример с тем, как ведет себя фреймворк при связях по двум полям. Можете потестировать на досуге.

Увы, кроме чистоты кода иногда играет роль и быстродействие.
Да все я так читаю, закрывая глаза на бред типа joinWith(), который будет делать бессмысленный join в БД, а потом все равно тащить данные отдельным запросом.

И хватит юлить, у вас изначальная проблема была в том, что вы делали join, а потом тащили по геттеру через связь данные (которую джоин не заполняет никак), что приводило к запросу для каждой записи.
Ну и что в итоге? Насколько быстрее получается вытаскивать? Сколько классов/кода/бессмысленных конструкций пришлось добавить?..
Я делал подобное, когда нужна была агрегация по присоединенной таблице, тут это излишне.

rommcr
Сообщения: 121
Зарегистрирован: 2014.12.24, 16:35

Re: Можно ли заставить GridView не выполнять лишние запросы к связанным таблицам?

Сообщение rommcr » 2017.08.24, 15:53

Nerf писал(а):
2017.08.24, 05:27
И хватит юлить, у вас изначальная проблема была в том, что вы делали join, а потом тащили по геттеру через связь данные (которую джоин не заполняет никак), что приводило к запросу для каждой записи.
Да, именно. Не знал, как заставить использовать данные с JOIN'а. Почему в моем случае не годится with - я писал выше. Причина - связь по двум полям и большое количество данных в таблице.
Nerf писал(а):
2017.08.24, 05:27
Ну и что в итоге? Насколько быстрее получается вытаскивать?
На 23 секунды. Выше был пример.

Что было изначально - вряд ли Вам известно. Первое слово "АБСТРАКТНО" надо было написать красным и покрупнее. Хотя, судя по манере общения, телепатия наверняка входит в Ваши таланты.

Спасибо всем.

Закрыто