讲师中心 微信公众号
AI工具推荐 视频效率加速

Laravel 中父子表联合查询与过滤:实现多条件搜索教程

千强君_2224

千强君_2224

发布时间:2025-11-30 13:14:02

|

799人浏览过

|

来源于php中文网

原创

Laravel 中父子表联合查询与过滤:实现多条件搜索教程

本教程详细介绍了如何在 laravel 应用中,针对具有父子关系的数据库表(如 `posts` 和 `posts_tags`),同时进行多条件查询和过滤。我们将探讨如何利用 eloquent orm 的 `join` 方法实现跨表数据筛选,以及作为替代方案的 `wherehas` 方法,帮助开发者高效地构建复杂的搜索功能,提升数据检索的灵活性和准确性。

在构建复杂的 Web 应用程序时,经常需要根据来自多个相关表的数据进行过滤和搜索。例如,在一个内容管理系统中,您可能需要根据文章的发布年份(存储在父表 posts 中)和文章关联的标签(存储在子表 posts_tags 中)来搜索文章。本教程将指导您如何在 Laravel 中高效地实现这种父子表联合查询与过滤。

1. 模型关系概述

首先,我们回顾一下示例中 Post 和 PostTag 模型之间的关系。

Post.php (父模型)

<?php
namespace App\Models\Posts;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    protected $table="posts";
    protected $guarded = [];

    // 定义与 PostTag 的一对多关系
    public function PostTags() {
        return $this->hasMany('App\Models\Posts\PostTag');
    }
}

PostTag.php (子模型)

<?php
namespace App\Models\Posts;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class PostTag extends Model
{
    use HasFactory;

    protected $table="posts_tags";
    protected $guarded = [];

    // 定义与 Post 的反向一对多关系
    public function GetPost() {
        return $this->belongsTo('App\Models\Posts\Post');
    }
}

这里,Post 模型通过 hasMany 方法定义了它拥有多个 PostTag,而 PostTag 模型通过 belongsTo 方法定义了它属于一个 Post。这是 Laravel 中标准的一对多关系定义。

2. 搜索表单与路由

用户通过一个包含年份和标签选择框的表单提交搜索请求。

搜索表单 (View)

<form action="{{ route('movies.movie_search') }}" method="GET">
    @csrf
    <select name="tag" class="select">
        <option value="" selected="selected">@lang('movies.movie_all')</option>
        @if ($tags)
            @foreach ($tags AS $tag)
                <option value="{{ $tag->id }}">{{ $tag->name }}</option>
            @endforeach
        @endif
    </select>
    <select name="year" class="select">
        <option value="" selected="selected">@lang('public.public_year')</option>
        @php
            $date = date('Y');
            for ($i = $date; $i > 1970; $i--) {
                echo "<option value=".$i.">".$i."</option>";
            }
        @endphp
    </select>
    <button type="submit" name="" class="filter">@lang('public.public_filter')</button>
</form>

路由定义 (web.php)

Route::prefix('/movies')->group(function (){
    Route::get('/search', [MovieController::class, 'MoviesDataSearch'])->name('movies.movie_search');
});

表单通过 GET 请求将 tag 和 year 参数发送到 movies.movie_search 路由,由 MovieController 的 MoviesDataSearch 方法处理。

Laravel Cloud
Laravel Cloud

Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。

下载

3. 控制器中的查询实现

在控制器中,我们需要根据 Request 对象中传入的 year 和 tag 参数来构建查询。

3.1 使用 join 方法实现联合查询

当需要根据关联表中的字段进行过滤,并且可能需要从关联表中选择数据时,使用数据库 JOIN 操作是最直接有效的方式。

MovieController.php

<?php

namespace App\Http\Controllers\Movies;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Posts\Post; // 引入 Post 模型
use App\Models\Posts\PostTag; // 引入 PostTag 模型 (虽然在此方案中不直接使用,但仍需引入)

class MovieController extends Controller
{
    public function MoviesDataSearch(Request $request) {
        // 确保 $request 不为空,或根据业务逻辑处理无搜索条件的情况
        // 这里的 empty($request) 判断通常不准确,应判断具体的搜索参数是否存在
        if (empty($request->year) && empty($request->tag)) {
            // 如果没有搜索条件,返回所有电影
            return Post::where('post_type', "movie")
                       ->where('is_delete', "0")
                       ->orderBy('id', 'DESC')
                       ->Paginate(12);
        }

        $query = Post::query(); // 初始化查询构建器

        // 筛选基本条件
        $query->where('posts.post_type', "movie")
              ->where('posts.is_delete', "0");

        // 如果存在年份条件,则添加年份过滤
        if ($request->filled('year')) {
            // 注意:如果 year 是精确匹配,应使用 '=' 而非 'like'
            // 如果需要模糊匹配,使用 'like'
            $query->where('posts.year', $request->year); // 假设是精确匹配
            // 如果是模糊匹配:$query->where('posts.year', 'like', '%'.$request->year.'%');
        }

        // 如果存在标签条件,则进行 JOIN 操作并添加标签过滤
        if ($request->filled('tag')) {
            $query->join('posts_tags', 'posts.id', '=', 'posts_tags.post_id');
            // 注意:如果 tag_id 是精确匹配,应使用 '=' 而非 'like'
            // 如果需要模糊匹配,使用 'like'
            $query->where('posts_tags.tag_id', $request->tag); // 假设是精确匹配
            // 如果是模糊匹配:$query->where('posts_tags.tag_id', 'like', '%'.$request->tag.'%');
        }

        // 为了避免在 JOIN 操作后出现重复的 Post 记录(如果一个 Post 有多个匹配的 PostTag),
        // 并且只关心 Post 本身的数据,可以使用 distinct() 或 groupBy()
        // 但如果只需要 Post 的数据,并且 PostTag 的过滤是排他性的,则可能不需要。
        // 这里我们假设每个 Post 只需要被返回一次,且只关心 Post 自身的数据。
        // 如果需要 PostTag 的数据,则需要指定 select 字段。
        $query->select('posts.*'); // 明确选择 Post 表的所有字段,避免字段名冲突

        $movies = $query->orderBy('posts.id', 'DESC')->Paginate(2);

        // 将所有请求参数附加到分页链接,以便在切换页面时保留搜索条件
        return $movies->appends($request->all());
    }
}

代码解析:

  1. Post::query(): 初始化一个 Eloquent 查询构建器,以便我们可以逐步添加条件。
  2. 基本条件: where('posts.post_type', "movie") 和 where('posts.is_delete', "0") 过滤了 posts 表的基本属性。注意,为了避免与 posts_tags 表中可能存在的同名字段冲突,我们明确使用了 posts.post_type 等带表名的字段。
  3. 年份过滤: if ($request->filled('year')) { ... } 检查 year 参数是否存在且非空,然后添加 where('posts.year', $request->year) 条件。这里假设 year 是精确匹配,如果您需要模糊匹配,可以使用 like 操作符。
  4. 标签过滤: if ($request->filled('tag')) { ... } 检查 tag 参数是否存在且非空。
    • $query->join('posts_tags', 'posts.id', '=', 'posts_tags.post_id');: 这是核心部分。它将 posts 表与 posts_tags 表通过 posts.id 和 posts_tags.post_id 字段进行内连接(INNER JOIN)。这意味着只有在两个表中都有匹配记录的行才会被返回。
    • $query->where('posts_tags.tag_id', $request->tag);: 在连接后,我们就可以直接在 posts_tags 表的 tag_id 字段上添加过滤条件。同样,这里假设 tag_id 是精确匹配。
  5. *`$query->select('posts.');**: 在执行join操作后,如果两个表中有同名字段(例如id),默认情况下可能会导致冲突或覆盖。通过明确指定select('posts.*'),我们确保只选择posts表的所有字段,避免了不必要的字段冲突,并确保返回的是Post` 模型实例。
  6. orderBy('posts.id', 'DESC'): 排序时同样明确指定表名。
  7. Paginate(2): 对结果进行分页。
  8. $movies->appends($request->all());: 将当前请求的所有参数附加到分页链接中,确保用户在点击分页链接时,原有的搜索条件仍然有效。

3.2 使用 whereHas 方法实现关联表过滤

whereHas 是 Eloquent 提供的一种更“Eloquent”的方式来过滤基于关联模型的记录。它在内部通常会生成一个子查询,用于检查是否存在满足特定条件的关联记录。

<?php

namespace App\Http\Controllers\Movies;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Posts\Post;

class MovieController extends Controller
{
    public function MoviesDataSearch(Request $request) {
        if (empty($request->year) && empty($request->tag)) {
            return Post::where('post_type', "movie")
                       ->where('is_delete', "0")
                       ->orderBy('id', 'DESC')
                       ->Paginate(12);
        }

        $query = Post::where('post_type', "movie")
                     ->where('is_delete', "0");

        // 如果存在年份条件
        if ($request->filled('year')) {
            $query->where('year', $request->year);
        }

        // 如果存在标签条件,使用 whereHas
        if ($request->filled('tag')) {
            $query->whereHas('PostTags', function ($q) use ($request) {
                $q->where('tag_id', $request->tag);
            });
        }

        $movies = $query->orderBy('id', 'DESC')->Paginate(2);

        return $movies->appends($request->all());
    }
}

代码解析:

  1. whereHas('PostTags', function ($q) use ($request) { ... });:
    • PostTags 是 Post 模型中定义的关系方法的名称。
    • 回调函数 $q 是一个查询构建器实例,它作用于关联模型 PostTag。
    • 在回调函数内部,我们添加了针对 PostTag 模型(即 posts_tags 表)的过滤条件 where('tag_id', $request->tag)。
  2. whereHas 的优点在于它更加语义化,更符合 Eloquent 的风格,并且不需要手动处理 JOIN 语句和字段冲突。它主要用于检查是否存在满足条件的关联记录,而不是直接选择关联表的字段。

4. join 与 whereHas 的选择

  • join:
    • 优点: 通常在数据库层面更高效,尤其是在需要从关联表中选择字段、进行聚合或处理更复杂的连接条件时。对于大型数据集,直接的 JOIN 性能可能更优。
    • 缺点: 需要手动管理连接条件和字段名冲突(通过 select 明确指定字段),代码可读性相对较低。如果一个父记录有多个子记录满足条件,JOIN 可能会返回重复的父记录行,需要 distinct() 或 groupBy() 来去重。
  • whereHas:
    • 优点: 更符合 Eloquent 的“面向对象”思想,代码更简洁、可读性更高。自动处理子查询逻辑,避免了字段冲突。
    • 缺点: 在某些情况下(例如子查询优化不佳或数据集非常庞大时),性能可能略低于手动 JOIN。主要用于检查关联记录的“存在性”,如果需要同时获取关联表的数据,则需要配合 with 进行预加载。

在本教程的场景中,两种方法都能有效实现需求。如果仅仅是根据关联表的存在性进行过滤,whereHas 更加优雅。如果涉及到从关联表选择数据或更复杂的查询,join 可能是更好的选择。

5. 注意事项与最佳实践

  • 输入验证: 在控制器中处理用户输入之前,务必进行严格的验证,防止 SQL 注入和其他安全漏洞。Laravel 提供了强大的验证功能。
  • 精确匹配 vs. 模糊匹配: 根据业务需求选择 where(精确匹配)或 where...like(模糊匹配)。对于年份和标签 ID,通常是精确匹配。
  • 空值处理: 确保您的查询逻辑能够优雅地处理搜索参数为空的情况,如示例中所示,当没有搜索条件时返回所有电影。
  • 性能优化: 对于高流量的应用程序,确保所有用于 WHERE 和 JOIN 条件的字段都已建立数据库索引,这将显著提高查询性能。
  • 可读性: 编写清晰、有注释的代码,尤其是在复杂的查询中。

通过掌握 join 和 whereHas 这两种强大的 Eloquent ORM 方法,您将能够灵活地处理 Laravel 应用程序中各种复杂的父子表联合查询与过滤需求。选择哪种方法取决于您的具体需求、性能考量以及代码可读性的偏好。

热门AI工具

更多
豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

相关专题

更多
laravel组件介绍
laravel组件介绍

laravel 提供了丰富的组件,包括身份验证、模板引擎、缓存、命令行工具、数据库交互、对象关系映射器、事件处理、文件操作、电子邮件发送、队列管理和数据验证。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

877

2024.04.09

laravel中间件介绍
laravel中间件介绍

laravel 中间件分为五种类型:全局、路由、组、终止和自定。想了解更多laravel中间件的相关内容,可以阅读本专题下面的文章。

855

2024.04.09

laravel使用的设计模式有哪些
laravel使用的设计模式有哪些

laravel使用的设计模式有:1、单例模式;2、工厂方法模式;3、建造者模式;4、适配器模式;5、装饰器模式;6、策略模式;7、观察者模式。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

2508

2024.04.09

thinkphp和laravel哪个简单
thinkphp和laravel哪个简单

对于初学者来说,laravel 的入门门槛较低,更易上手,原因包括:1. 更简单的安装和配置;2. 丰富的文档和社区支持;3. 简洁易懂的语法和 api;4. 平缓的学习曲线。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

4601

2024.04.10

laravel入门教程
laravel入门教程

本专题整合了laravel入门教程,想了解更多详细内容,请阅读专题下面的文章。

6710

2025.08.05

laravel实战教程
laravel实战教程

本专题整合了laravel实战教程,阅读专题下面的文章了解更多详细内容。

4416

2025.08.05

laravel面试题
laravel面试题

本专题整合了laravel面试题相关内容,阅读专题下面的文章了解更多详细内容。

8289

2025.08.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

1516

2026.03.04

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

60

2026.10.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
墨刀帮助中心
墨刀帮助中心

共0课时 | 0人学习

MyEclipse学习中心
MyEclipse学习中心

共0课时 | 0人学习

Apache Subversion 官方手册
Apache Subversion 官方手册

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn