You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel自定义查询构建器跨关联表查询返回空值问题

问题:跨关联表多字段搜索无法实现“任一匹配即返回”的需求

问题场景

我有purchases表和purchase_company_details表,想要实现只要任一表的对应字段匹配搜索值就返回记录,但当前当搜索值仅存在于其中一个字段时会返回空值。

示例

  • 搜索PO时:purchases表的po_no字段和purchase_company_details表的name字段中,name字段有匹配值,能正常返回数据;
  • 搜索PO-时:PO-仅存在于po_no字段,name字段无匹配,此时返回空值。

现有查询日志

[2023-02-21 14:20:09] local.DEBUG: array (
  0 => 
  array (
    'query' => 'select count(*) as aggregate from "purchases" where "po_no"::text like ? and exists (select * from "purchase_company_details" where "purchases"."id" = "purchase_company_details"."purchase_id" and "name"::text like ?)',
    'bindings' => 
    array (
      0 => '%PO-07%',
      1 => '%PO-07%',
    ),
    'time' => 4.63,
  ),
)

请求URL参数

api/v1/purchase?page=1&order_column=id&order_direction=desc&filter_match=or&f[1][column]=po_no&f[1][operator]=contains&f[1][query_1]=PO-&f[2][column]=seller_details.name&f[2][operator]=contains&f[2][query_1]=PO

自定义查询构建器代码

...
class CustomQueryBuilder
{

    protected $defaultFilterMatch = 'and';

    public function apply($query, $data)
    {
        return $query->when(isset($data['f']), function ($query) use ($data) {
            foreach ($data['f'] as $filter) {
                $this->applyFilter($filter, $query, $data);
            }
        });
    }

    protected function applyFilter($filter, $query, $data)
    {
        $filter['match'] = $data['filter_match'] ?: $this->defaultFilterMatch;
        if (strpos($filter['column'], '.') !== false) {
            // nested column
            list($relation, $relatedColumn) = explode('.', $filter['column']);
            $filter['column'] = $relatedColumn;
            $filter['match'] = 'and';

            $query->whereHas($relation, function ($q) use ($filter) {
                $this->{camel_case($filter['operator'])}($filter, $q);
            });
        } else {
            // normal column
            $this->{camel_case($filter['operator'])}($filter, $query);
        }
    }

...
// contains:q1
    protected function contains($filter, $query)
    {
        return $query->where($filter['column'], 'like', '%' . $filter['query_1'] . '%', $filter['match']);
    }

...

}

问题核心

单参数查询正常,但多字段查询无法实现“任一字段匹配即返回”的需求。当前生成的SQL用and关联主表条件和关联表的exists条件,导致必须两个条件同时满足才会返回结果。

解决方案

问题出在两处:一是处理关联字段时强制覆盖了全局的filter_match配置,二是多条件叠加时未按or逻辑处理关联表查询。修改步骤如下:

1. 重构apply方法,统一包裹过滤条件

将所有过滤条件放入闭包中,根据全局filter_match配置决定是用and还是or关联:

public function apply($query, $data)
{
    return $query->when(isset($data['f']), function ($query) use ($data) {
        $matchType = $data['filter_match'] ?: $this->defaultFilterMatch;
        
        // 用闭包包裹所有过滤条件,统一处理逻辑关联
        $query->where(function ($q) use ($data, $matchType) {
            foreach ($data['f'] as $filter) {
                $filter['match'] = $matchType;
                $this->applyFilter($filter, $q, $data);
            }
        });
    });
}

2. 修改applyFilter方法,取消强制设置match为and

关联字段的匹配逻辑遵循全局配置,根据match值选择whereHas或orWhereHas:

protected function applyFilter($filter, $query, $data)
{
    if (strpos($filter['column'], '.') !== false) {
        list($relation, $relatedColumn) = explode('.', $filter['column']);
        $filter['column'] = $relatedColumn;
        
        // 根据全局匹配类型选择对应的关联查询方法
        $method = $filter['match'] === 'or' ? 'orWhereHas' : 'whereHas';
        $query->$method($relation, function ($q) use ($filter) {
            $this->{camel_case($filter['operator'])}($filter, $q);
        });
    } else {
        $this->{camel_case($filter['operator'])}($filter, $query);
    }
}

3. 调整contains方法,正确处理逻辑类型

根据match值选择where或orWhere:

protected function contains($filter, $query)
{
    $method = $filter['match'] === 'or' ? 'orWhere' : 'where';
    return $query->$method($filter['column'], 'like', '%' . $filter['query_1'] . '%');
}

修改后生成的SQL会变成:

select count(*) as aggregate from "purchases" 
where (
    "po_no"::text like ? 
    or exists (select * from "purchase_company_details" where "purchases"."id" = "purchase_company_details"."purchase_id" and "name"::text like ?)
)

此时只要主表字段或关联表字段任一匹配,就会返回对应记录。


内容的提问来源于stack exchange,提问作者princeoo7

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 05:45:43