0

0

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

DDD

DDD

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

|

779人浏览过

|

来源于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 方法处理。

腾讯交互翻译
腾讯交互翻译

腾讯AI Lab发布的一款AI辅助翻译产品

下载

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工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

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

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

340

2024.04.09

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

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

293

2024.04.09

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

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

773

2024.04.09

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

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

385

2024.04.10

laravel入门教程
laravel入门教程

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

141

2025.08.05

laravel实战教程
laravel实战教程

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

85

2025.08.05

laravel面试题
laravel面试题

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

80

2025.08.05

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

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

500

2026.03.04

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PHP课程
PHP课程

共137课时 | 13.4万人学习

JavaScript ES5基础线上课程教学
JavaScript ES5基础线上课程教学

共6课时 | 11.3万人学习

PHP新手语法线上课程教学
PHP新手语法线上课程教学

共13课时 | 1.0万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号