0

0

Laravel Eloquent:基于关联关系是否存在进行父模型过滤与删除

碧海醫心

碧海醫心

发布时间:2025-12-01 12:12:14

|

761人浏览过

|

来源于php中文网

原创

Laravel Eloquent:基于关联关系是否存在进行父模型过滤与删除

本文深入探讨在 laravel eloquent 中,如何高效地处理多对多关系中无关联子记录的父模型查询与删除。文章详细介绍了两种核心策略:一是利用 `wheredoesnthave` 方法直接基于关系进行过滤;二是引入并维护一个去范式化的计数列以优化查询性能。通过示例代码和注意事项,帮助开发者选择并实现最适合其应用场景的数据管理方案。

在 Laravel 应用开发中,处理模型之间的多对多关系是常见的场景。有时,我们需要识别或删除那些在特定多对多关系中没有任何关联子记录的父模型。例如,在一个订单与空调(商品)的多对多关系中,我们可能需要找出或删除那些没有任何空调关联的订单。本文将详细介绍两种有效的方法来解决此类问题。

方法一:利用 whereDoesntHave 进行关系过滤

Laravel Eloquent 提供了 whereDoesntHave 方法,它允许我们根据模型是否不拥有特定关系来过滤查询结果。这是一种非常直观且符合 Eloquent 哲学的方式来解决此类问题。

工作原理:whereDoesntHave(relationName) 会在数据库层面生成一个 WHERE NOT EXISTS 子查询,以检查主模型记录是否在指定的关联表中没有对应的记录。这确保了只返回那些在多对多关系中没有任何关联的父模型。

示例场景: 假设我们有一个 Order 模型和一个 Aircon 模型,它们之间是多对多关系。我们希望找出当前认证用户下所有没有关联任何空调的订单,或者直接删除这些订单。

代码实现:

use App\Models\Order;
use Illuminate\Support\Facades\Auth;

// 1. 查找当前用户下所有没有关联任何空调的订单
$ordersWithoutAircons = Order::whereDoesntHave('aircons')
                             ->where('user_id', Auth::id())
                             ->get();

// 2. 直接删除当前用户下所有没有关联任何空调的订单
// 注意:执行此操作前请务必确认,删除操作不可逆
$deletedCount = Order::whereDoesntHave('aircons')
                      ->where('user_id', Auth::id())
                      ->delete();

echo "已删除 {$deletedCount} 个没有关联空调的订单。";

// 3. 如果需要包含其他关联(例如用户),可以在 `whereDoesntHave` 之后使用 `with`
$ordersWithUserButNoAircons = Order::with('user')
                                   ->whereDoesntHave('aircons')
                                   ->where('user_id', Auth::id())
                                   ->get();

代码解释:

  • Order::whereDoesntHave('aircons'): 这一部分是核心,它指示 Eloquent 只选择那些与 aircons 关系没有任何关联的 Order 记录。
  • ->where('user_id', Auth::id()): 这是一个额外的条件,用于将结果限制为当前认证用户的订单。
  • ->get(): 获取符合条件的订单集合。
  • ->delete(): 直接执行删除操作,返回被删除的记录数量。

注意事项:

  • whereDoesntHave 在处理大量数据时可能会导致性能问题,因为它会生成一个子查询。对于极端大规模的数据集,可能需要考虑其他优化策略。
  • 确保 aircons 是在 Order 模型中正确定义的多对多关系。

方法二:通过去范式化的计数列优化查询

对于需要频繁查询没有关联子记录的父模型,或者对查询性能有极高要求的场景,可以考虑引入一个去范式化的计数列。例如,在 orders 表中添加一个 aircons_count 列,用于存储每个订单关联的空调数量。

Magic Eraser
Magic Eraser

AI移除图片中不想要的物体

下载

工作原理: 通过在父模型表中维护一个计数列,每次关联或解除关联子模型时,都同步更新这个计数。这样,在查询时可以直接通过这个计数列进行过滤,避免了复杂的关联查询,从而显著提高查询速度。

实现步骤:

  1. 添加计数列: 在 orders 表中添加 aircons_count 字段。

    Schema::table('orders', function (Blueprint $table) {
        $table->unsignedInteger('aircons_count')->default(0)->after('user_id');
    });
  2. 维护计数列: 这是最关键的一步。每次向 Order 关联或解除关联 Aircon 时,都需要手动更新 aircons_count。这可以通过模型事件监听器、观察者(Observer)或者在业务逻辑中显式更新来实现。

    示例:在业务逻辑中维护

    // 假设在 Order 模型中定义了 aircons 关系
    class Order extends Model
    {
        public function aircons()
        {
            return $this->belongsToMany(Aircon::class);
        }
    
        // 关联空调时更新计数
        public function attachAircon(Aircon $aircon)
        {
            if (!$this->aircons->contains($aircon)) {
                $this->aircons()->attach($aircon);
                $this->increment('aircons_count');
            }
        }
    
        // 解除关联空调时更新计数
        public function detachAircon(Aircon $aircon)
        {
            if ($this->aircons->contains($aircon)) {
                $this->aircons()->detach($aircon);
                $this->decrement('aircons_count');
            }
        }
    }
    
    // 在使用时
    $order = Order::find(1);
    $aircon = Aircon::find(1);
    
    $order->attachAircon($aircon); // 关联并更新计数
    // 或者
    $order->detachAircon($aircon); // 解除关联并更新计数

    更推荐的方式:使用 Eloquent 事件或观察者 在 Order 模型中监听 pivotAttached 和 pivotDetached 事件,自动更新计数。

    // 在 Order 模型中
    protected static function booted()
    {
        static::pivotAttached(function ($model, $relationName, $pivotIds, $pivotAttributes) {
            if ($relationName === 'aircons') {
                $model->increment('aircons_count');
            }
        });
    
        static::pivotDetached(function ($model, $relationName, $pivotIds) {
            if ($relationName === 'aircons') {
                $model->decrement('aircons_count');
            }
        });
    }
  3. 使用计数列进行查询: 一旦计数列被正确维护,查询就变得非常简单和高效。

    // 1. 查找当前用户下所有没有关联任何空调的订单
    $ordersWithoutAircons = Order::where('aircons_count', 0)
                                 ->where('user_id', Auth::id())
                                 ->get();
    
    // 2. 直接删除当前用户下所有没有关联任何空调的订单
    $deletedCount = Order::where('aircons_count', 0)
                          ->where('user_id', Auth::id())
                          ->delete();
    
    echo "已删除 {$deletedCount} 个没有关联空调的订单。";
    
    // 3. 查找当前用户下所有有至少一个空调的订单
    $ordersWithAircons = Order::with('user')
                               ->where('aircons_count', '>', 0)
                               ->where('user_id', Auth::id())
                               ->get();

注意事项:

  • 数据一致性: 维护计数列需要额外的逻辑来确保数据一致性。任何遗漏的更新操作都可能导致计数不准确。
  • 写操作开销: 每次关联或解除关联时,都会增加一次写操作(更新计数列)。对于写密集型应用,这可能是一个需要权衡的因素。
  • 首次填充: 如果是现有项目,在添加 aircons_count 列后,需要编写一次性脚本来为所有现有订单填充正确的计数。

选择合适的策略

  • whereDoesntHave:

    • 优点: 实现简单,不需要修改数据库结构,数据一致性由 Eloquent 自动处理。
    • 缺点: 对于非常大的数据集或频繁的查询,性能可能不如计数列。
    • 适用场景: 查询频率不高,或者数据集规模适中,追求开发效率和代码简洁性的场景。
  • 去范式化计数列:

    • 优点: 查询性能极高,尤其适用于需要频繁查询的场景。
    • 缺点: 增加了数据库结构复杂性,需要额外逻辑来维护数据一致性,增加了写操作的开销。
    • 适用场景: 对查询性能有严格要求,数据集非常庞大,且能够接受维护计数列的复杂性。

总结

在 Laravel Eloquent 中处理多对多关系中无关联子记录的父模型查询与删除,开发者可以根据具体需求和性能考量,选择 whereDoesntHave 方法或引入去范式化的计数列。whereDoesntHave 提供了一种简洁的 Eloquent 解决方案,而计数列则为高并发、大数据量的场景提供了卓越的查询性能。理解这两种方法的优缺点,将有助于构建更高效、更健壮的 Laravel 应用。

相关专题

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

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

317

2024.04.09

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

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

276

2024.04.09

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

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

370

2024.04.09

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

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

371

2024.04.10

laravel入门教程
laravel入门教程

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

81

2025.08.05

laravel实战教程
laravel实战教程

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

64

2025.08.05

laravel面试题
laravel面试题

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

67

2025.08.05

数据库Delete用法
数据库Delete用法

数据库Delete用法:1、删除单条记录;2、删除多条记录;3、删除所有记录;4、删除特定条件的记录。更多关于数据库Delete的内容,大家可以访问下面的文章。

269

2023.11.13

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

19

2026.01.20

热门下载

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

精品课程

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

共7课时 | 0.6万人学习

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号