ActiveRecord где и порядок по таблице

У меня есть три таблицы базы данных:

product (id, name)

product_has_adv (продукт, преимущество, сортировка, важное)

преимущество (id, текст)

В ProductModel я определил это:

public function getAdvantages()
    {
        return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
            ->viaTable('product_has_advantage', ['product' => 'id']);
    }

Я получаю преимущества без проблем.

Но теперь мне нужно добавить кладку product_has_advantage.important = 1, а также отсортировать преимущества сортировки в таблице product_has_advantage.

Как и где я должен это понимать?

Ответы

Ответ 1

Использование методов via и viaTable с отношениями приведет к двум отдельным запросам.

Вы можете указать вызываемый в третьем параметре, например:

public function getAdvantages()
{
    return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
        ->viaTable('product_has_advantage', ['product' => 'id'], function ($query) {
            /* @var $query \yii\db\ActiveQuery */

            $query->andWhere(['important' => 1])
                ->orderBy(['sort' => SORT_DESC]);
        });
}

Будет применен фильтр important, но сортировка не будет, так как это произойдет в первом запросе. В результате порядок идентификаторов в выражении IN будет изменен.

В зависимости от вашей логики базы данных, возможно, лучше переместить столбцы important и sort в таблицу advantage.

Затем просто добавьте условие и выполните сортировку по существующей цепочке методов:

public function getAdvantages()
{
    return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
        ->viaTable('product_has_advantage', ['product' => 'id'])
        ->andWhere(['important' => 1])
        ->orderBy(['sort' => SORT_DESC]);
}

Ответ 2

Использование методов viaTable с отношениями приведет к двум отдельным запросам, но если вам не нужен метод link(), вы можете использовать innerJoin следующим образом для сортировки по таблице product_has_advantage:

public function getAdvantages()
{
    $query = AdvantageModel::find();
    $query->multiple = true;
    $query->innerJoin('product_has_advantage','product_has_advantage.advantage = advantage.id');
    $query->andWhere(['product_has_advantage.product' => $this->id, 'product_has_advantage.important' => 1]);
    $query->orderBy(['product_has_advantage.sort' => SORT_DESC]);
    return $query;
}

Примечание, чем $query->multiple = true позволяет использовать этот метод как отношение Yii2 hasMany.

Ответ 3

Только для справки https://github.com/yiisoft/yii2/issues/10174 Это почти невозможно для столбцов ORDER BY viaTable(). Для Yii 2.0.7 он возвращает набор идентификаторов из viaTable() запроса, и final/top query IN() игнорирует порядок.

Ответ 4

Сначала вам нужно создать модель с именем ProductHasAdv для таблицы соединений (product_has_adv) с помощью CRUD.

Затем создайте отношение в модели product и соберите его:

  public function getAdvRels()
    {
        return $this->hasMany(ProductHasAdv::className(), ['product' => 'id'])->
        orderBy(['sort' => SORT_ASC]);;
    }

Затем создайте второе отношение следующим образом:

public function getAdvantages()
{
    $adv_ids = [];
    foreach ($this->advRels as $adv_rel)
        $adv_ids[] = $adv_rel->advantage;
    return $this->hasMany(Advantage::className(), ['id' => 'advantage'])->viaTable('product_has_adv', ['product' => 'id'])->orderBy([new Expression('FIELD (id, ' . implode(',', $adv_ids) . ')')]);
}

Это будет сортировать окончательный результат, используя технику order by FIELD.

Не забудьте добавить:

use yii\db\Expression;

линия к голове.

Ответ 5

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

Пример для кода выше:

public function getAdvantages()
{
    return $this->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
        ->viaTable('product_has_advantage', ['product' => 'id'])
        ->innerJoin('product_has_advantage','XXX')
        ->orderBy('product_has_advantage.YYY'=> SORT_ASC);
}

Позаботьтесь о том, чтобы изменить XXX с правильным ходом и YYY с правильным столбцом сортировки.

Ответ 6

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

        $query = Product::find();
        $query->joinWith([
                         'product_has_adv' => function ($query)
                         {
                            $query->alias('pha');
                            $query->orderBy('pha.sort ASC');
                            $query->joinWith(['advantage ' => function ($query){
                                $query->select([
                                            'a.id',
                                            'a.text',
                                            ]);
                                 $query->alias('a');
                            }]);
                         },
                         ]);

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

        "product_has_adv": [
        {
            "product": "875",
            "advantage": "true",
            "sort": "0",
            "important": "1",
            "advantage ": {
                "id": "875",
                "text": "Some text..",
            }
        },

Ответ 7

Как объяснил @arogachev, viaTable использует два отдельных запроса, что делает любой промежуточный orderBy устаревшим

Вы можете заменить viaTable на innerJoin следующим образом, аналогично решению @MartinM

public function getAdvantages()
{
   return $this->hasMany(AdvantageModel::class, ['pha.product' => 'id'])
       ->innerJoin('product_has_advantage pha', 'pha.advantage = advantage.id')
       ->andWhere(['pha.important' => 1])
       ->orderBy(['pha.sort' => SORT_ASC]);
}

Настраивая результат hasMany, вы настраиваете запрос для целевого класса - AdvantageModel::find(); product_has_advantage можно объединить с помощью идентификатора advantage

Второй параметр hasMany, link, можно просмотреть как [ query.column => $this->attribute ], который теперь можно поддерживать через объединенный product_has_advantage и его идентификатор product


Примечание, при использовании viaTable параметр ссылки можно рассматривать так, как будто промежуточный запрос завершен, и мы начинаем с него; [ query.column => viaTable.column ] следовательно ['id', 'advantage'] в вашем вопросе

Ответ 8

public function getAdvantages()
{
    return $this
        ->hasMany(AdvantageModel::className(), ['id' => 'advantage'])
        ->viaTable('product_has_advantage', ['product' => 'id'])
        ->andWhere(['important' => 1])
        ->orderBy(['sort' => SORT_DESC]);
}