Yii2 GridView SearchModel 用于计算关系

Yii2 GridView SearchModel for count of relation

我有一个模型 itemimage 模型有 hasMany 关系。在项目 GridView 中,我有一列显示 item 有多少张图片。我希望能够按此列排序,并且可能能够使用复选框进行过滤以仅显示 items 而没有 images.

我尝试向搜索模型添加一个检查以查询 with images 关系,我想我应该为 image 的位置添加一个 andFilterWhere计数小于输入值。但是我不确定拥有该逻辑的最佳方法,是否有更好的 Yii 方法来过滤该计数?

更新 根据 的信息,我已经更新了我的 item 模型,以使用一种方法 returns 关系计数。

然后我像这样更新我的搜索模型:

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

不再显示具有 0 个 imageitems 的问题(并且选中该框不显示任何内容),因为加入将在 image 中找不到该 ID 的任何内容table

您应该检查 WHERE imageCount.image_count IS NULL 而不是 WHERE imageCount.image_count=0,因为那些没有任何相关图像的行将在 image_count 下显示为 null 并更改来自

的条件
$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 及其相关标签在结点 table 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 |
+----------+--------+

现在考虑上面的 tables 和数据,我有名称为 ShootsSearch 的搜索模型,我用它来显示所有拍摄,我想显示每个拍摄的标签数ShootTag 模型中保存的照片。

我没有为 GridView 添加代码,因为它不相关,我的搜索模型 ShootsSearch 具有以下搜索方法,该方法可以正确处理 shottags 模型中的任何射击计数。

  • 与您的代码不同的是,我在第一个查询中使用 ShootsSearch 模型,而不是您使用 Item::find(); 时的 Shoots 模型这应该是 ItemSearch::find(); 而不是因为您正在使用的别名 image_count 在搜索模型中声明,

  • 然后主查询中的 new Expression('if(st.totalTags is NOT NULL,st.totalTags,0) as totalTags') 行需要将空值显示为 0,以便您可以在此处使用条件 select。

  • 然后您需要检查 if ($this->totalTags == '0') { 以应用 $query->andWhere(['IS', 'totalTags', new Expression('null')]);,因为对于没有标签的 totalTags,实际值将是 null可用于任何 Shoot,在其他部分,您将使用 query->andFilterWhere(['=', 'totalTags', $this->totalTags]);

这在您想要的所有三种情况下都能正常工作,请参见下图

第一次默认查看

搜索总标签数为 4 的拍摄

搜索 TotalTags 计数为 0 的拍摄

您应该在代码中替换以下内容

  • public $totalTagspublic $image_count.
  • ShootTagsImage.
  • shoot_iditem_id.
  • ShootsSearchItemSearch/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;
    }
}

我的理解是需要统计一个关联的数据量table,并根据统计结果进行排序过滤

那么你的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经常需要计算和统计,而且你的数据量很大,那么建议你重新定义这个字段到table项中。

更新:

我的错,聚合字段不能通过where条件过滤,但是你可以使用having:

$query->andFilterHaving([
    'image_count' => $this->image_count,
]);