Laravel 递归查询:高效排除指定父级及其所有子孙节点

Laravel 递归查询:高效排除指定父级及其所有子孙节点

本文详细介绍了在 laravel 递归关系中,如何高效地查询并排除指定父级及其所有子孙节点的数据。通过利用 laravel 的模型关系和自定义查询作用域,结合一个辅助的扁平化函数,本教程提供了一种实用的解决方案,用于处理层级数据结构中复杂的排除逻辑,确保精准获取所需数据。

在构建具有层级结构的应用时,例如分类、标签或评论系统,我们经常会遇到需要处理递归关系的情况。Laravel 提供了强大的 Eloquent ORM,通过自关联关系可以很好地管理这类数据。本教程将深入探讨如何在一个递归模型中,实现一个高级查询,即排除指定父级及其所有子孙节点的数据。

理解递归关系模型

首先,我们定义一个名为 Hobbies 的模型,它代表一个具有层级关系的兴趣爱好列表。该模型通过 parent_id 字段指向其父级爱好,从而形成一个树状结构。

// app/Models/Hobbies.phphasMany(Hobbies::class, 'parent_id');    }    /**     * 获取当前爱好的父爱好。     */    public function parent_hobbies()    {        return $this->belongsTo(Hobbies::class, 'parent_id');    }    /**     * 递归获取当前爱好的所有子孙爱好。     */    public function allsub()    {        return $this->sub_hobbies()->with('allsub');    }    /**     * 递归获取当前爱好的所有祖先爱好。     */    public function allparent()    {        return $this->parent_hobbies()->with('allparent');    }    // ... 其他方法和作用域将在此处添加}

在上述模型中:

sub_hobbies 定义了“一对多”的子级关系。parent_hobbies 定义了“多对一”的父级关系。allsub 和 allparent 是递归关系,它们通过 with(‘allsub’) 和 with(‘allparent’) 实现了加载所有子孙或祖先节点的功能。

核心需求分析

我们的目标是:给定一个特定爱好的 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”之外的所有爱好。

实现方案:自定义查询作用域

为了实现这一需求,我们将通过在 Hobbies 模型中添加一个自定义查询作用域(Scope)和辅助方法来完成。

// app/Models/Hobbies.php (续)flatten($item));            }        }        // 过滤掉空的数组项,确保只返回有效的模型数据        return array_filter($result);    }    /**     * 查询所有不属于给定爱好及其子孙线的爱好。     *     * @param Builder $query Eloquent 查询构建器实例。     * @param int $id 要排除的父级爱好的ID。     * @return Builder 修改后的查询构建器。     */    public function scopeIsNotLine(Builder $query, int $id): Builder    {        // 1. 获取指定爱好及其所有子孙爱好        // 使用 with('allsub') 递归加载所有子孙节点        $hobbiesToExclude = Hobbies::with('allsub')->where('id', $id)->get()->toArray();        // 2. 将嵌套结果扁平化,并提取所有需要排除的爱好ID        // 调用 flatten 辅助方法将嵌套的爱好数据转换为扁平数组        $flattenedHobbies = collect($this->flatten($hobbiesToExclude));        // 从扁平化后的数据中提取所有爱好的 ID        $excludeIds = $flattenedHobbies->map(function ($item) {            return $item['id'] ?? null; // 确保 id 存在        })->filter()->unique()->all(); // 过滤空值并去重        // 3. 使用 whereNotIn 条件排除这些 ID        $query->whereNotIn('id', $excludeIds);        // 4. (可选) 添加其他过滤条件,例如排除已归档的爱好        // 这里的 is_archive 关系是一个假设,你需要根据实际模型定义        // return $query->whereDoesntHave('is_archive');        return $query; // 如果没有 is_archive 关系,直接返回 $query    }}

flatten 辅助函数详解

flatten 方法是解决问题的关键之一。由于 with(‘allsub’) 加载的数据是一个嵌套的数组结构(因为关系是递归的),我们需要一个方法来遍历这个嵌套结构,并提取出所有独立的爱好项,无论它们位于哪一层。

它递归地遍历输入的数组。对于每个数组项,如果它本身是一个数组(意味着它可能包含嵌套关系),它会:提取当前项中所有非数组/非对象的值(即当前模型自身的属性,如 id, name, parent_id)。然后递归调用自身处理当前项的子数组(即嵌套关系的数据),并将结果合并到 result 数组中。最终,它返回一个扁平化的数组,其中包含了所有层级的爱好数据。

scopeIsNotLine 作用域实现

这个作用域是主要的查询逻辑:

获取要排除的爱好及其子孙线:Hobbies::with(‘allsub’)->where(‘id’, $id)->get()->toArray();这一步首先通过 with(‘allsub’) 加载指定 ID 的爱好及其所有递归子孙。.get()->toArray() 将 Eloquent 集合转换为 PHP 数组,以便后续的扁平化处理。

扁平化结果并提取 ID:$flattenedHobbies = collect($this->flatten($hobbiesToExclude));$excludeIds = $flattenedHobbies->map(…)->filter()->unique()->all();这里利用 flatten 方法将复杂的嵌套数组结构转换为一个简单的数组,其中每个元素都是一个独立的爱好数据数组。接着,通过 Laravel 的集合方法 map 提取出每个爱好的 id,filter 移除可能存在的空值,unique 确保 ID 不重复,最后 all() 转换为纯 PHP 数组。

使用 whereNotIn 排除:$query->whereNotIn(‘id’, $excludeIds);这是 Eloquent 的核心功能,它会修改当前查询,使其只返回 ID 不在 $excludeIds 列表中的爱好。

额外过滤条件(可选):$query->whereDoesntHave(‘is_archive’);原始问题中包含了一个 whereDoesntHave(‘is_archive’) 的条件。这表示在排除指定父子线的同时,还希望排除那些具有 is_archive 关系(例如,表示已归档)的爱好。如果你的模型没有这个关系,或者不需要这个过滤,可以将其移除。

使用方法

一旦 scopeIsNotLine 作用域被定义,你就可以像使用任何其他 Eloquent 作用域一样来使用它:

use AppModelsHobbies;// 假设要排除 ID 为 1 的爱好及其所有子孙$targetId = 1;$filteredHobbies = Hobbies::isNotLine($targetId)->get();// $filteredHobbies 将包含所有不属于 ID 为 1 的爱好及其子孙的爱好foreach ($filteredHobbies as $hobby) {    echo $hobby->name . "n";}

注意事项与优化

性能考虑

对于非常深或非常宽的递归层级,with(‘allsub’) 会导致大量的 JOIN 操作,可能影响查询性能。将 Eloquent 集合转换为数组 (toArray()) 并在 PHP 中进行扁平化处理,对于大规模数据集可能会消耗较多内存和 CPU 资源。如果性能成为瓶颈,可以考虑在数据库层面进行优化,例如使用 递归 CTE (Common Table Expressions)。MySQL 8+、PostgreSQL 和 SQL Server 都支持 CTE,可以在数据库层面直接生成排除列表,从而减少 PHP 端的处理负担。

flatten 方法的健壮性:当前 flatten 方法假设模型属性是标量或简单的数组。如果模型关系中包含更复杂的对象或集合,可能需要调整 array_filter 的条件。

替代方案:闭包表或路径枚举:对于非常复杂的递归关系查询,更专业的解决方案是使用 闭包表 (Closure Table)路径枚举 (Path Enumeration) 模式。这些模式通过额外的辅助表或字段来存储节点间的路径信息,从而将递归查询转换为简单的非递归查询,极大地提高查询效率和灵活性。Laravel 社区也有一些包实现了这些模式。

总结

本教程提供了一种在 Laravel 中处理递归关系并排除特定分支的实用方法。通过结合 Eloquent 的递归关系加载、自定义的扁平化函数和查询作用域,我们能够有效地实现复杂的层级数据过滤需求。尽管这种 PHP 端的处理方式对于中小型数据集非常有效,但在处理大规模或深度层级数据时,建议考虑数据库层面的优化方案以获得更好的性能。

以上就是Laravel 递归查询:高效排除指定父级及其所有子孙节点的详细内容,更多请关注php中文网其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1339871.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月13日 03:20:24
下一篇 2025年12月13日 03:20:35

相关推荐

  • HTMLrev 上的免费 HTML 网站模板

    HTMLrev 是唯一的人工策划的库专门专注于免费 HTML 模板,适用于由来自世界各地慷慨的模板创建者制作的网站、登陆页面、投资组合、博客、电子商务和管理仪表板世界。 这个人就是我自己 Devluc,我已经工作了 1 年多来构建、改进和更新这个很棒的免费资源。我自己就是一名模板制作者,所以我知道如…

    2025年12月24日
    300
  • 如何使用 Laravel 框架轻松整合微信支付与支付宝支付?

    如何通过 laravel 框架整合微信支付与支付宝支付 在 laravel 开发中,为电商网站或应用程序整合支付网关至关重要。其中,微信支付和支付宝是中国最流行的支付平台。本文将介绍如何使用 laravel 框架封装这两大支付平台。 一个简单有效的方法是使用业内认可的 easywechat lara…

    2025年12月24日
    000
  • Laravel 框架中如何无缝集成微信支付和支付宝支付?

    laravel 框架中微信支付和支付宝支付的封装 如何将微信支付和支付宝支付无缝集成到 laravel 框架中? 建议解决方案 考虑使用 easywechat 的 laravel 版本。easywechat 是一个成熟、维护良好的库,由腾讯官方人员开发,专为处理微信相关功能而设计。其 laravel…

    2025年12月24日
    300
  • 如何在 Laravel 框架中轻松集成微信支付和支付宝支付?

    如何用 laravel 框架集成微信支付和支付宝支付 问题:如何在 laravel 框架中集成微信支付和支付宝支付? 回答: 建议使用 easywechat 的 laravel 版,easywechat 是一个由腾讯工程师开发的高质量微信开放平台 sdk,已被广泛地应用于许多 laravel 项目中…

    2025年12月24日
    000
  • 使用Laravel框架如何整合微信支付和支付宝支付?

    使用 Laravel 框架整合微信支付和支付宝支付 在使用 Laravel 框架开发项目时,整合支付网关是常见的需求。对于微信支付和支付宝支付,推荐采用以下方法: 使用第三方库:EasyWeChat 的 Laravel 版本 建议直接使用现有的 EasyWeChat 的 Laravel 版本。该库由…

    2025年12月24日
    000
  • 如何将微信支付和支付宝支付无缝集成到 Laravel 框架中?

    如何简洁集成微信和支付宝支付到 Laravel 问题: 如何将微信支付和支付宝支付无缝集成到 Laravel 框架中? 答案: 强烈推荐使用流行的 Laravel 包 EasyWeChat,它由腾讯开发者维护。多年来,它一直保持更新,提供了一个稳定可靠的解决方案。 集成步骤: 安装 Laravel …

    2025年12月24日
    100
  • 您不需要 CSS 预处理器

    原生 css 在最近几个月/几年里取得了长足的进步。在这篇文章中,我将回顾人们使用 sass、less 和 stylus 等 css 预处理器的主要原因,并向您展示如何使用原生 css 完成这些相同的事情。 分隔文件 分离文件是人们使用预处理器的主要原因之一。尽管您已经能够将另一个文件导入到 css…

    2025年12月24日
    000
  • React 嵌套组件中,CSS 样式会互相影响吗?

    react 嵌套组件 css 穿透影响 在 react 中,嵌套组件的 css 样式是否会相互影响,取决于采用的 css 解决方案。 传统 css 如果使用传统的 css,在嵌套组件中定义的样式可能会穿透影响到父组件。例如,在给出的代码中: 立即学习“前端免费学习笔记(深入)”; component…

    2025年12月24日
    000
  • React 嵌套组件中父组件 CSS 修饰会影响子组件样式吗?

    对嵌套组件的 CSS 修饰是否影响子组件样式 提问: 在 React 中,如果对嵌套组件 ComponentA 配置 CSS 修饰,是否会影响到其子组件 ComponentB 的样式?ComponentA 是由 HTML 元素(如 div)组成的。 回答: 立即学习“前端免费学习笔记(深入)”; 在…

    2025年12月24日
    000
  • 在 React 项目中实现 CSS 模块

    react 中的 css 模块是一种通过自动生成唯一的类名来确定 css 范围的方法。这可以防止大型应用程序中的类名冲突并允许模块化样式。以下是在 react 项目中使用 css 模块的方法: 1. 设置 默认情况下,react 支持 css 模块。你只需要用扩展名 .module.css 命名你的…

    2025年12月24日
    000
  • 网络进化!

    Web 应用程序从静态网站到动态网页的演变是由对更具交互性、用户友好性和功能丰富的 Web 体验的需求推动的。以下是这种范式转变的概述: 1. 静态网站(1990 年代) 定义:静态网站由用 HTML 编写的固定内容组成。每个页面都是预先构建并存储在服务器上,并且向每个用户传递相同的内容。技术:HT…

    2025年12月24日
    000
  • 为什么多年的经验让我选择全栈而不是平均栈

    在全栈和平均栈开发方面工作了 6 年多,我可以告诉您,虽然这两种方法都是流行且有效的方法,但它们满足不同的需求,并且有自己的优点和缺点。这两个堆栈都可以帮助您创建 Web 应用程序,但它们的实现方式却截然不同。如果您在两者之间难以选择,我希望我在两者之间的经验能给您一些有用的见解。 在这篇文章中,我…

    2025年12月24日
    000
  • action在css中的用法

    CSS 中 action 关键字用于定义鼠标悬停或激活元素时的行为,语法:element:action { style-property: value; }。它可以应用于 :hover 和 :active 伪类,用于创建交互效果,如更改元素外观、显示隐藏元素或启动动画。 action 在 CSS 中…

    2025年12月24日
    000
  • css规则的类型有哪些

    CSS 规则包括:通用规则:选择所有元素类型选择器:根据元素类型选择元素类选择器:根据元素的 class 属性选择元素ID 选择器:根据元素的 id 属性选择元素(唯一)后代选择器:选择特定父元素内的元素子选择器:选择作为特定父元素的直接子元素的元素伪类:基于元素的状态或特性选择元素伪元素:创建元素…

    2025年12月24日
    000
  • 网页设计css样式代码大全,快来收藏吧!

    减少很多不必要的代码,html+css可以很方便的进行网页的排版布局。小伙伴们收藏好哦~ 一.文本设置    1、font-size: 字号参数  2、font-style: 字体格式 3、font-weight: 字体粗细 4、颜色属性 立即学习“前端免费学习笔记(深入)”; color: 参数 …

    2025年12月24日
    000
  • css中id选择器和class选择器有何不同

    之前的文章《什么是CSS语法?详细介绍使用方法及规则》中带了解CSS语法使用方法及规则。下面本篇文章来带大家了解一下CSS中的id选择器与class选择器,介绍一下它们的区别,快来一起学习吧!! id选择器和class选择器介绍 CSS中对html元素的样式进行控制是通过CSS选择器来完成的,最常用…

    2025年12月24日
    000
  • CSS如何实现任意角度的扇形(代码示例)

    本篇文章给大家带来的内容是关于CSS如何实现任意角度的扇形(代码示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。 扇形制作原理,底部一个纯色原形,里面2个相同颜色的半圆,可以是白色,内部半圆按一定角度变化,就可以产生出扇形效果 扇形绘制 .shanxing{ position:…

    2025年12月24日
    000
  • php约瑟夫问题如何解决

    “约瑟夫环”是一个数学的应用问题:一群猴子排成一圈,按1,2,…,n依次编号。然后从第1只开始数,数到第m只,把它踢出圈,从它后面再开始数, 再数到第m只,在把它踢出去…,如此不停的进行下去, 直到最后只剩下一只猴子为止,那只猴子就叫做大王。要求编程模拟此过程,输入m、n, 输出最后那个大王的编号。…

    好文分享 2025年12月24日
    000
  • CSS新手整理的有关CSS使用技巧

    [导读]  1、不要使用过小的图片做背景平铺。这就是为何很多人都不用 1px 的原因,这才知晓。宽高 1px 的图片平铺出一个宽高 200px 的区域,需要 200*200=40, 000 次,占用资源。  2、无边框。推荐的写法是     1、不要使用过小的图片做背景平铺。这就是为何很多人都不用 …

    好文分享 2025年12月23日
    000
  • CSS中实现图片垂直居中方法详解

    [导读] 在曾经的 淘宝ued 招聘 中有这样一道题目:“使用纯css实现未知尺寸的图片(但高宽都小于200px)在200px的正方形容器中水平和垂直居中。”当然出题并不是随意,而是有其现实的原因,垂直居中是 淘宝 工作中最 在曾经的 淘宝UED 招聘 中有这样一道题目: “使用纯CSS实现未知尺寸…

    好文分享 2025年12月23日
    000

发表回复

登录后才能评论
关注微信