0

0

Laravel 多对多关系中高效查询共享分类的文章

聖光之護

聖光之護

发布时间:2025-07-31 09:30:40

|

274人浏览过

|

来源于php中文网

原创

Laravel 多对多关系中高效查询共享分类的文章

在构建现代Web应用时,多对多关系是常见的数据模型之一。例如,一篇文章可以属于多个分类,而一个分类下也可以包含多篇文章。在这种场景下,一个常见的需求是:给定一篇文章,如何高效地找出所有与其共享至少一个分类的其他文章?

为了更好地理解这一需求,我们假设存在以下数据库表结构和eloquent模型定义:

数据库表结构:

posts
    - id
    - name

categories
    - id
    - name

category_post (中间表)
    - post_id
    - category_id

Eloquent 模型定义:

Post.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Post extends Model
{
    /**
     * 文章与分类的多对多关系
     */
    public function categories(): BelongsToMany
    {
        return $this->belongsToMany(Category::class);
    }
}

Category.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Category extends Model
{
    /**
     * 分类与文章的多对多关系
     */
    public function posts(): BelongsToMany
    {
        return $this->belongsToMany(Post::class);
    }
}

传统多步查询方法及其局限性

在不熟悉Eloquent高级查询的情况下,一种直观但效率较低的方法是分多步进行查询。例如,为了获取与ID为1的文章共享分类的所有文章,可以采用以下步骤:

use App\Models\Post;
use Illuminate\Support\Facades\DB;

// 1. 查找目标文章
$post = Post::find(1);

// 2. 获取目标文章关联的所有分类ID
$categoryIds = $post->categories()->pluck('id');

// 3. 根据这些分类ID,从中间表获取所有相关的文章ID
$postIds = DB::table('category_post')
    ->whereIn('category_id', $categoryIds)
    ->pluck('post_id')
    ->unique(); // 确保文章ID不重复

// 4. 根据获取到的文章ID,查询所有相关文章
$relatedPosts = DB::table('posts')
    ->whereIn('id', $postIds)
    ->get();

这种方法虽然能够达到目的,但它执行了至少四次独立的数据库查询(一次查找文章,一次查询分类ID,一次查询中间表,最后一次查询相关文章),这在性能上可能不是最优的,尤其是在高并发或大数据量场景下。

利用 Eloquent whereHas 进行高效查询

Laravel Eloquent ORM 提供了 whereHas 方法,用于基于关系的存在性来过滤模型。结合嵌套关系路径,我们可以用一个更优雅、更高效的查询来解决上述问题。

whereHas 方法的第一个参数是关系名称。当需要基于多层嵌套关系进行筛选时,可以使用点号 (.) 来指定关系路径。在本例中,我们想要找到与特定文章共享分类的文章。这意味着:

上班人导航
上班人导航

上班人必备的职场办公导航网站

下载
  1. 我们正在查询 posts 表。
  2. 这些 posts 必须拥有 categories。
  3. 这些 categories 又必须关联到 我们指定的那篇文章 (post)。

因此,关系路径是 categories.posts。查询逻辑如下:

use App\Models\Post;

$targetPostId = 1; // 假设我们要查询与ID为1的文章共享分类的文章

$relatedPosts = Post::whereHas('categories.posts', function ($query) use ($targetPostId) {
    // 这里的 $query 作用于 'categories' 关系下的 'posts' 关系
    // 也就是说,我们筛选的是那些其分类关联了目标文章的 posts
    $query->where('posts.id', $targetPostId);
})
->where('id', '!=', $targetPostId) // 排除掉目标文章本身
->get();

代码解析:

  • Post::whereHas('categories.posts', ...):我们从 Post 模型开始查询。categories.posts 表示我们正在检查一个文章的分类,而这些分类又关联了其他文章。
  • function ($query) use ($targetPostId):这是一个闭包,用于进一步约束 posts 关系。
  • $query->where('posts.id', $targetPostId):在 categories.posts 关系中,我们要求关联的 post 的 id 必须是 $targetPostId。这确保了我们只考虑那些与目标文章共享分类的文章。
  • ->where('id', '!=', $targetPostId):这一行是可选的,用于从结果集中排除掉查询的目标文章本身,因为我们通常只关心“其他”相关文章。

生成的 SQL 查询分析

尽管上述 Eloquent 查询看起来简洁,但它在底层会生成一个包含子查询的 SQL 语句。这通常比多步独立查询更高效,因为数据库可以在一个操作中处理所有逻辑,减少了客户端与数据库之间的往返次数。

生成的 SQL 示例大致如下:

SELECT *
FROM `posts`
WHERE EXISTS (
    SELECT *
    FROM `categories`
    INNER JOIN `category_post` ON `categories`.`id` = `category_post`.`category_id`
    WHERE `posts`.`id` = `category_post`.`post_id`
    AND EXISTS (
        SELECT *
        FROM `posts` AS `laravel_reserved_0`
        INNER JOIN `category_post` AS `laravel_reserved_1` ON `laravel_reserved_0`.`id` = `laravel_reserved_1`.`post_id`
        WHERE `categories`.`id` = `laravel_reserved_1`.`category_id`
        AND `laravel_reserved_0`.`id` = ?
    )
)
AND `id` != ?

这个 SQL 查询通过 EXISTS 子句来判断是否存在符合条件的关联记录。外层 EXISTS 检查文章是否与某个分类关联,内层 EXISTS 则进一步检查该分类是否关联了目标文章。这种结构允许数据库高效地进行过滤。

注意事项与最佳实践

  • whereHas 与 with 的区别: whereHas 用于过滤父模型,只返回符合条件的父模型实例,而不会加载关联数据。如果你需要同时加载这些相关分类或文章的数据,应该结合 with 方法使用,例如 Post::whereHas(...)->with('categories')。
  • 性能考量: 尽管 whereHas 生成的 SQL 通常包含子查询,但现代数据库优化器通常能很好地处理它们。对于极大数据量或复杂查询,始终建议使用 DB::listen 或 Laravel Debugbar 等工具来检查生成的 SQL 和执行时间,以便进行性能调优。
  • 清晰的命名: 确保你的模型关系方法名清晰、准确,例如 categories() 和 posts(),这有助于 whereHas 路径的理解和代码的可读性。

总结

在 Laravel 的多对多关系中,要高效地查询与给定模型共享关联的其他模型,whereHas 方法结合嵌套关系路径是一个强大且优雅的解决方案。它将多步查询合并为一个单一的、由 Eloquent 驱动的查询,极大地提升了代码的简洁性和执行效率。通过理解其背后的 SQL 逻辑,开发者可以更好地利用 Laravel Eloquent 的强大功能来构建高性能的应用程序。掌握这种查询技巧对于处理复杂关系型数据的过滤需求至关重要。

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

WorkBuddy
WorkBuddy

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

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

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

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

341

2024.04.09

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

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

296

2024.04.09

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

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

795

2024.04.09

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

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

387

2024.04.10

laravel入门教程
laravel入门教程

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

167

2025.08.05

laravel实战教程
laravel实战教程

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

86

2025.08.05

laravel面试题
laravel面试题

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

84

2025.08.05

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

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

851

2026.03.04

Python WebSocket实时通信与异步服务开发实践
Python WebSocket实时通信与异步服务开发实践

本专题聚焦 Python 在实时通信场景中的开发实践,系统讲解 WebSocket 协议原理、长连接管理、消息推送机制以及异步服务架构设计。内容包括客户端与服务端通信实现、连接稳定性优化、消息队列集成及高并发处理策略。通过完整案例,帮助开发者构建高效稳定的实时通信系统,适用于聊天应用、实时数据推送等场景。

7

2026.03.18

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Laravel---API接口
Laravel---API接口

共7课时 | 0.7万人学习

PHP自制框架
PHP自制框架

共8课时 | 0.6万人学习

PHP面向对象基础课程(更新中)
PHP面向对象基础课程(更新中)

共12课时 | 0.7万人学习

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

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