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
相关产品推荐
相关产品推荐

