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

Laravel多模型搜索问题:单表搜索正常,多表整合失败

实现Laravel多模型搜索的几种方法

嘿,我来帮你搞定多表搜索的问题!你已经有了单表的搜索逻辑,扩展到多模型其实很灵活,咱们分几种场景来聊:


方法一:用Union合并查询结果(适合结构相似的模型)

如果你的几个模型(比如News、Article、Product)结构类似,或者你想把搜索结果合并成一个统一的列表展示,用union是最直接的方式,它能把多个模型的查询结果合并成一个SQL查询,效率也比较高。

步骤1:给每个模型添加搜索Scope

首先确保每个需要搜索的模型都有自己的scopeSearch方法(或者用Trait统一提取,后面会说),比如你的News模型已经有了,那再给Article模型加一个:

// App/Models/Article.php
use Illuminate\Database\Eloquent\Builder;

public function scopeSearch(Builder $query, $search) {
    return $query->whereHas('translations', function ($q) use ($search) {
        $q->where('title', 'like', "%{$search}%")
          ->orWhere('content', 'like', "%{$search}%");
    });
}

步骤2:在控制器中合并查询

接下来在控制器里,把每个模型的搜索查询用union合并,注意要统一返回的字段(用别名对齐差异),还要加个type字段区分模型类型:

// App/Http/Controllers/NewsController.php(或者新建一个SearchController专门处理搜索)
use Illuminate\Support\Facades\DB;

public function search(Request $request)
{
    $query = $request->get('q');
    $searchQueries = [];

    if ($request->filled('q')) {
        // News的搜索查询,统一字段并标记类型
        $newsQuery = News::search($query)
            ->select(['id', 'title', 'created_at', DB::raw("'news' as type")]);
        $searchQueries[] = $newsQuery;

        // Article的搜索查询
        $articleQuery = Article::search($query)
            ->select(['id', 'title', 'created_at', DB::raw("'article' as type")]);
        $searchQueries[] = $articleQuery;

        // 如果是Product模型,字段名不一样就用别名
        $productQuery = Product::search($query)
            ->select(['id', 'name as title', 'created_at', DB::raw("'product' as type")]);
        $searchQueries[] = $productQuery;
    } else {
        // 没有搜索词时,默认取各模型的最新数据(可根据需求调整)
        $newsQuery = News::query()
            ->select(['id', 'title', 'created_at', DB::raw("'news' as type")])
            ->orderBy('created_at', 'desc');
        $searchQueries[] = $newsQuery;

        $articleQuery = Article::query()
            ->select(['id', 'title', 'created_at', DB::raw("'article' as type")])
            ->orderBy('created_at', 'desc');
        $searchQueries[] = $articleQuery;
    }

    // 合并所有查询
    $combinedQuery = array_shift($searchQueries);
    foreach ($searchQueries as $q) {
        $combinedQuery = $combinedQuery->union($q);
    }

    // 排序并分页
    $results = $combinedQuery->orderBy('created_at', 'desc')->paginate(10);

    return view('search.results', compact('results'));
}

方法二:合并集合(适合结构差异大的模型)

如果你的模型结构差异很大,没法用Union统一字段,那可以分别搜索每个模型,再把结果集合合并,最后手动处理分页。

控制器代码示例

public function search(Request $request)
{
    $query = $request->get('q');
    $results = collect();

    if ($request->filled('q')) {
        // 搜索News并标记类型
        $news = News::search($query)
            ->orderBy('created_at', 'desc')
            ->get()
            ->each(fn($item) => $item->type = 'news');
        $results = $results->merge($news);

        // 搜索Article
        $articles = Article::search($query)
            ->orderBy('created_at', 'desc')
            ->get()
            ->each(fn($item) => $item->type = 'article');
        $results = $results->merge($articles);

        // 搜索Product
        $products = Product::search($query)
            ->orderBy('created_at', 'desc')
            ->get()
            ->each(fn($item) => $item->type = 'product');
        $results = $results->merge($products);
    } else {
        // 默认取各模型最新几条数据
        $news = News::orderBy('created_at', 'desc')->take(5)->get()->each(fn($item) => $item->type = 'news');
        $results = $results->merge($news);

        $articles = Article::orderBy('created_at', 'desc')->take(5)->get()->each(fn($item) => $item->type = 'article');
        $results = $results->merge($articles);
    }

    // 对合并后的集合排序
    $sortedResults = $results->sortByDesc('created_at')->values();

    // 手动处理分页
    $perPage = 10;
    $currentPage = $request->page ?? 1;
    $paginatedResults = new \Illuminate\Pagination\LengthAwarePaginator(
        $sortedResults->slice(($currentPage - 1) * $perPage, $perPage)->values(),
        $sortedResults->count(),
        $perPage,
        $currentPage,
        ['path' => $request->url(), 'query' => $request->query()]
    );

    return view('search.results', compact('paginatedResults'));
}

优化:用Trait统一搜索逻辑

如果多个模型的搜索逻辑重复,别重复写代码!可以把scopeSearch提取成一个Trait,让模型们共享:

// App/Traits/Searchable.php
use Illuminate\Database\Eloquent\Builder;

trait Searchable
{
    public function scopeSearch(Builder $query, $search)
    {
        // 优先检查是否有translations关联(像你的News模型那样)
        if (method_exists($this, 'translations')) {
            return $query->whereHas('translations', function ($q) use ($search) {
                $q->where('title', 'like', "%{$search}%")
                  ->orWhere('content', 'like', "%{$search}%");
            });
        }
        // 没有translations关联的模型,直接查自身字段
        return $query->where('name', 'like', "%{$search}%")
                    ->orWhere('description', 'like', "%{$search}%");
    }
}

然后在模型里直接use这个Trait就行:

// App/Models/News.php
use App\Traits\Searchable;

class News extends Model
{
    use Searchable;

    // ... 其他代码(比如translations关联)
}

进阶:用全文索引提升性能

如果你的数据量很大,用like做模糊搜索会很慢,建议给搜索字段加MySQL全文索引,或者用Laravel Scout配合Meilisearch/Algolia这类搜索引擎,不仅搜索更快,多模型搜索也会更灵活。

比如给News的translations表的title和content字段加全文索引:

ALTER TABLE news_translations ADD FULLTEXT INDEX ft_title_content (title, content);

然后修改scopeSearch用全文搜索:

public function scopeSearch(Builder $query, $search) {
    return $query->whereHas('translations', function ($q) use ($search) {
        $q->whereRaw("MATCH(title, content) AGAINST(? IN BOOLEAN MODE)", [$search]);
    });
}

内容的提问来源于stack exchange,提问作者Ali Özen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 03:42:15