Via в relation и JOIN

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
lnghost
Сообщения: 134
Зарегистрирован: 2011.07.26, 18:05
Откуда: Воронеж
Контактная информация:

Via в relation и JOIN

Сообщение lnghost »

Приветствую. У меня есть релейшн:

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

public function getPages()
    {
        return $this->hasMany(Page::class, ['id' => 'page_id'])
            ->viaTable(PageOwner::tableName(), ['organization_id' => 'id'])->accessFilter();
    }
Всё работало хорошо, пока в PageOwner не появилось 3к записей. Дело в том, что via делает junction через WHERE IN(), то есть сначала выгребает все 3000 записей из промежуточной таблицы, а затем пихает нужный ID в IN() основного запроса. В связи с чем возникает две проблемы:
1. Время выполнения невероятно дольше, чем если бы это работало через JOIN
2. Мускуль тупо не жрёт такие длинные запросы и ругается на слишком длинную строку.

Всё приложение построено на AR, взять и просто отказаться от него в сторону голого Query не возможно. Есть ли варианты решения без глобального переделывания релейшнов и работы со связями? В идеале сделать так, чтобы via запросы работали через JOIN, а не через IN
Конференция: yii@conference.jabber.ru Сайт: http://kamaran.ru
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Via в relation и JOIN

Сообщение ElisDN »

Вместо связи:

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

$organization = Organization::findOne($id);

$query = $organization->getPages();
выгребайте через:

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

$organization = Organization::findOne($id);

$query = Page::find()->joinWith('pageOwner po', false)->andWhere(['po.organization_id' => $organization->id]);
lnghost
Сообщения: 134
Зарегистрирован: 2011.07.26, 18:05
Откуда: Воронеж
Контактная информация:

Re: Via в relation и JOIN

Сообщение lnghost »

Это жутко тупо )
Половину проекта придётся переписывать. Нет ли вариантов без изменения основного кода это пофиксить, только на уровне методов релейшнов?

Плюс в проекте есть запросы такого вида:

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

$data = Organization::find()->innerJoinWith('pages')->...->all()
То есть есть фильтрация по заджойненным записям
Конференция: yii@conference.jabber.ru Сайт: http://kamaran.ru
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Via в relation и JOIN

Сообщение ElisDN »

lnghost писал(а): 2018.04.13, 21:53 То есть есть фильтрация по заджойненным записям
Если собирать JOIN-ы вручную, то:

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

Organization::find()
    ->alias('o')
    ->innerJoin('{{%organization_pages}} op', 'op.organization_id = o.id')
    ->innerJoin('{{%pages}} p', 'p.id = op.page_id')
    ->andWhere(...)
    ->groupBy('o.id')
    ->all()
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Via в relation и JOIN

Сообщение ElisDN »

lnghost писал(а): 2018.04.13, 21:53 То есть есть фильтрация по заджойненным записям
Или вместо GROUP BY поизвращаться с подзапросом WHERE EXISTS:

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

Organization::find()
    ->alias('o')
    ->andWhere([
        'exists',
        PageOwner::find()
            ->alias('op')
            ->innerJoin(['p' => Page::tableName()], 'p.id = op.page_id')
            ->andWhere(new Expresion('op.organization_id = o.id'))
            ->andWhere(...)
    ])
    ->all()
andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Via в relation и JOIN

Сообщение andku83 »

Если я вас правильно понял то проблема возникает при использовании связанных данных?
Если вам не нужны связанные данные при применении JOIN то не получайте их:

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

$data = Organization::find()->innerJoinWith('pages', false)->...->all()
Я не уверен что вам нужно получать полностью все 3к записей, либо если это все-таки необходимо, то возможно вам нужно работать с ними через ->each(), тогда объектов будет создаваться меньше и связанные данные будут запрашиваться по частям.
lnghost
Сообщения: 134
Зарегистрирован: 2011.07.26, 18:05
Откуда: Воронеж
Контактная информация:

Re: Via в relation и JOIN

Сообщение lnghost »

Смотрите, я понимаю, как это сделать через джойны. Проблема не в этом. Есть огромное продакшн приложение, где повсеместно использовался релейшн 'pages'. И в виде $model->getPages()->...->all(), и в виде $model->pages, и Organization::find()->innerJoinWith('pages')->...->all().
Теперь возникла проблема с тем, что via работает откровенно через жопу, и релейшн жрёт тонну ресурсов, в том числе времени. Переделать все места где он используется в приложении на джойны – не реально. Можно ли как-то реализовать схему внутри метода getPages(), чтобы не пришлось половину приложения перепиливать? То есть как-то заменить via() на что-то другое, либо изменить его поведение. Покопался в ядре – ничего подходящего не нашёл.
Конференция: yii@conference.jabber.ru Сайт: http://kamaran.ru
andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Via в relation и JOIN

Сообщение andku83 »

Возможно вы не тем путем пытаетесь решить проблему.
Опишите более конкретно вашу задачу с большей привязкой к вашей теме.
У меня была задача с экспортом более 5к товаров с множеством связей и подобная проблема не возникала.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Via в relation и JOIN

Сообщение ElisDN »

lnghost писал(а): 2018.04.15, 01:40 Теперь возникла проблема с тем, что via работает откровенно через жопу, и релейшн жрёт тонну ресурсов, в том числе времени... То есть как-то заменить via() на что-то другое, либо изменить его поведение. Покопался в ядре – ничего подходящего не нашёл.
Вместо innerJoinWith('pages') вызывайте innerJoinWith('pages', false), чтобы не подгружать страницы повторно через IN.
lnghost писал(а): 2018.04.15, 01:40 Переделать все места где он используется в приложении на джойны – не реально.
Всё реально.
Ответить