Yii2 GridView SearchModel 用于计算关系
Yii2 GridView SearchModel for count of relation
我有一个模型 item
与 image
模型有 hasMany
关系。在项目 GridView
中,我有一列显示 item
有多少张图片。我希望能够按此列排序,并且可能能够使用复选框进行过滤以仅显示 item
s 而没有 image
s.
我尝试向搜索模型添加一个检查以查询 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 个 image
的 items
的问题(并且选中该框不显示任何内容),因为加入将在 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 $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;
}
}
我的理解是需要统计一个关联的数据量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,
]);
我有一个模型 item
与 image
模型有 hasMany
关系。在项目 GridView
中,我有一列显示 item
有多少张图片。我希望能够按此列排序,并且可能能够使用复选框进行过滤以仅显示 item
s 而没有 image
s.
我尝试向搜索模型添加一个检查以查询 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 个 image
的 items
的问题(并且选中该框不显示任何内容),因为加入将在 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 $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;
}
}
我的理解是需要统计一个关联的数据量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,
]);