Страница 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.
В рекурсии вызывается

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

$query->joinWith(['productOption'])
в модели у меня связь:

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

    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 Та вы издеваетесь. Зачем выполнять столько запросов, если у вас уже все есть.
А как мне исключение в рамках одной группы сделать, чтобы в одной группе не учитывались фильтры этой группы
я делаю так:

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

if ($group != $group_slug) {

}
т.е.

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

                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 :D