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]);
}