Lar*el 递归模型:实现排除特定祖先及其所有后代记录的查询


Laravel 递归模型:实现排除特定祖先及其所有后代记录的查询

本教程详细介绍了如何在 lar*el 递归关系中,高效地查询并排除指定节点及其所有子孙节点的数据。通过定义 eloquent 模型中的递归关系,并结合自定义的 scope 方法和辅助函数,我们能够从复杂的层次结构数据中,精确地过滤掉特定分支,实现灵活的数据检索。文章涵盖了模型设置、核心逻辑实现、代码示例及性能优化考量。

Lar*el 递归关系模型设置

在处理具有父子关系的层级数据时,Lar*el Eloquent 提供了强大的递归关系定义能力。假设我们有一个 hobbies 表,其结构如下:

- id
- name
- parent_id

其中 parent_id 字段指向其父级爱好。为了在 Eloquent 模型中表示这种递归关系,我们需要在 Hobbies 模型中定义相应的关联方法:

// app/Models/Hobbies.php
<?php

namespace App\Models;

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

class Hobbies extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'parent_id'];

    /**
     * 获取当前爱好的所有子爱好。
     */
    public function sub_hobbies()
    {
        return $this->hasMany(Hobbies::class, 'parent_id');
    }

    /**
     * 获取当前爱好的父爱好。
     */
    public function parent_hobbies()
    {
        return $this->belongsTo(Hobbies::class, 'parent_id');
    }

    /**
     * 递归获取当前爱好的所有子孙爱好。
     * 使用 with('allsub') 实现无限层级预加载。
     */
    public function allsub()
    {
        return $this->sub_hobbies()->with('allsub');
    }

    /**
     * 递归获取当前爱好的所有祖先爱好。
     * 使用 with('allparent') 实现无限层级预加载。
     */
    public function allparent()
    {
        return $this->parent_hobbies()->with('allparent');
    }

    // ... 其他方法或 Scope
}

上述模型定义中,sub_hobbies 和 parent_hobbies 定义了直接的父子关系。allsub 和 allparent 方法通过 with 语句递归地加载所有子孙或祖先,这对于处理深度不确定的层级结构至关重要。

问题场景:排除特定分支及其所有后代

我们的目标是:给定一个爱好ID,查询所有爱好,但排除该ID对应的爱好及其所有子孙爱好。

例如,有以下爱好层级结构:

- 爱好 1
  - 爱好 11
  - 爱好 12
    - 爱好 121
    - 爱好 122
  - 爱好 13
- 爱好 2
  - 爱好 21
  - 爱好 22
    - 爱好 221
    - 爱好 222
  - 爱好 23
- 爱好 3
  - 爱好 31
  - 爱好 32
    - 爱好 321
    - 爱好 322
  - 爱好 33

如果给定“爱好 1”的ID,我们希望查询结果中不包含“爱好 1”、“爱好 11”、“爱好 12”、“爱好 121”、“爱好 122”和“爱好 13”。

CodeGeeX CodeGeeX

智谱AI发布的AI编程辅助工具插件,可以实现自动代码生成、代码翻译、自动编写注释以及智能问答等功能

CodeGeeX 166 查看详情 CodeGeeX

解决方案实现

为了实现上述目标,我们可以在 Hobbies 模型中添加一个局部作用域(Scope)方法 scopeIsNotLine 和一个私有辅助函数 flatten。

核心思路

  1. 获取排除列表: 首先,根据给定的ID,使用 allsub 关系递归地获取该爱好及其所有子孙爱好。
  2. 扁平化数据: 将获取到的嵌套结果转换成一个包含所有相关爱好ID的扁平数组。
  3. 执行查询: 使用 whereNotIn 条件,从所有爱好中排除这些ID。

代码实现

在 app/Models/Hobbies.php 模型中添加以下方法:

// app/Models/Hobbies.php

class Hobbies extends Model
{
    // ... 其他已定义的方法

    /**
     * 局部作用域:查询不属于指定爱好及其子孙链的所有爱好。
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param int $id 要排除的根爱好ID
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeIsNotLine($query, $id)
    {
        // 1. 获取要排除的根爱好及其所有子孙爱好
        // toArray() 将 Eloquent 集合转换为 PHP 数组,便于后续处理
        $hobbiesToExclude = Hobbies::with('allsub')->where('id', $id)->get()->toArray();

        // 2. 将嵌套的爱好数据扁平化,提取所有爱好节点的ID
        // 使用 collect 辅助函数和 map 闭包来提取ID
        $excludeIds = collect($this->flattenRecursiveData($hobbiesToExclude))
                        ->map(function ($item) {
                            // 确保 item 是数组且包含 'id' 键
                            return is_array($item) && isset($item['id']) ? $item['id'] : null;
                        })
                        ->filter() // 过滤掉 null 值
                        ->flatten() // 确保结果是扁平数组
                        ->unique() // 确保ID唯一
                        ->all();

        // 3. 执行查询:排除在 $excludeIds 列表中的所有爱好
        // 示例中还包含一个 whereDoesntH*e('is_archive') 条件,
        // 这表示排除那些没有关联 'is_archive' 关系的爱好,
        // 这是一个额外的业务逻辑,可根据实际需求移除或修改。
        return $query->whereNotIn('id', $excludeIds)->whereDoesntH*e('is_archive');
    }

    /**
     * 辅助函数:将嵌套的递归结果扁平化为包含所有节点(非嵌套)的数组。
     *
     * 该函数会遍历输入的数组,提取每个数组元素(代表一个爱好节点)的非数组属性,
     * 并递归处理其内部的嵌套数组(如 'sub_hobbies')。
     *
     * @param array $array 嵌套的爱好数据数组
     * @return array 扁平化的爱好节点数组
     */
    private function flattenRecursiveData(array $array): array
    {
        $result = [];
        foreach ($array as $item) {
            if (is_array($item)) {
                // 提取当前项的非数组属性(即当前节点自身的属性,不包含嵌套关系)
                $result[] = array_filter($item, function ($value) {
                    return !is_array($value) && !is_object($value);
                });

                // 递归处理当前项中的所有嵌套数组(例如 'sub_hobbies')
                foreach ($item as $key => $value) {
                    if (is_array($value)) {
                        $result = array_merge($result, $this->flattenRecursiveData($value));
                    }
                }
            }
        }
        // 过滤掉可能产生的空数组
        return array_filter($result);
    }
}

使用示例

在控制器或任何需要查询的地方,你可以像这样使用 isNotLine 局部作用域:

use App\Models\Hobbies;

// 假设要排除的爱好ID是 1
$hobbies = Hobbies::isNotLine(1)->get();

// $hobbies 集合中将包含除了 ID 为 1 及其所有子孙爱好之外的所有爱好。

注意事项与优化

  1. flattenRecursiveData 辅助函数: 这个函数负责将 Lar*el with 预加载出来的嵌套数组结构扁平化。它的工作原理是遍历每一个层级的节点,提取其自身的标量属性,并递归地处理其包含的子数组(例如 sub_hobbies 关系)。最终,collect(...)->map(...)->flatten()->unique()->all() 链式操作将这些扁平化的节点转换为唯一的ID列表。
  2. 性能考量:
    • N+1 问题: Hobbies::with('allsub') 语句本身会通过预加载解决 N+1 问题,但对于非常深的递归层级和大量数据,一次性加载整个分支到内存中可能会消耗较多资源。
    • 数据库效率: 对于支持 CTE(Common Table Expressions,如 MySQL 8+, PostgreSQL, SQL Server)的数据库,使用 CTE 可以更高效地在数据库层面进行递归查询和过滤,减少应用层的数据处理负担。例如,可以使用 CTE 递归地找出所有要排除的ID,然后直接在主查询中使用 NOT IN。
  3. 通用性: scopeIsNotLine 中的 whereDoesntH*e('is_archive') 是一个额外的条件,用于排除那些没有 is_archive 关系的爱好。如果你的应用没有这个需求,可以将其移除。
  4. 替代方案:
    • CTE (Common Table Expressions): 对于大型或深度递归的数据集,考虑使用数据库的 CTE 功能。你可以在 Lar*el 中通过 DB::raw 或编写更复杂的 Eloquent 查询来实现。
    • 预排序遍历树 (Nested Set Model) 或路径枚举 (Path Enumeration): 如果层级结构非常深且查询频繁,可以考虑在数据库层面采用这些专门的树结构存储方案,它们能极大地优化树形结构查询的性能。

总结

通过在 Lar*el Eloquent 模型中定义递归关系,并结合自定义的局部作用域和辅助函数,我们可以有效地处理复杂的层级数据查询需求,例如排除特定分支及其所有子孙节点。这种方法保持了代码的清晰性和 Eloquent 的优雅

以上就是Lar*el 递归模型:实现排除特定祖先及其所有后代记录的查询的详细内容,更多请关注php中文网其它相关文章!


# php  # 保山优良的全屏营销推广  # 游戏行业网站建设传播  # 民工seo蜘蛛池  # 清远响应式网站建设费用  # 福州网站建设方案报价  # 六安网站建设哪家技术好  # 保险营销视频号推广方案  # seo实战密码知识点  # 自定义  # 过滤掉  # 我们可以  # 你可以  # 扁平化  # 遍历  # 加载  # 已有  # 管理系统  # 递归  # red  # 作用域  # app  # laravel  # mysql  # 网站建设与维护的定义  # 淘特营销推广怎么做的 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 优化推广96088 】 【 技术知识133117 】 【 IDC资讯59369 】 【 网络运营7196 】 【 IT资讯61894 】


相关推荐: J*aScript二进制处理_ArrayBuffer与Blob  TikTok网页版实时观看入口 TikTok网页版短视频在线浏览  NumPy 高性能技巧:基于多列条件查找最近邻行索引的向量化实现  顺丰快递单号查询寄件人 顺丰寄件人查询入口  win11怎么更改账户类型 Win11标准用户和管理员权限切换【教程】  Bootstrap 5导航栏折叠功能失效:数据属性迁移指南  lol小红书怎么|直播|?lol小红书|直播|是什么意思?  重返未来:1999卡戎全方位攻略  Word如何将文字快速转成表格 Word文本转换成表格功能使用技巧【效率】  sublime如何自定义文件类型图标_AFileIcon插件的主题切换与个性化配置  《小黑盒》删除历史浏览方法  《雷电模拟器》截图方法介绍  Go Template中优雅处理循环最后一项:自定义函数实践  铁路12306座位怎么选_12306官方选座操作方法  稻壳阅读器官方直达网址链接 稻壳阅读器文档阅读平台主页资源入口  《律学法考》查看学习数据方法  Go语言中方法接收器的选择:值类型还是指针类型?  创建快捷方式启动系统保护  优化CSS动画与J*aScript定时器协同:构建稳定Toast提示  《procreate》绘制渐变效果教程  《虎扑》关闭社区内容推荐方法  虫虫漫画绿色安全入口_虫虫漫画绿色安全入口安全看漫画  秋风萧瑟洪波涌起中的萧瑟指的是什么  中通快递官网指定查询 中通快递单号查询平台入口  excel怎么计算平均值 excel平均函数*ERAGE使用教学  创客贴登录页面入口 创客贴网页版最新网址链接  CSS过渡与滚动滚动事件结合应用_scroll与transition动画  《顺丰同城骑士》查看我的技能方法  在Peewee中处理PostgreSQL记录重复:一站式数据摄取教程  Win10如何关闭开机锁屏界面_Windows10跳过锁屏直接登录设置  《我的恋爱逃生攻略》中文名字输入方法  iQOO手机信号差网络不稳定怎么办 信号问题原因排查与增强设置【攻略】  C++如何实现单例模式_C++线程安全的单例模式写法  三星A55应用闪退排查步骤_Samsung A55稳定性优化技巧  苹果自助维修计划支持哪些设备机型  谷歌学术论文搜索引擎 谷歌学术官网入口论坛永久链接  斯宾塞称XGP云游戏“蒸蒸日上”:正在构建一个游戏从未如此唾手可得的未来  铁路12306怎么申请退票_铁路12306退票申请操作流程  使用jQuery精确检测除指定元素外任意位置的点击事件  Win10如何关闭操作中心通知 Win10免打扰设置全攻略【清爽】  《淘宝联盟》推广自己的店铺方法  多闪APP官方下载安装入口_多闪最新版本获取入口  Golang如何操作指针参数_Go pointer参数传递规则  房产|直播|视频号怎么认证开通?|直播|需要什么资质?  外卖小程序对接第三方配送  钉钉任务无法提醒如何处理 钉钉任务提醒优化方法  Python项目中的条件导入:解决跨模块依赖问题  附近酒吧怎么找?  性能与资源监视器快捷打开  英国搜索:多数英国人认为语言搜索是未来搜索 

 2025-12-02

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

运城市盐湖区信雨科技有限公司


运城市盐湖区信雨科技有限公司

运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。

 8156699

 13765294890

 8156699@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.