17370845950

在 Laravel 中根据关联模型的第一个(最旧)记录日期进行排序

本教程将指导您如何在 laravel 项目中,根据 `hasmany` 关系中关联子模型的第一个(最旧)记录的日期,对父模型进行高效排序。我们将利用 laravel 提供的 `hasone` 关系结合 `oldestofmany()` 方法,简化复杂的查询逻辑,实现精确且性能优异的排序功能。

在 Laravel 应用开发中,一个常见的需求是根据关联模型的数据来对主模型进行排序。例如,我们有一个 Course 模型,它通过 hasMany 关系关联了多个 Session 模型。现在,我们需要根据每个课程的“第一个”会话(即日期最旧的会话)的日期来对课程进行排序。直接使用 orderBy 结合 hasMany 关系通常无法满足这种“获取并排序第一个关联记录”的复杂逻辑。

核心概念:hasOne 与 oldestOfMany()

Laravel 提供了 oldestOfMany() 方法,这是一个强大的工具,专门用于从 hasMany 关系中获取单个“最旧”或“最新”记录。当与 hasOne 关系结合使用时,它允许我们定义一个“一对一”的关系,但这个“一”是从“一对多”关系中筛选出来的特定记录。

要实现按第一个会话日期排序课程,我们首先需要在 Course 模型中定义一个特殊的 hasOne 关系,该关系将返回该课程下日期最旧的一个会话。

假设我们的 Session 模型有一个 created_at 字段用于表示会话创建日期。

// app/Models/Course.php

namespace App\Models;

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

class Course extends Model
{
    /**
     * 获取课程最早的一个会话。
     * 使用 oldestOfMany() 方法来确保我们只获取日期最旧的那个会话。
     * 默认情况下,oldestOfMany() 会根据主键进行排序。
     * 如果要根据 created_at 排序,需要明确指定。
     */
    public function oldestSession(): HasOne
    {
        // 明确指定根据 'created_at' 字段来确定“最旧”的会话
        return $this->hasOne(Session::class)->oldestOfMany('created_at');
    }

    /**
     * 如果您需要获取所有会话,可以定义一个标准的 hasMany 关系
     */
    public function sessions()
    {
        return $this->hasMany(Session::class);
    }
}

在 oldestOfMany() 方法中,我们传入了 'created_at' 作为参数,这指示 Laravel 应该根据 sessions 表的 created_at 字段来确定哪个会话是“最旧的”。如果没有指定参数,它将默认使用主键进行排序。

实现排序逻辑

定义了 oldestSession 关系后,我们就可以在查询 Course 模型时利用这个关系进行排序。为了实现父模型(Course)的排序,我们需要通过 JOIN 操作将每个课程的最早会话日期引入主查询中。

以下是实现这一排序逻辑的推荐方法:

// 例如,在控制器或服务中

use App\Models\Course;
use App\Models\Session;
use Illuminate\Support\Facades\DB;

class CourseController extends Controller
{
    public function index()
    {
        // 1. 构建一个子查询,用于获取每个 course_id 对应的最早会话日期
        $firstSessionsSubquery = Session::select('course_id', DB::raw('MIN(created_at) as first_session_date'))
            ->groupBy('course_id');

        // 2. 将主查询 (Course) 与上述子查询进行左连接 (leftJoinSub)
        //    这样每个课程就能关联到它的最早会话日期
        $courses = Course::select('courses.*') // 明确选择 courses 表的所有列,避免列名冲突
            ->leftJoinSub(
                $firstSessionsSubquery,
                'first_sessions', // 子查询的别名
                'first_sessions.course_id', '=', 'courses.id'
            )
            ->orderBy('first_sessions.first_session_date', 'asc') // 根据最早会话日期进行升序排序
            ->with('oldestSession') // 如果需要同时加载每个课程的最早会话的完整对象,则进行预加载
            ->get();

        return view('courses.index', compact('courses'));
    }
}

代码解释:

  • $firstSessionsSubquery: 这个子查询会遍历 sessions 表,为每个 course_id 找到最小的 created_at 值,并将其命名为 first_session_date。
  • leftJoinSub(): 我们使用 leftJoinSub 将 Course 模型与这个子查询的结果连接起来。`