
本文介绍在 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 合并:
$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 “让数据库做它擅长的事” 的设计哲学。
