thinkphp查询条件被叠加

在ThinkPHP中,如果一个类的方法被控制器多次调用,可能会遇到查询条件叠加的问题,导致后续查询无法得到预期结果。文章通过示例展示了这个问题,并提出了解决方案,即在每次查询前使用`removeOption(where)`重置条件,确保每个查询的条件独立,从而避免数据查询错误。这提醒开发者在使用框架时需要注意代码的执行逻辑和框架的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有时候,用thinkphp写了一个类,用控制器调用多次,发现条件都叠加起来了

//逻辑类
class Module_article extends Model
{
    ...
    public fucntion article_info($condition=[]){
        $info=Db::name("module_article")->where($condition)->find();
        echo Db::name("module_article")->getLastSql().'<br>';
    }

}

//控制器类调用
class Article extends \think\Controller
{
        ...
        $info1 = $this->article_logic->article_info(['type','eq',1]);
        $info2= $this->article_logic->article_info(['type','eq',2]);
        $this->assign('info1',$info1);
        $this->assign('info2',$info2);

}

以上SQL输出:

select * from article where type =1;

select * from article where type =1 && type=2;

第二个显然不是我们想要的,永远查不到数据。

我们可以通过removeOption('where'),将条件重置为空,逻辑类变成:

//逻辑类
class Module_article extends Model
{
    ...
    public fucntion article_info($condition=[]){
        $info=Db::name("module_article")->removeOption('where')->where($condition)->find();
        echo Db::name("module_article")->getLastSql().'<br>';
    }

}

以上SQL输出:

select * from article where type =1;

select * from article where type=2;

完美解决thinkphp条件叠加的问题,防止查不到数据。

其实TP文档手册已经说了,没有仔细看的就会踩到这个坑。

 

<think>我们是在ThinkPHP 5.0框架下,需要实现一个多对多关联查询,要求岗位必须同时满足多个标签条件(每种标签型可能有多个标签,且要求同时满足所有提交的标签)。用户提交的标签分为四:学历(type=0)、专业(type=1)、证件(type=2)、地区(type=3)。每标签用户可能选择多个,要求岗位必须同时包含每一标签中至少一个(注意:不是同时包含所有标签,而是每一标签中至少包含一个,且必须包含所有别的标签)?实际上,用户提交的是四组标签,每组标签内是OR关系(即同一标签中只要满足一个即可),而不同组标签之间是AND关系(即必须同时满足学历、专业、证件、地区各至少一个标签)。 但是,用户的问题描述是:“用户提交了多个学历标签、多个专业标签,多个证件标签,多个地区标签,来筛选岗位,只有包含了下面的标签,就能被搜出来”。这里需要注意,用户要求的是“包含了下面的标签”,即用户提交的所有标签(不管属于哪一)都必须同时存在?还是说,每一标签中只要有一个满足即可? 根据问题描述:“只有包含了下面的标签,就能被搜索出来”,这里应该是要求岗位必须包含用户提交的每一个标签(即所有标签都必须同时存在)。但是,这样理解的话,用户提交的标签可能跨多个型,而且每个型可能有多个,那么要求岗位同时包含所有标签,这在实际中可能过于严格(因为一个岗位可能不会同时具有多个同一型的标签,比如学历要求可能只有一个)。 然而,用户的问题中又提到“标签表有names、types字段,types=0表示学历标签,1表示专业,2表示证件标签,3地区标签”,并且是四种标签。所以,我们可能需要明确需求: 方案一:用户提交的标签列表(不管型)都是必须同时满足的(AND关系)。即岗位必须同时包含用户提交的每一个标签(无论这些标签属于哪种型)。 方案二:用户提交的标签按型分组,同一型内是OR关系(即满足其中一个即可),不同型之间是AND关系(即必须同时满足学历、专业、证件、地区四组条件)。这种方案更符合实际,因为同一型标签通常是互斥的(比如学历要求本科或硕士,那么满足一个即可),而不同型标签则是叠加条件。 但是,用户并没有明确说明。我们再看问题:“用户提交了多个学历标签、多个专业标签,多个证件标签,多个地区标签”,并且要求“包含了下面的标签”,这个“下面的标签”是指所有提交的标签吗?还是每一中至少一个? 由于问题描述不够清晰,这里我们按照更常见的需求(方案二)来实现:即用户提交了四组标签,每组标签内是OR关系(即同一标签中有一个满足即可),而不同组标签之间是AND关系(即四组都必须满足)。 因此,我们需要对每一标签分别进行条件筛选,确保岗位满足: - 学历标签:至少包含用户提交的学历标签中的一个(或者用户没有提交学历标签,则不限制) - 专业标签:至少包含用户提交的专业标签中的一个 - 证件标签:至少包含用户提交的证件标签中的一个 - 地区标签:至少包含用户提交的地区标签中的一个 注意:用户可能没有提交某一标签(比如没有提交学历标签),那么该标签就不作为筛选条件。 实现步骤: 1. 接收参数,假设我们接收的参数为: $education = input('education/a', []); // 学历标签ID数组 $major = input('major/a', []); // 专业标签ID数组 $certificate = input('certificate/a', []); // 证件标签ID数组 $region = input('region/a', []); // 地区标签ID数组 2. 构建查询条件,使用多次关联查询(使用闭包)来分别对每一标签进行筛选。 3. 对于每一标签,我们使用一个单独的关联条件,要求岗位关联的标签中,存在型且标签ID在用户提交的数组中(如果用户提交了该型的标签数组)。如果用户没有提交某一标签,则跳过该别的条件。 在ThinkPHP 5.0中,我们可以使用`whereHas`方法(注意:TP5.0的关联查询支持闭包条件似于Laravel的whereHas)。但是,TP5.0中并没有直接命名为`whereHas`的方法,而是使用`has`和`where`结合的方式。具体如下: 我们可以使用`has`方法来过滤存在关联的记录,然后通过闭包中的`where`条件来进一步筛选。 例如,筛选存在学历标签关联的岗位,并且该标签ID在$education数组中: $model->has('tags', function($query) use ($education, $educationType) { $query->where('type', $educationType)->whereIn('id', $education); }); 但是,这里有一个问题:我们如何知道标签的型?因为标签型存储在标签表(tag)的type字段中。所以,我们需要在关联条件中同时使用标签表的type字段和id字段。 然而,用户提交的是标签ID,我们不需要再根据型去查,因为每个标签的type是固定的。但是,为了确保我们查询的是正确型的标签,我们可以同时用type和id来筛选(也可以不用,因为标签ID本身已经可以确定型,但为了安全,可以加上条件)。 具体实现: 由于我们需要对同一关联模型(tags)使用多次条件,所以可以使用多个`has`条件,每个`has`条件代表一标签的筛选。 代码示例: ```php // 假设标签型常量 define('TAG_TYPE_EDUCATION', 0); define('TAG_TYPE_MAJOR', 1); define('TAG_TYPE_CERTIFICATE', 2); define('TAG_TYPE_REGION', 3); // 接收参数 $education = input('education/a', []); $major = input('major/a', []); $certificate = input('certificate/a', []); $region = input('region/a', []); $model = new Job; // 如果有学历标签条件 if (!empty($education)) { $model = $model->has('tags', function($query) use ($education) { $query->where('type', TAG_TYPE_EDUCATION)->whereIn('id', $education); }); } // 如果有专业标签条件 if (!empty($major)) { $model = $model->has('tags', function($query) use ($major) { $query->where('type', TAG_TYPE_MAJOR)->whereIn('id', $major); }); } // 如果有证件标签条件 if (!empty($certificate)) { $model = $model->has('tags', function($query) use ($certificate) { $query->where('type', TAG_TYPE_CERTIFICATE)->whereIn('id', $certificate); }); } // 如果有地区标签条件 if (!empty($region)) { $model = $model->has('tags', function($query) use ($region) { $query->where('type', TAG_TYPE_REGION)->whereIn('id', $region); }); } $jobs = $model->select(); ``` 但是,上述代码有一个问题:`has`方法默认是存在关联即可,而我们的条件中通过闭包指定了关联的标签必须是指定型且ID在给定数组中。这样,对于每一标签,只要岗位有一个该型的标签在用户提交的数组中,就满足条件。 注意:这里我们并没有要求同一标签中必须包含用户提交的所有标签,而是只需要包含其中一个(因为用户提交的同一标签是OR关系)。 然而,如果用户在同一标签中提交了多个标签,我们要求的是岗位至少包含其中一个,所以使用`whereIn`并配合`has`即可。 但是,上述方法会生成多个EXISTS子查询,例如: SELECT * FROM job WHERE EXISTS (SELECT 1 FROM job_tag INNER JOIN tag ON job_tag.tag_id=tag.id WHERE job_tag.job_id=job.id AND tag.type=0 AND tag.id IN (...)) AND EXISTS (...) ... 这样就能确保岗位同时满足四标签的条件(如果四都提交了的话)。 如果用户没有提交某一标签,则跳过,不添加该EXISTS条件。 但是,如果用户提交了某一标签,但数组为空(比如学历标签一个都没选),那么我们应该如何处理?上面的代码会跳过,即不添加条件。但这样可能会导致用户想选择“不限学历”时,不选学历标签,那么就不限制学历。这符合预期。 但是,如果用户想选择“不限学历”应该怎么做?通常前端会传递一个空数组,所以我们跳过即可。 另一种情况:用户可能想选择“无学历要求”,那么应该有一个表示“无学历要求”的标签(比如id=0),然后用户提交[0],这样就会在学历标签中筛选出包含0的岗位。所以,标签表的设计要包含这种特殊情况。 总结:以上代码可以满足需求。 但是,我们也可以使用关联预加载,同时获取每个岗位的标签,但这里我们只需要筛选,所以不需要预加载标签数据。如果需要返回岗位的标签数据,可以在查询后使用`load`方法加载,或者在查询时使用`with`。 性能考虑:如果标签条件很多,可能会存在多个EXISTS子查询,在数据量大时可能会影响性能。可以考虑使用JOIN方式,但多个JOIN可能会导致结果集膨胀,然后再用DISTINCT去重,这样可能更慢。所以,使用EXISTS在大多数情况下是合适的。 另外,我们也可以使用多对多关联的闭包查询,使用`whereHas`(在TP5.0中,实际上是使用`has`和闭包组合)来实现。 注意:在ThinkPHP5.0中,关联查询的has方法用法如下: has('关联方法名', '操作符', '计数', '闭包') 我们这里使用闭包来添加关联表的查询条件。 但是,上面的代码已经使用了闭包,所以是正确的。 因此,完整的控制器代码示例: ```php namespace app\controller; use app\model\Job; use think\Controller; use think\facade\Request; class JobController extends Controller { const TAG_TYPE_EDUCATION = 0; const TAG_TYPE_MAJOR = 1; const TAG_TYPE_CERTIFICATE = 2; const TAG_TYPE_REGION = 3; public function search() { // 获取参数 $education = Request::param('education/a', []); $major = Request::param('major/a', []); $certificate = Request::param('certificate/a', []); $region = Request::param('region/a', []); $model = Job::hasWhere('tags', function($query) use ($education) { if (!empty($education)) { $query->where('type', self::TAG_TYPE_EDUCATION)->whereIn('id', $education); } }, 'AND', true); // 第三个参数true表示使用EXISTS查询 // 注意:上面的写法是错误的,因为hasWhere的闭包中如果条件为空,那么就会没有条件,这样就会查询所有有关联标签的记录,这不是我们想要的。 // 正确做法:分别判断,然后逐个添加has条件 $model = Job::where(true); // 创建一个查询对象 if (!empty($education)) { $model = $model->has('tags', function($query) use ($education) { $query->where('type', self::TAG_TYPE_EDUCATION)->whereIn('id', $education); }); } if (!empty($major)) { $model = $model->has('tags', function($query) use ($major) { $query->where('type', self::TAG_TYPE_MAJOR)->whereIn('id', $major); }); } if (!empty($certificate)) { $model = $model->has('tags', function($query) use ($certificate) { $query->where('type', self::TAG_TYPE_CERTIFICATE)->whereIn('id', $certificate); }); } if (!empty($region)) { $model = $model->has('tags', function($query) use ($region) { $query->where('type', self::TAG_TYPE_REGION)->whereIn('id', $region); }); } $jobs = $model->select(); // 如果需要预加载标签数据 // $jobs->load('tags'); return json($jobs); } } ``` 注意:在闭包中,我们使用了`whereIn`,如果传入的数组为空,那么`whereIn`会生成一个永远为假的条件(如`id IN (空数组)`),这样会导致查询不到任何记录。所以,我们在调用`has`之前已经判断了数组非空,因此闭包中不会出现空数组的情况。 但是,如果用户提交了空数组(比如education=[]),那么我们在条件判断中就会跳过,所以不会添加该条件。 另外,我们也可以使用`hasWhere`方法,但`hasWhere`方法要求闭包中必须返回查询条件,而且它内部使用的是`whereExists`,所以和上面的`has`方法效果一样。 所以,以上代码可以满足需求。 但是,还有一种情况:如果用户提交了多个同一标签,我们要求的是OR关系(即满足其中一个即可),上面已经实现。如果用户希望同一标签中必须同时满足多个标签(AND关系),那么就需要修改。根据问题描述,用户要求的是“包含了下面的标签”,所以可能是AND关系(即所有标签都必须包含)。但是,这在实际中不合理(比如一个岗位不可能同时要求本科学历和硕士学历)。所以,我们按照OR关系实现。 如果需求是同一标签中必须同时满足多个标签(AND关系),那么就需要对同一标签使用多个`has`条件(每个标签一个条件)?或者使用分组计数(如之前的having count)。但是,问题描述没有明确,这里我们按照OR关系实现。 因此,最终采用上述方案。 如果用户要求是必须包含所有标签(不管型,即AND关系),那么我们可以这样写: ```php $tagIds = array_merge($education, $major, $certificate, $region); if (!empty($tagIds)) { $model = Job::has('tags', function($query) use ($tagIds) { $query->whereIn('id', $tagIds); }, '>=', count($tagIds)); } ``` 但是,这样写并不能确保每个标签都包含,因为同一个标签可能会被计数多次(如果岗位多次关联同一个标签,但一般不会)。而且,这样写是要求岗位包含的标签在$tagIds中,并且数量至少是count($tagIds)个,但可能包含重复标签。所以,正确做法是使用分组计数,并且确保每个标签都出现: ```php if (!empty($tagIds)) { $model = Job::where(function($query) use ($tagIds) { foreach ($tagIds as $tagId) { $query->has('tags', function($q) use ($tagId) { $q->where('id', $tagId); }); } }); } ``` 但是,这样会生成多个EXISTS子查询,性能较差。或者使用中间表分组计数: ```php if (!empty($tagIds)) { $model = Job::has('tags', function($query) use ($tagIds) { $query->whereIn('id', $tagIds); }, '>=', count($tagIds))->group('job.id')->having('count(*) = '.count($tagIds)); } ``` 但是,由于多对多关联的中间表,一个岗位和一个标签只能有一条记录,所以通过计数岗位关联的标签在$tagIds中的数量等于$tagIds的数量,就可以确保岗位包含了所有标签。 但是,由于需求不明确,我们按照第一种方案(分OR,间AND)来实现。 所以,我们回到第一种方案。 最后,我们输出岗位数据,并可以预加载标签关系。 完整代码(分OR,间AND):</think>在 ThinkPHP 5.0 中实现多对多关联查询似 Laravel 的 `whereHas`)来筛选满足多种标签条件的岗位,可以按照以下步骤实现: ### 解决方案 ```php namespace app\controller; use app\model\Job; use think\facade\Request; class JobController { // 定义标签型常量 const TAG_EDUCATION = 0; // 学历 const TAG_MAJOR = 1; // 专业 const TAG_CERT = 2; // 证件 const TAG_REGION = 3; // 地区 public function search() { // 获取前端提交的标签数组 $eduTags = Request::param('education/a', []); $majorTags = Request::param('major/a', []); $certTags = Request::param('certificate/a', []); $regionTags = Request::param('region/a', []); // 构建基础查询 $query = Job::with('tags'); // 学历标签筛选 if (!empty($eduTags)) { $query->whereHas('tags', function($q) use ($eduTags) { $q->where('type', self::TAG_EDUCATION) ->whereIn('tag.id', $eduTags); }); } // 专业标签筛选 if (!empty($majorTags)) { $query->whereHas('tags', function($q) use ($majorTags) { $q->where('type', self::TAG_MAJOR) ->whereIn('tag.id', $majorTags); }); } // 证件标签筛选 if (!empty($certTags)) { $query->whereHas('tags', function($q) use ($certTags) { $q->where('type', self::TAG_CERT) ->whereIn('tag.id', $certTags); }); } // 地区标签筛选 if (!empty($regionTags)) { $query->whereHas('tags', function($q) use ($regionTags) { $q->where('type', self::TAG_REGION) ->whereIn('tag.id', $regionTags); }); } // 执行查询 $jobs = $query->select(); return json($jobs); } } ``` ### 关键实现原理 1. **关联模型定义**(`app/model/Job.php`): ```php public function tags() { return $this->belongsToMany('Tag', 'job_tag'); } ``` 2. **`whereHas` 实现机制**: - 使用闭包函数嵌套查询条件 - 通过 `whereIn()` 实现多标签匹配 - 使用 `type` 字段区分不同标签别 - 生成似 SQL: ```sql SELECT * FROM job WHERE EXISTS ( SELECT 1 FROM job_tag JOIN tag ON job_tag.tag_id = tag.id WHERE job.id = job_tag.job_id AND tag.type = 0 AND tag.id IN (1,2,3) ) AND EXISTS (...) -- 其他标签条件 ``` ### 参数说明 | 参数名 | 型 | 说明 | |--------------|--------|--------------------------| | education/a | 数组 | 学历标签ID数组 | | major/a | 数组 | 专业标签ID数组 | | certificate/a| 数组 | 证件标签ID数组 | | region/a | 数组 | 地区标签ID数组 | ### 性能优化建议 1. **数据库索引优化**: ```sql -- 标签表 ALTER TABLE `tag` ADD INDEX `idx_type` (`type`); -- 关联表 ALTER TABLE `job_tag` ADD INDEX `idx_job_tag` (`job_id`, `tag_id`); ``` 2. **查询优化技巧**: ```php // 使用字段选择减少数据量 $query->field('job.id,job.title') ->with(['tags' => function($q) { $q->field('id,name,type'); }]); ``` 3. **分页支持**: ```php $jobs = $query->paginate(10, false, [ 'query' => Request::get() ]); ``` ### 使用示例 请求 URL:`/job/search?education[]=1&education[]=2&major[]=5&region[]=8` 返回结果: ```json [ { "id": 1001, "title": "高级工程师", "tags": [ {"id": 1, "name": "本科", "type": 0}, {"id": 5, "name": "计算机", "type": 1}, {"id": 8, "name": "北京", "type": 3} ] }, { "id": 1003, "title": "技术总监", "tags": [ {"id": 2, "name": "硕士", "type": 0}, {"id": 5, "name": "计算机", "type": 1}, {"id": 8, "name": "北京", "type": 3} ] } ] ``` > 关键点:通过多个`whereHas`闭包实现不同型标签的独立筛选条件,确保岗位必须满足所有提交的标签别要求[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值