Yii2 GridView SearchModel для подсчета отношений - PullRequest
0 голосов
/ 12 декабря 2018

У меня есть модель item, которая имеет отношение hasMany к модели image.В элементе GridView у меня есть столбец, который показывает, сколько изображений имеет item.Я хотел бы иметь возможность сортировать по этому столбцу и, возможно, иметь возможность фильтрации с флажком, чтобы показать только item с без image с.

Я попытался добавить проверку в модель поисказапросить with отношение images, и я решил добавить andFilterWhere, где число image меньше, чем входное значение.Однако я не уверен, что лучший способ иметь такую ​​логику, есть ли лучший Yii способ фильтрации по этому количеству?

Обновление Следующая информация от этот вопрос Я обновил мою item модель, чтобы иметь метод, который возвращает счетчик отношений.

Затем я обновил свою модель поиска следующим образом:

class ItemSearch extends Item
{
    public $image_count;

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['id', 'product_id', 'manufacturer_id', 'scale_id', 'owner_id', 'quantity'], 'integer'],
            [['sku_number', 'description', 'details', 'condition'], 'safe'],
            [['price', 'sale_price', 'image_count'], 'number'],
        ];
    }

    /**
     * @inheritdoc
     */
    public function scenarios()
    {
        // bypass scenarios() implementation in the parent class
        return Model::scenarios();
    }

    /**
     * Creates data provider instance with search query applied
     *
     * @param array $params
     *
     * @return ActiveDataProvider
     */
    public function search($params)
    {
        $query = Item::find();
        $subQuery = Image::find()->select('item_id, COUNT(id) as image_count')->groupBy('item_id');
        $query->leftJoin(['imageCount' => $subQuery], 'imageCount.item_id = id');

        // add conditions that should always apply here

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);

        $dataProvider->setSort([
            'attributes' => [
                'id',
                'product_id',
                'manufacturer_id',
                'scale_id',
                'owner_id',
                'image_count' => [
                    'asc' => ['imageCount.image_count' => SORT_ASC],
                    'desc' => ['imageCount.image_count' => SORT_DESC],
                    'label' => 'Images'
                ]
            ]
        ]); 

        $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
        if (!empty($this->id)) {
            $query->andFilterWhere([
                'id' => $this->id,
            ]);
        }
        if (!empty($this->product_id)) {
            $query->andFilterWhere([
                'product_id' => $this->product_id,
            ]);
        }
        if (!empty($this->manufacturer_id)) {
            $query->andFilterWhere([
                'manufacturer_id' => $this->manufacturer_id,
            ]);
        }
        if (!empty($this->scale_id)) {
            $query->andFilterWhere([
                'scale_id' => $this->scale_id,
            ]);
        }
        if (!empty($this->owner_id)) {
            $query->andFilterWhere([
                'owner_id' => $this->owner_id,
            ]);
        }
        if (!empty($this->image_count)) {
            $query->andWhere(['is','imageCount.image_count',new \yii\db\Expression('null')]);
         }

        $query->andFilterWhere(['like', 'sku_number', $this->sku_number])
            ->andFilterWhere(['like', 'description', $this->description])
            ->andFilterWhere(['like', 'details', $this->details])
            ->andFilterWhere(['like', 'condition', $this->condition]);

        return $dataProvider;
    }
}

Проблема теперь в том, что вышеприведенный запрос выдаст что-то вроде этого:

SELECT `item`.* FROM `item` LEFT JOIN (SELECT `item_id`, COUNT(id) as image_count FROM `image` GROUP BY `item_id`) `imageCount` ON imageCount.item_id = id WHERE `imageCount`.`image_count`=0

Проблема в том, что items с 0 image s больше не показываются (и флажок ничего не показывает)потому что объединение ничего не найдет для этого идентификатора в таблице image 1034 *

Ответы [ 2 ]

0 голосов
/ 17 декабря 2018

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

Тогда ваш метод ItemSearch::search() сможет изменить это значение:

public function search($params)
{
    $query = Item::find()->select('*, (select count(*) from image where image.item_id = item.id) as image_count'); // If the table name of your Image table is image, if the table name of your Item table is item

    // add conditions that should always apply here

    $dataProvider = new ActiveDataProvider([
        'query' => $query,
    ]);

    $dataProvider->setSort([
        'attributes' => [
            'id',
            'product_id',
            'manufacturer_id',
            'scale_id',
            'owner_id',
            'image_count'
        ]
    ]); 

    $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
    if (!empty($this->id)) {
        $query->andFilterWhere([
            'id' => $this->id,
        ]);
    }
    if (!empty($this->product_id)) {
        $query->andFilterWhere([
            'product_id' => $this->product_id,
        ]);
    }
    if (!empty($this->manufacturer_id)) {
        $query->andFilterWhere([
            'manufacturer_id' => $this->manufacturer_id,
        ]);
    }
    if (!empty($this->scale_id)) {
        $query->andFilterWhere([
            'scale_id' => $this->scale_id,
        ]);
    }
    if (!empty($this->owner_id)) {
        $query->andFilterWhere([
            'owner_id' => $this->owner_id,
        ]);
    }
    if (!empty($this->image_count)) {
        $query->andWhere([
            'image_count' => $this->image_count,
        ]);
     }

    $query->andFilterWhere(['like', 'sku_number', $this->sku_number])
        ->andFilterWhere(['like', 'description', $this->description])
        ->andFilterWhere(['like', 'details', $this->details])
        ->andFilterWhere(['like', 'condition', $this->condition]);

    return $dataProvider;
}

Однако, если для вашего image_count часто требуются вычисления и статистика, а объем данных большой, рекомендуется переопределить это поле в таблице элементов.

ОБНОВЛЕНИЕ:

Моя ошибка, агрегированные поля не могут быть отфильтрованы по условию where, но вы можете использовать:

$query->andFilterHaving([
    'image_count' => $this->image_count,
]);
0 голосов
/ 12 декабря 2018

Вы должны проверить WHERE imageCount.image_count IS NULL вместо WHERE imageCount.image_count=0, поскольку те строки, которые не имеют связанных изображений, будут отображаться как null в image_count и изменить условие с

$query->andFilterWhere(['imageCount.image_count' => 0]); 

до

$query->andWhere(['is','imageCount.image_count',new \yii\db\Expression('null')]);`

Ваш запрос должен быть сгенерирован как

SELECT `item`.* FROM `item` 
LEFT JOIN
(
   SELECT `item_id`, COUNT(id) as image_count 
   FROM `image` 
   GROUP BY `item_id`
) `imageCount` 

ON imageCount.item_id = id 

WHERE `imageCount`.`image_count` is NULL

Обновление

Поскольку у вас все еще возникают проблемы,Я подумал, что у меня есть свободное время, чтобы найти эту проблему, и у меня возникло следующее

У вас возникли проблемы при фильтрации по элементам, имеющим 0 для изображений. У меня нет точной схемы, которую выесть, но я приведу пример ниже с похожим сценарием, где у меня есть Shoots и связанные с ним теги в соединительной таблице ShootTag модель

  • Shoots

Ниже приведен пример схемы и данных

+----+------------+--------+------------+
| id | name       | active | shoot_type |
+----+------------+--------+------------+
|  1 | aslam omer |      1 | modeling   |
|  2 | asif       |      1 | modeling   |
|  3 | saleem     |      1 | modeling   |
|  4 | sajid      |      1 | modeling   |
|  5 | tasleem    |      1 | modeling   |
|  6 | tehseen    |      1 | modeling   |
|  7 | amjad      |      1 | modeling   |
|  8 | shaban     |      1 | modeling   |
|  9 | irfan      |      1 | modeling   |
+----+------------+--------+------------+
  • ShootTags

Ниже приведен пример схемы и данных

+----------+--------+
| shoot_id | tag_id |
+----------+--------+
|        1 |      1 |
|        1 |      2 |
|        1 |      3 |
|        1 |      4 |
|        2 |      1 |
|        2 |      2 |
|        2 |      3 |
|        3 |      1 |
|        4 |      1 |
|        4 |      4 |
+----------+--------+

Теперь, учитывая приведенные выше таблицы и данные, у меня есть поисковая модель с именем ShootsSearch, которую я использую.g, чтобы отобразить все снимки, и я хочу показать количество тегов для каждого снимка, сохраненного в модели ShootTag.

Я не добавляю код для GridView, так как он не имеет значения, мойПоисковая модель ShootsSearch имеет следующий метод поиска, который работает правильно для любого подсчета по отношению к выстрелам в модели меток.

  • Что отличается от вашего кода тем, что я использую модель ShootsSearch для первого запроса, а не модель Shoots, так как вы используете Item::find();, который должен быть ItemSearch::find(); вместо этого в качестве псевдонима, который вы используете image_count, объявляется в модели поиска,

  • Затем строка new Expression('if(st.totalTags is NOT NULL,st.totalTags,0) as totalTags') в основном запросе должна отображать нулевые значения как 0так что вы можете использовать условный выбор здесь.

  • Затем вам нужно проверить if ($this->totalTags == '0') { для применения $query->andWhere(['IS', 'totalTags', new Expression('null')]);, поскольку фактическое значение будет null для totalTags, гденикакие теги недоступны для любых Shoot, а в остальной части вы будете использовать query->andFilterWhere(['=', 'totalTags', $this->totalTags]);

. Это работает правильно во всех трех сценариях, которые вы хотите видеть на изображениях ниже

Вид по умолчанию в первый раз

enter image description here

Поиск снимков с общим количеством тегов 4

enter image description here

Поиск снимков с счетчиком TotalTags 0

enter image description here

Вы должнызамените следующее в своем коде

  • public $totalTags на public $image_count.
  • ShootTags на Image.
  • shoot_id на item_id.
  • ShootsSearch с ItemSearch / self.

Вот модель поиска, код проверен и работает.

class ShootsSearch extends Shoots
{
    /**
     * @var mixed
     */
    public $totalTags;

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['id', 'active', 'totalTags'], 'integer'],
            [['name', 'shoot_type', 'description', 'totalTags'], 'safe']
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function scenarios()
    {
        // bypass scenarios() implementation in the parent class
        return Model::scenarios();
    }

    /**
     * Creates data provider instance with search query applied
     *
     * @param array $params
     *
     * @return ActiveDataProvider
     */
    public function search($params)
    {
        $subQuery = ShootTag::find()->select(
            [
                new Expression('shoot_id, COUNT(shoot_id) as totalTags')
            ]
        )->groupBy('shoot_id');
        $query = ShootsSearch::find()->alias('s')
            ->select(
                [
                    's.*',
                    new Expression('if(st.totalTags is NOT NULL,st.totalTags,0) as totalTags')
                ]
            )->leftJoin(['st' => $subQuery], 'st.shoot_id=s.id');

        // add conditions that should always apply here

        $dataProvider = new ActiveDataProvider(
            [
                'query' => $query
            ]
        );

        $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([
            'id' => $this->id,
            'active' => $this->active
        ]);

        $query->andFilterWhere(['like', 'name', $this->name])
            ->andFilterWhere(['like', 'shoot_type', $this->shoot_type])
            ->andFilterWhere(['like', 'description', $this->description]);

        if ($this->totalTags == '0') {
            $query->andWhere(['IS', 'totalTags', new Expression('null')]);
        } else {
            $query->andFilterWhere(['=', 'totalTags', $this->totalTags]);
        }

        return $dataProvider;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...