Страница 1 из 1
relation joinWith в цикле, смена alias
Добавлено: 2019.12.25, 17:18
webplus
Здравствуйте!
Есть LEFT JOIN-ы которые циклом добавляются.
Код: Выделить всё
LEFT JOIN `shop_product_option` `po_cvet`
ON (`po`.`product_id` = `po_cvet`.`product_id`)
AND ((`po_cvet`.`slug`='sinij') AND (`po_cvet`.`slug_option`='cvet'))
LEFT JOIN `shop_product_option` `po_razmer`
ON (`po`.`product_id` = `po_razmer`.`product_id`)
AND ((`po_razmer`.`slug` IN ('77', '99')) AND (`po_razmer`.`slug_option`='razmer'))
мне нужно чтобы в ON у второго join было
ON (`po_cvet`.`product_id` = `po_razmer`.`product_id`) и так далее. т.е. алиас шага назад добавлялось к текущему.
Я пытаюсь сделать так:
Код: Выделить всё
$query = ProductOption::find()->alias('po');
foreach ($filters_array as $group => $filter) {
if ($group != $_group['slug_option']) {
$query->joinWith(['productOption' => function($q) use ($filter, $group) {
return $q->alias("po_{$group}")
->andOnCondition(['IN', "`po_{$group}`.slug", $filter])
->andOnCondition(["`po_{$group}`.slug_option" => $group]);
}]);
}
}
в модели у меня
Код: Выделить всё
public function getProductOption()
{
return $this->hasMany(ProductOption::className(), ['product_id' => 'product_id']);
}
вопрос как алиас в relation менять.
Можно конечно использовать $query->leftJoin и в нем ON прописать - это удобно, но как в нем andOnCondition чтобы оно добавилось не во WHERE главного запроса, а в AND leftJoin-а?
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.25, 18:43
webplus
вот такое безобразие получилось, но работает правильно:
Код: Выделить всё
$alias = ['`po`'];
$query = ProductOption::find()->alias('po');
foreach ($filters_array as $group => $filter) {
if ($group != $_group['slug_option']) {
$query->leftJoin(productOption::tableName() . " po_{$group}", end($alias) . ".product_id = po_{$group}.product_id
AND `po_{$group}`.slug IN (" . "'" . implode("','", $filter) . "'" . ")
AND `po_{$group}`.slug_option='{$group}'");
$alias[] = "`po_{$group}`";
}
}
может можно в leftJoin сделать AND при помощи Condition, чтобы это не делать
AND `po_{$group}`.slug IN (" . "'" . implode("','", $filter) . "'" . ")?
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.25, 21:24
yiiliveext
$i++, прям как в примере
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.25, 23:56
webplus
yiiliveext писал(а): ↑2019.12.25, 21:24
$i++, прям как в примере
Вот сделал рекурсивное Relation для JoinWith, но мне кажется что я примитивно его сделал.
В модели ProductOption
Код: Выделить всё
static public function recursiveRelation(&$query, $group_slug, $filters_array) {
foreach ($filters_array as $group => $filter) {
if($group != $group_slug) {
return $query->joinWith(['productOption' => function ($q) use ($filter, $group, $group_slug, $filters_array) {
$q->alias("po_{$group}")
->andOnCondition(['IN', "`po_{$group}`.slug", $filter])
->andOnCondition(["`po_{$group}`.slug_option" => $group]);
unset($filters_array[$group]);
return static::recursiveRelation($q, $group_slug, $filters_array);
}]);
}
}
}
В контроллере:
Код: Выделить всё
private function getFilters($productsQuery, $filters_array)
{
$out = [];
$groups = ProductOption::find()
->select([ProductOption::tableName() . '.*', ProductOption::tableName() . '.option_' . \Yii::$app->language . ' as group_option'])
->leftJoin($productsQuery->tablesUsedInFrom, $productsQuery->tablesUsedInFrom['{{shop_product}}'] . '.id = ' . ProductOption::tableName() . '.product_id')
->where($productsQuery->where)
->groupBy('group_option')
->orderBy('group_option ASC')
->asArray()
->all();
$i = 0;
foreach ($groups as $_group) {
$out[$i]['parent'] = $_group['group_option'];
$group_slug = $_group['slug_option'];
$query = ProductOption::find()->alias('po');
if(isset($filters_array) && count($filters_array) > 0) {
//ВОТ ВЫЗЫВАЮ РЕКУРСИЮ В joinWith
ProductOption::recursiveRelation($query, $group_slug, $filters_array);
}
$alias = ['`po`'];
foreach ($filters_array as $group => $filter) {
if ($group != $group_slug) {
$alias[] = "`po_{$group}`";
}
}
$alias = end($alias);
$query->select(['po.*', 'po.value_' . \Yii::$app->language . ' as value', "count({$alias}.product_id) as count_product"])
->join('JOIN', $productsQuery->tablesUsedInFrom, $productsQuery->tablesUsedInFrom['{{shop_product}}'] . '.id = po.product_id')
//->andWhere($productsQuery->where)
->andWhere(['po.slug_option' => $_group['slug_option']])
->groupBy('po.slug');
$out[$i]['filters'] = $query->asArray()->all();
$i++;
}
return $out;
}
но мне не удалось из ProductOption::recursiveRelation() получить alias последней таблицы в left join
мне пришлось сделать:
Код: Выделить всё
$alias = ['`po`'];
foreach ($filters_array as $group => $filter) {
if ($group != $group_slug) {
$alias[] = "`po_{$group}`";
}
}
$alias = end($alias);
А $filters_array содержит выбранные галочками фильтры:
Код: Выделить всё
Array
(
[cvet] => Array
(
[0] => sinij
)
[razmer] => Array
(
[0] => 99
)
[ves] => Array
(
[0] => 180
)
)
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.26, 10:42
yiiliveext
А зачем вы два раза в цикле присоединяете?
И зачем там рекурсия, обычный цикл в конце которого делаем $lastGroupSlug = $groupSlag, чтобы иметь доступ к предыдущему слагу в новой итерации.
В рекурсии, кстати, не вижу соединения по product_id.
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.26, 13:32
webplus
yiiliveext писал(а): ↑2019.12.26, 10:42
А зачем вы два раза в цикле присоединяете?
В рекурсии, кстати, не вижу соединения по product_id.
В рекурсии вызывается
в модели у меня связь:
Код: Выделить всё
public function getProductOption()
{
return $this->hasMany(ProductOption::className(), ['product_id' => 'product_id']);
}
В рекурсии в функцию $query передается по ссылке "&", т.е.
&$query . И мы получаем что к каждому joinWith в нем добавляем рекурсивно еще joinWith и так далее. И у нас выстраивается при помощи рекурсии правильные ON в joinWith,
т.е. в первом left join
ON (`po`.`product_id` = `po_cvet`.`product_id`) во втором
ON (`po_cvet`.`product_id` = `po_razmer`.`product_id`), а в третьем:
ON (`po_razmer`.`product_id` = `po_ves`.`product_id`) - вот для этого нужна рекурсия!
А как без рекурсии сделать я не пойму, вы написали как то можно, можете как то подсказать?
Вот немного переделал рекурсию:
Код: Выделить всё
static public function recursiveRelation(&$query, $group_slug, $filters_array)
{
foreach ($filters_array as $group => $filter) {
// if($group != $group_slug) {
return $query->joinWith(['productOption' => function ($q) use ($filter, $group, $group_slug, $filters_array) {
$q->alias("po_{$group}")
->andOnCondition(['IN', "`po_{$group}`.slug", $filter])
->andOnCondition(["`po_{$group}`.slug_option" => $group]);
unset($filters_array[$group]);
return static::recursiveRelation($q, $group_slug, $filters_array);
}]);
// }
}
}
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.26, 19:01
yiiliveext
webplus писал(а): ↑2019.12.26, 13:32
А как без рекурсии сделать я не пойму, вы написали как то можно, можете как то подсказать?
Код: Выделить всё
$query = ProductOption::find()->alias('po');
foreach ($filters_array as $group => $filter) {
$query->joinWith(['productOption' => function ($q) use ($filter, $group, $filters_array) {
$q->andOnCondition(['IN', "`po_{$group}`.slug", $filter])
->andOnCondition(["`po_{$group}`.slug_option" => $group])
->alias("po_{$group}");
}]);
$query->prepare(Yii::$app->db->queryBuilder);
$query->alias("po_{$group}");
}
$query->alias("po");
$query->join = array_reverse($query->join);
$query->select("`po`.*, po.value_ru AS value, COUNT(po_$group.product_id)")
->join('JOIN', $productsQuery->tablesUsedInFrom, $productsQuery->tablesUsedInFrom['{{shop_product}}'] . '.id = po.product_id')
->groupBy('po.slug');
die(var_dump($query->prepare(Yii::$app->db->queryBuilder)->createCommand()->rawSql));
Мне другое непонятно, зачем вам этот цикл foreach ($groups as $_group)
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.26, 19:58
webplus
yiiliveext писал(а): ↑2019.12.26, 19:01
Мне другое непонятно, зачем вам этот цикл foreach ($groups as $_group)
Да ваш вариант также работает.
Я даже не знал что можно делать так:
Код: Выделить всё
$query->prepare(\Yii::$app->db->queryBuilder);
и
Код: Выделить всё
$query->join = array_reverse($query->join);
У меня в начале выбираются все группы:
Код: Выделить всё
$groups = ProductOption::find()
->select([ProductOption::tableName() . '.*', ProductOption::tableName() . '.option_' . \Yii::$app->language . ' as group_option'])
->leftJoin($productsQuery->tablesUsedInFrom, $productsQuery->tablesUsedInFrom['{{shop_product}}'] . '.id = ' . ProductOption::tableName() . '.product_id')
->where($productsQuery->where)
->groupBy('group_option')
->orderBy('group_option ASC')
->asArray()
->all();
// Потом в цикле к массиву добавляются фильтры
$out = [];
$i = 0;
foreach ($groups as $_group) {
$out[$i]['parent'] = $_group['group_option'];
// Тут код query
$out[$i]['filters'] = $query->asArray()->all();
$i++;
}
Я сделал это чтобы во вьюхе вывести так
Код: Выделить всё
<?php foreach ($filters as $filter):?>
<div class="panel panel-default">
<div class="panel-heading"><?=$filter['parent']?></div>
<div class="panel-body">
<?php foreach ($filter['filters'] as $item):?>
<p><?=FilterCheckboxWidget::widget(['url' => ['catalog/list', 'slug' => $category->slug],'item' => $item, 'productsQuery' => $productsQueryClone])?></p>
<?php endforeach;?>
</div>
</div>
<?php endforeach;?>
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.26, 20:35
yiiliveext
Та вы издеваетесь. Зачем выполнять столько запросов, если у вас уже все есть.
Код: Выделить всё
private function getFilters($productsQuery, $filters_array)
{
$query = ProductOption::find()->alias('po');
foreach ($filters_array as $group => $filter) {
$query->joinWith(['productOption' => function ($q) use ($filter, $group, $filters_array) {
$q->andOnCondition(['IN', "`po_{$group}`.slug", $filter])
->andOnCondition(["`po_{$group}`.slug_option" => $group])
->alias("po_{$group}");
}]);
$query->prepare(Yii::$app->db->queryBuilder);
$query->alias("po_{$group}");
}
$query->alias("po");
$query->join = array_reverse($query->join);
$items = $query->select("`po`.*, `po`.option_ru AS group_option, po.value_ru AS value, COUNT(po_$group.product_id)")
->join('JOIN', $productsQuery->tablesUsedInFrom, $productsQuery->tablesUsedInFrom['{{shop_product}}'] . '.id = po.product_id')
->where($productsQuery->where)
->groupBy('po.slug')->asArray()->all();
$out = [];
$i = 0;
$items = ArrayHelper::index($items, null, 'group_option');
foreach ($items as $key => $value) {
$out[$i]['parent'] = $key;
$out[$i]['filters'] = $value;
$i++;
}
return $out;
}
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.26, 20:53
webplus
yiiliveext писал(а): ↑2019.12.26, 20:35
Та вы издеваетесь. Зачем выполнять столько запросов, если у вас уже все есть.
А как мне исключение в рамках одной группы сделать, чтобы в одной группе не учитывались фильтры этой группы
я делаю так:
т.е.
Код: Выделить всё
foreach ($filters_array as $group => $filter) {
if ($group != $group_slug) {
$query->joinWith(['productOption' => function ($q) use ($filter, $group, $filters_array) {
$q->andOnCondition(['IN', "`po_{$group}`.slug", $filter])
->andOnCondition(["`po_{$group}`.slug_option" => $group])
->alias("po_{$group}");
}]);
$query->prepare(\Yii::$app->db->queryBuilder);
$query->alias("po_{$group}");
$alias = "`po_{$group}`";
}
}
$query->alias("po");
if(!empty($query->join)) {
$query->join = array_reverse($query->join);
}
$group_slug - берется из группированного запроса в цикле групп.
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.26, 21:59
yiiliveext
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.26, 22:06
webplus
Спасибо! Только вот как в activeRecord в yii2 все это собрать. тут надо подумать
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.26, 22:28
yiiliveext
webplus писал(а): ↑2019.12.26, 22:06
Спасибо! Только вот как в activeRecord в yii2 все это собрать. тут надо подумать
Через $query->union()
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.26, 23:30
yiiliveext
Как-то так, адаптируйте под себя, это я тестовый пример делал.
Код: Выделить всё
$lastGroup = null;
$fGroups = ShopProductOption::find()->alias('po')
->select('slug_option')
->join('JOIN', '{{shop_product}}', '{{shop_product}}.id = po.product_id')
->groupBy('po.slug_option')->column();
$unions = [];
foreach ($fGroups as $fGroup) {
$query = ShopProductOption::find()->alias('po');
$query->select("`po`.*, `po`.option_ru AS group_option, po.value_ru AS value");
if (!empty($filters_array)) {
foreach ($filters_array as $group => $filter) {
if ($group != $fGroup) {
$query->joinWith(['productOption' => function ($q) use ($filter, $group, $filters_array) {
$q->andOnCondition(['IN', "`po_{$group}`.slug", $filter])
->andOnCondition(["`po_{$group}`.slug_option" => $group])
->alias("po_{$group}");
}]);
$query->prepare(Yii::$app->db->queryBuilder);
$query->alias("po_{$group}");
$lastGroup = $group;
}
}
$query->alias("po");
$query->join = array_reverse($query->join);
$query->addSelect("COUNT(po_$lastGroup.product_id) as count");
} else {
$query->addSelect("COUNT(po.product_id) as count");
}
$query->join('JOIN', '{{shop_product}}', '{{shop_product}}.id = po.product_id')
->andWhere(['po.slug_option' => $fGroup])
->groupBy('po.slug');
$unions[] = clone $query;
unset($query);
}
$query = array_shift($unions);
foreach ($unions as $union) {
$query->union($union);
}
$items = $query->asArray()->all();
$items = ArrayHelper::index($items, null, 'group_option');
$out = array_map(function ($filterGroup, $filters) {
return ['parent' => $filterGroup, 'filters' => $filters];
}, array_keys($items), array_values($items));
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.26, 23:31
webplus
yiiliveext писал(а): ↑2019.12.26, 22:28
webplus писал(а): ↑2019.12.26, 22:06
Спасибо! Только вот как в activeRecord в yii2 все это собрать. тут надо подумать
Через $query->union()
Но ведь по логики union-ы должны быть в циклах групп, в цикле фильтров у нас left join-ы создаются, а union - это я так понимаю в цикле групп запрос в union вставляется.
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.26, 23:36
yiiliveext
webplus писал(а): ↑2019.12.26, 23:31
Но ведь по логики union-ы должны быть в циклах групп, в цикле фильтров у нас left join-ы создаются, а union - это я так понимаю в цикле групп запрос в union вставляется.
Смотрите пример выше. Рабочий, проверен.
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.27, 08:36
unknownby
Хорошо бы еще перенести всю логику из контроллера в модель
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.27, 09:45
yiiliveext
unknownby писал(а): ↑2019.12.27, 08:36
Хорошо бы еще перенести всю логику из контроллера в модель
У человека это в приватном методе. С чего вы решили, что это не в модели?
Re: relation joinWith в цикле, смена alias
Добавлено: 2019.12.27, 10:14
unknownby
yiiliveext писал(а): ↑2019.12.27, 09:45
unknownby писал(а): ↑2019.12.27, 08:36
Хорошо бы еще перенести всю логику из контроллера в модель
У человека это в приватном методе. С чего вы решили, что это не в модели?
Листал и вот было написано
http://prntscr.com/qga3s6