如何在 Laravel 中合并两个查询并关联平均值与用户数据

如何在 Laravel 中合并两个查询并关联平均值与用户数据

本文介绍在 laravel 中通过 sql join 或集合合并方式,将课程统计的每日平均值(avgstudy/avgargument)与当前用户的实际学习数据(study/argument)按日期对齐,生成结构化结果集。

在 Laravel 中实现两个查询结果按 date 字段精准合并(即仅保留两个结果集中都存在的日期),推荐使用 数据库层面的 INNER JOIN,而非 PHP 层面的集合合并——这样更高效、语义更清晰,且能充分利用数据库索引与优化器。

✅ 推荐方案:单次 SQL JOIN 查询(最优实践)

直接在数据库中完成聚合与关联,避免 N+1 和内存遍历:

use Illuminate/Support/Facades/DB;
use Illuminate/Support/Facades/Auth;

$result = DB::table(DB::raw('(
    SELECT 
        date AS avgDate,
        ROUND(AVG(study), 1) AS avgStudy,
        ROUND(AVG(argument), 1) AS avgArgument
    FROM study_arguments 
    WHERE course_id = 2 
    GROUP BY date
) AS avg_data'))
->join(DB::raw('(
    SELECT date, study, argument 
    FROM study_arguments 
    WHERE user_id = ?
) AS user_data'), 'avg_data.avgDate', '=', 'user_data.date')
->select(
    'avg_data.avgDate',
    'avg_data.avgStudy',
    'avg_data.avgArgument',
    'user_data.study',
    'user_data.argument'
)
->setBindings([Auth::user()->id])
->get();

? 说明: 外层 DB::table(…) 包裹子查询 avg_data(课程 2 的每日均值); join(…) 关联 user_data 子查询(当前用户所有记录); ON avg_data.avgDate = user_data.date 确保只返回双方共有的日期(即你期望的三行结果); setBindings() 安全绑定用户 ID,防止 SQL 注入。

⚠️ 注意事项

  • 日期类型一致性:确保 study_arguments.date 字段为 DATE 类型(非 DATETIME),否则 GROUP BY date 和 JOIN 可能因时间部分不匹配而失败。建表时应使用 $table->date(‘date’)(如答案中所示)。
  • 空值处理:若某日用户有记录但课程无其他用户数据(导致 AVG 为 NULL),该行将被 INNER JOIN 自动排除;如需保留用户数据(显示 NULL 平均值),请改用 LEFT JOIN 并调整子查询逻辑。
  • 性能优化:为 study_arguments(course_id, date) 和 (user_id, date) 添加复合索引:

    // 在 migration 中添加
    $table->index(['course_id', 'date']);
    $table->index(['user_id', 'date']);

? 替代方案:Eloquent + 集合合并(适用于简单场景)

若因复杂条件难以写 JOIN,可先获取两组数据,再用 Laravel Collection 合并:

Cursor

Cursor

一个新的IDE,使用AI来帮助您重构、理解、调试和编写代码。

下载

$avgData = StudyArgument::where('course_id', 2)
    ->selectRaw('date as avgDate, ROUND(AVG(study), 1) as avgStudy, ROUND(AVG(argument), 1) as avgArgument')
    ->groupBy('date')
    ->get()
    ->keyBy('avgDate'); // 以 date 为键

$userData = StudyArgument::where('user_id', Auth::user()->id)
    ->select('date', 'study', 'argument')
    ->get()
    ->keyBy('date');

// 合并:只取交集日期
$merged = $avgData->intersectByKeys($userData)->map(function ($avg, $date) use ($userData) {
    return (object) [
        'avgDate'     => $date,
        'avgStudy'    => $avg->avgStudy,
        'avgArgument' => $avg->avgArgument,
        'study'       => $userData[$date]->study,
        'argument'    => $userData[$date]->argument,
    ];
})->values();

✅ 优点:逻辑直观,便于调试;
❌ 缺点:两次查询 + 内存处理,大数据量时性能较差,且无法利用数据库 JOIN 优化。

✅ 总结

方案 适用场景 效率 推荐度
原生 JOIN 子查询 生产环境、数据量大、需精确关联 ⭐⭐⭐⭐⭐ ★★★★★
Collection 合并 快速原型、逻辑复杂难 SQL 化、数据量小 ⭐⭐ ★★☆

始终优先选择数据库层关联——它更可靠、更快速,也更符合 Laravel “让数据库做它擅长的事” 的设计哲学。

https://www.php.cn/faq/1987685.html

发表回复

Your email address will not be published. Required fields are marked *