Получить данные через промежуточную таблицу.

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
slo_nik
Сообщения: 344
Зарегистрирован: 2013.10.07, 19:08

Получить данные через промежуточную таблицу.

Сообщение slo_nik »

Добрый день.
Есть таблица категорий, товаров и промежуточная таблицы.
В модели категорий родительские и дочерние категории, связаны через parent_id. Так же имеется связь на промежуточную таблицу, связь через category_id.

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

public function getParent()
{
    return $this->hasOne(Catalog::class, ['id' => 'parent_id']);
}
    
public function getCategories()
{
    return $this->hasMany(Catalog::class, ['parent_id' => 'id']);
}

public function getProducts()
{
    return $this->hasMany(Products::class,['id' => 'product_id'])->viaTable('{{%products_categories}}', ['category_id' => 'id']);
}
В модели товаров есть связь на промежуточную таблицу через product_id

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

public function getCategoriesProduct()
{
    return $this->hasMany(Catalog::class, ['id' => 'category_id'])->with('parent')->viaTable('{{%products_categories}}', ['product_id' => 'id']);
}
Получить товары связанные с дочерней категорией или, наоборот, дочернюю категорию, связанную с товарами особого труда не составляет.
Теперь мне потребовалось вывести родительские категории и получить товары, которые связаны с дочерними категориями текущей родительской категории.
Например, есть родительские категории "Игровые комплексы" и "Игрушки". У этих родительский категорий есть дочерние "Детские горки", "Детские домики" для первой и "Мягкие игрушки", "Умные игрушки" для второй. Теперь на главной мне необходимо вывести товары в таком виде.
ИГРОВЫЙ КОМПЛЕКСЫ
Детские горки, Детские домики
ИГРУШКИ
Мягкие игрушки, Умные игрушки
Такой запрос позволяет вывести мне родительские категории у которых есть дочерние категории с привязанными товарами.

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

$query = Catalog::find()
    ->from(['catalog' => Catalog::tableName()])
    ->where(['catalog.status' => Catalog::STATUS_ACTIVE, 'catalog.parent_id' => null])
    ->with(['categories', 'products'])
    ->joinWith([
        'categories' => function(ActiveQuery $query){
            $query->from(['category' => Catalog::tableName()]);
        }
    ])
В debug панели он выглядит так

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

SELECT COUNT(*) FROM `catalog` `catalog`
     LEFT JOIN `catalog` `category` ON `catalog`.`id` = `category`.`parent_id`
     WHERE (`catalog`.`status`=1) AND (`catalog`.`parent_id` IS NULL)
Так же идёт запрос на промежуточную таблицу где в IN() находятся id родительских категорий, а их в промежуточной таблице вообще нет, там хранятся только id дочерних категорий.

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

SELECT * FROM `products_categories` WHERE `category_id` IN (1, 2, 3, 14, 19)
Если я пытаюсь добавить joinWith для товаров

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

$query = Catalog::find()
    ->from(['catalog' => Catalog::tableName()])
    ->where(['catalog.status' => Catalog::STATUS_ACTIVE, 'catalog.parent_id' => null])
    ->with(['categories', 'products'])
    ->joinWith([
        'categories' => function(ActiveQuery $query){
            $query->from(['category' => Catalog::tableName()]);
        }
    ])
    ->joinWith([
        'products' => function(ActiveQuery $query){
           $query->from(['product' => Products::tableName()])->where(['product.status' => Products::STATUS_ACTIVE]);
        }
    ])
то запрос получается такой

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

SELECT COUNT(*) FROM `catalog` `catalog`
   LEFT JOIN `catalog` `category` ON `catalog`.`id` = `category`.`parent_id`
   LEFT JOIN `products_categories` ON `catalog`.`id` = `products_categories`.`category_id`
   LEFT JOIN `products` `product` ON `products_categories`.`product_id` = `product`.`id`
   WHERE ((`catalog`.`status`=1) AND (`catalog`.`parent_id` IS NULL)) AND (`product`.`status`=2)
и в итоге вообще ничего не выводится. Ни родительских категорий, ни товаров.
Подскажите, как изменить запрос, чтобы подтягивались товары с учётом limit(4) для каждой родительской категории?
Аватара пользователя
Alexum
Сообщения: 683
Зарегистрирован: 2016.09.26, 10:00

Re: Получить данные через промежуточную таблицу.

Сообщение Alexum »

Похоже, что вы джоините продукты для корневых категорий, а можно в первом подзапросе для связи 'categories', т.е. от дочерних категорий. И у вас одновременно и with() и joinWith() на одни и теже связи. В чём смысл? Как вариант, можно обойтись чем-то вида ->with(['categories.products']).
slo_nik
Сообщения: 344
Зарегистрирован: 2013.10.07, 19:08

Re: Получить данные через промежуточную таблицу.

Сообщение slo_nik »

Alexum писал(а): 2018.04.09, 11:33 Похоже, что вы джоините продукты для корневых категорий, а можно в первом подзапросе для связи 'categories', т.е. от дочерних категорий. И у вас одновременно и with() и joinWith() на одни и теже связи. В чём смысл? Как вариант, можно обойтись чем-то вида ->with(['categories.products']).
Для родительских категорий вообще нет товаров, есть только для дочерних. На вывести родительские категории и под каждой из категорий вывести товары для её дочерних.
И если убрать joinWith(), то выведет все родительские категории, даже те, у которых для дочерних нет товара, а мне надо только те, у которых дочерние связаны с товаром.
Аватара пользователя
proctoleha
Сообщения: 298
Зарегистрирован: 2016.07.10, 19:00

Re: Получить данные через промежуточную таблицу.

Сообщение proctoleha »

Вообще привязывать друг к другу id <-> parent_id - это решение в лоб, сделать по быстрому и не заморачиваться. Но, очень часто, потом, наступает момент когда заморачиваться всё-таки приходится. И тогда с удивлением обнаруживаешь, что умные люди давно придумали Nested Sets, в том числе и для yii2. Это в плане рекомендаций на будущее, при планировании структуры приложений.

А так, для начала попробуйте поработать напрямую с БД, тупо составить SQL запрос, который бы всё выбирал, и потом адаптировать его к yii2. Т.е. попробовать, сначала, поработать на самом низком уровне, непосредственно с БД.
Вот за что я не люблю линукс, так это за свои кривые, временами, руки
andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Получить данные через промежуточную таблицу.

Сообщение andku83 »

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

$query = Catalog::find()
    ->from(['catalog' => Catalog::tableName()])
    ->where(['catalog.status' => Catalog::STATUS_ACTIVE, 'catalog.parent_id' => null])
    ->with(['categories'])
    ->innerJoinWith([
        'categories categories',
        'categories.products prod' => function(ActiveQuery $query){
           $query->andOnCondition(['status' => Products::STATUS_ACTIVE]);
        }
    ])
а реализацию получения товаров с лимитом в 4 нужно делать через отельную логику, и запрашиваться они будут отдельным запросом для каждой корневой категории
slo_nik
Сообщения: 344
Зарегистрирован: 2013.10.07, 19:08

Re: Получить данные через промежуточную таблицу.

Сообщение slo_nik »

а реализацию получения товаров с лимитом в 4 нужно делать через отельную логику, и запрашиваться они будут отдельным запросом для каждой корневой категории
А пример можно, хотя бы схематичный?
andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Получить данные через промежуточную таблицу.

Сообщение andku83 »

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

    /**
     * @param integer $limit
     * @return Product[]
     */
    public function getProductsWithLimit($limit = 4)
    {
        $catList = ArrayHelper::getColumn($this->categories, 'id');
        $catList[] = $this->id; //если вдруг товары могут быть и в родительской категории

        $query = Product::find()->active()->distinct()
            ->with('...')
            ->innerJoinWith('categoriesProduct categoriesProduct')
            ->andWhere(['categoriesProduct.id' => $catList])
            ->limit($limit);
        return $query->all();
    }
slo_nik
Сообщения: 344
Зарегистрирован: 2013.10.07, 19:08

Re: Получить данные через промежуточную таблицу.

Сообщение slo_nik »

andku83 писал(а): 2018.04.09, 15:42

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

    /**
     * @param integer $limit
     * @return Product[]
     */
    public function getProductsWithLimit($limit = 4)
    {
        $catList = ArrayHelper::getColumn($this->categories, 'id');
        $catList[] = $this->id; //если вдруг товары могут быть и в родительской категории

        $query = Product::find()->active()->distinct()
            ->with('...')
            ->innerJoinWith('categoriesProduct categoriesProduct')
            ->andWhere(['categoriesProduct.id' => $catList])
            ->limit($limit);
        return $query->all();
    }
Не совсем понял, этот запрос как бы отдельно идёт от первого. В первом запросе нельзя вывести данные с лимитом?
andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Получить данные через промежуточную таблицу.

Сообщение andku83 »

там лимит будет влиять на другие данные
slo_nik
Сообщения: 344
Зарегистрирован: 2013.10.07, 19:08

Re: Получить данные через промежуточную таблицу.

Сообщение slo_nik »

andku83 писал(а): 2018.04.10, 16:07 там лимит будет влиять на другие данные
Да, я пробовал в свой первый запрос добавлять лимит, но выводило только для одной категории товары.
В Вашем примере используется "->active()", зачем? И в "->with('...')" подразумевается связь на связующую таблицу?
andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Получить данные через промежуточную таблицу.

Сообщение andku83 »

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

    public function active()
    {
        return $this->andWhere(['!=', '{{%product}}.status', Product::STATUS_OFF]);
    }
"->with('...')" - для полученияя дополнительных связей если они нужны
slo_nik
Сообщения: 344
Зарегистрирован: 2013.10.07, 19:08

Re: Получить данные через промежуточную таблицу.

Сообщение slo_nik »

andku83 писал(а): 2018.04.16, 11:35

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

    public function active()
    {
        return $this->andWhere(['!=', '{{%product}}.status', Product::STATUS_OFF]);
    }
"->with('...')" - для полученияя дополнительных связей если они нужны
Благодарю, буду дальше разбираться.
Ответить