Задать вопрос
@Aleksei-22

Как правильно отфильтровать товары, значения свойств которых(цвет, размер и т.п.) находятся в одной колонке таблицы?

Есть 3 таблицы property(id, свойства продукта), property-value(id, property-id, значение свойств) и value-product(value-id, product-id):
5fea369697903221210062.jpeg
5fea36b5668f1930878627.jpeg
5fea36c62f9eb929074284.jpeg

Часть кода модели поиска:
.......
    public function search($params, $id, $sort_get = null, $sort_get_limit = null) {
            $query = Product::find()
                    ->where(['category_id' => $id])
                    ->joinWith(['brand', 'valueProducts.value', 'vidTovara', 'productComments'])
                    ->groupBy('id');
        }
        $dataProvider = new ActiveDataProvider([
            'query' => $query,
            'pagination' => [
                'totalCount' => $query->count(),
                'defaultPageSize' => 3,
                'pageSize' => $sort_get_limit,
                'forcePageParam' => false, 
                'pageSizeParam' => false
                ],
            'sort' => [
                'attributes' => [
                    'price' => [
                        'asc' => ['price' => SORT_ASC],
                        'desc' => ['price' => SORT_DESC],
                    ],
                    'name' => [
                        'asc' => ['name' => SORT_ASC],
                        'desc' => ['name' => SORT_DESC],
                    ],
                    'id' => [
                        'asc' => ['id' => SORT_ASC],
                        'desc' => ['id' => SORT_DESC],
                    ],
                ],
            ]
        ]);        
        $sort = (isset($sort_get) ? $sort_get : 'id');
        $string = $sort_get[0];
        if ($string === '-') { 
            $sort_minus = substr($sort, 1);
            $dataProvider->sort->defaultOrder[$sort_minus] = SORT_DESC;
        } else {
            $dataProvider->sort->defaultOrder[$sort] = SORT_ASC;
        }        
        $this->load($params);
        if (!$this->validate()) {
            // uncomment the following line if you do not want to return any records when validation fails
            // $query->where('0=1');
            return $dataProvider;
        }
        // grid filtering conditions
        $query->andFilterWhere([
            'brand.name' => $this->brand,
            'new' => $this->new,
            'hit' => $this->hit,
            'property_values.value' => $this->property,
        ]);
        $query->andFilterWhere([
                    'and',
                    ['>=', 'price', $this->min_price],
                    ['<=', 'price', $this->max_price],
                ])
                ->andFilterWhere([
                    'or',
                    ['>', 'quantity', $this->av],
                    ['=', 'quantity', $this->notav],
                ])
                ->andFilterWhere([
                    'and',
                    ['>', 'old_price', $this->skidka],
                ]);
        return $dataProvider;
    }
}

Часть кода фильтра с формой, который в представлении выводится слева:
use yii\bootstrap4\ActiveForm;
use yii\helpers\ArrayHelper;
/* @var $this yii\web\View */
/* @var $model frontend\models\ProductSearch */
/* @var $form yii\widgets\ActiveForm */
?>
<div class="product-search">
        $form = ActiveForm::begin([
            'action' => ['view', 'id' => $_id],
            'method' => 'get',        
            'options' => [
                'data-pjax' => 1,
                'id' => 'form_sort',
            ],
        ]); 
    }?>    
   .......
    <div class="filter_zagl_div">
        <?php $brand = ArrayHelper::map($category_id, 'brand.name', 'brand.name') ?>
        <?php if ($brand): ?>       
        <span class="">Бренд</span>
        <?php echo $form->field($model, 'brand')->checkboxList(ArrayHelper::map($category_id, 'brand.name', 'brand.name'))->label(false) ?> 
        <?php endif; ?>
        <?php foreach ($property as $propertys): ?>
        <span class=""><?php echo $propertys['name']; ?></span>
        <?php $value = (new \frontend\models\PropertyValues())->getValues($propertys, $_id);  ?> 
        <?php echo $form->field($model, 'property')->label(false)->checkboxList(ArrayHelper::map($value, 'value', 'value')) ?>
        <?php endforeach; ?>
    </div>    
    <?php ActiveForm::end(); ?>   
</div>


Вот так это выглядит:
5fea40ae8d2bb531123688.jpeg
Фильтр работает, всё фильтруется, но не совсем так, как нужно.
Так как свойства у фильтров, начиная с "количество цветов" и ниже, находятся в одной колонке таблицы, то если выбрать, например количество цветов 12 и количество карандашей 24 то фильтр покажет и товар с 24 цветами, и товар с 12 карандашами, хотя, по идее, фильтр должен ничего не показать, так как товара одновременно с 24 цветами и 12 карандашами в базе нет.
5fea42bab1215485954093.jpeg

Пробовал сделать вот таким способом:
Модель поиска (изменённый код):
..........
        $query->andFilterWhere([
            'property_values.value' => $this->p1,
        ]);              
        $query->andFilterWhere([
            'property_values.value' => $this->p2,
        ]);        
        $query->andFilterWhere([
            'property_values.value' => $this->p3,
        ]);        
        $query->andFilterWhere([
                    'and',
                    ['>=', 'price', $this->min_price],
                    ['<=', 'price', $this->max_price],
                ])
                ->andFilterWhere([
                    'or',
                    ['>', 'quantity', $this->av],
                    ['=', 'quantity', $this->notav],
                ])
                ->andFilterWhere([
                    'and',
                    ['>', 'old_price', $this->skidka],
                ]);
        return $dataProvider;
    }
}


Изменённая часть кода фильтра с формой, который в представлении выводится слева:
.......
<div class="filter_zagl_div">
        <?php $brand = ArrayHelper::map($category_id, 'brand.name', 'brand.name') ?>
        <?php if ($brand): ?>       
        <span class="">Бренд</span>
        <?php echo $form->field($model, 'brand')->checkboxList(ArrayHelper::map($category_id, 'brand.name', 'brand.name'))->label(false) ?> 
        <?php endif; ?>
        <?php $i = 1; foreach ($property as $propertys): ?>
        <span class=""><?php echo $propertys['name']; ?></span>
        <?php $value = (new \frontend\models\PropertyValues())->getValues($propertys, $_id);  ?> 
        <?php echo $form->field($model, 'p'.$i.'')->label(false)->checkboxList(ArrayHelper::map($value, 'value', 'value'));
        if ($i == 15){ break; }$i++;?>
        <?php endforeach; ?>
    </div>
.......


При таком коде, если фильтровать в двух позициях, то ничего не выводится. Так как видимо идёт два запроса к одной таблице.
5fea4598d8e70055687140.jpeg
5fea4709b863a437126856.jpeg

Выводимые данные при print_r DataProvider'а:
5fea4875488dc288748416.jpeg

Фильтры цены, бренда, новинок и т.п. работают правильно, т.к. находятся в разных таблицах.
Подскажите пожалуйста, как можно сделать, чтобы фильтры из одной таблицы(из одной колонки) работали по группам. Выбрал количество цветов и 12, и 24, фильтр показал и товар с 12 цветами, и товар с 24 цветами. А если выбрал количество цветов 24 и количество карандашей 12, то фильтр показал бы товар у которого количество цветов равно 24 и количество карандашей равно 12(если есть такой товар, у меня в базе его нет), а не показывал бы два товара, у одного есть 24 цвета, а у другого 12 карандашей.

Сделал таким образом:
$query = Product::find()
                    ->where(['category_id' => $id])
                    ->joinWith(['brand', 'vidTovara', 'productComments'])
                    ->leftJoin('value_product AS vp1', '`product`.`id` = `vp1`.`product_id`')
                    ->leftJoin('value_product AS vp2', '`product`.`id` = `vp2`.`product_id`')
                    ->leftJoin('property_values AS value1', '`vp1`.`value_id` = `value1`.`id`')
                    ->leftJoin('property_values AS value2', '`vp2`.`value_id` = `value2`.`id`')
                    ->groupBy('id');
......
$query->andFilterWhere([
            'value1.value' => $this->p1,
        ]);      
        
        $query->andFilterWhere([
            'value2.value' => $this->p2,
        ]);

Теперь фильтр работает правильно. Только как это всё нормально можно сделать, если фильтров может быть до 15-ти. Получается много LEFT JOIN.

Так тоже работает правильно.
$query = Product::find()
->where(['category_id' => $id])
->joinWith(['brand', 'vidTovara', 'productComments'])
->groupBy('id');
.............
$pv = ['p1', 'p2', 'p3'];
$i = 1; foreach ($pv as $q):
$query->leftJoin('value_product AS v'.$q.'', '`product`.`id` = `v'.$q.'`.`product_id`')
->leftJoin('property_values AS value'.$i.'', '`v'.$q.'`.`value_id` = `value'.$i.'`.`id`')
->andFilterWhere(['value'.$i.'.value' => $this->$q]);
if ($i == 15){ break; }$i++;
endforeach;
Можно ли сделать так, только подумать над переменными и условиями? Или есть другое решение?
  • Вопрос задан
  • 174 просмотра
Подписаться 1 Простой 10 комментариев
Решения вопроса 1
@Aleksei-22 Автор вопроса
foreach ($filter_c as $q):
            $query->joinWith(['valueProducts '.$q.'', 'valueProducts.value v'.$q.''])
                ->andFilterWhere(['v'.$q.'.value' => $this->$q]);
     endforeach;
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы