Laravel 11 查询近30天每日数据需用Eloquent日期范围+分组统计+Carbon处理,核心步骤包括:1. 基础查询按日分组并补全缺失日期;2. 用CTE优化生成连续日期;3. 封装作用域复用;4. 注意时区、索引、空数据和软删除。

<p>要查询 Laravel 11 中近 30 天的每日数据(如订单数、用户注册数等),核心是用 Eloquent 的日期范围 + 分组统计,配合 Carbon 处理时间。以下是一个完整、可直接运行的示例,以 <strong>用户注册数</strong> 为例:</p>
<H3>1. 基础模型查询(按日分组统计)</H3>
<p>假设你有 <code>User</code> 模型,<code>created_at</code> 是注册时间字段:</p>
<font color="#666">
<pre><code>// app/Http/Controllers/ReportController.php
use App\Models\User;
use Carbon\Carbon;
public function dailyRegistrations()
{
$thirtyDaysAgo = now()->subDays(29); // 从今天起往前推 29 天,共 30 天(含今天)
$data = User::whereBetween('created_at', [$thirtyDaysAgo, now()])
->selectRaw('DATE(created_at) as date')
->selectRaw('COUNT(*) as count')
->groupBy('date')
->orderBy('date')
->get();
// 补全缺失日期(如某天无注册,返回 0)
$fullRange = collect();
for ($i = 29; $i >= 0; $i--) {
$date = now()->subDays($i)->format('Y-m-d');
$item = $data->firstWhere('date', $date);
$fullRange->push([
'date' => $date,
'count' => $item ? (int)$item->count : 0,
]);
}
return response()->json($fullRange);
}</code></pre>
</font>
<H3>2. 使用数据库原生函数优化(推荐 MySQL/PostgreSQL)</H3>
<p>避免 PHP 循环补全,改用 SQL 生成连续日期(MySQL 8.0+ 或 PostgreSQL):</p>
<font color="#666">
<pre><code>// 仅适用于支持 CTE 的数据库(如 MySQL 8.0+)
$rawSql = "
WITH RECURSIVE dates AS (
SELECT ? AS date
UNION ALL
SELECT DATE_ADD(date, INTERVAL 1 DAY)
FROM dates
WHERE date < ?
)
SELECT d.date, COALESCE(u.count, 0) as count
FROM dates d
LEFT JOIN (
SELECT DATE(created_at) as date, COUNT(*) as count
FROM users
WHERE created_at >= ?
GROUP BY DATE(created_at)
) u ON d.date = u.date
ORDER BY d.date
";
$thirtyDaysAgo = now()->subDays(29)->format('Y-m-d');
$today = now()->format('Y-m-d');
$data = DB::select($rawSql, [$thirtyDaysAgo, $today, $thirtyDaysAgo]);
return response()->json($data);</code></pre>
</font>
<H3>3. 封装成复用的查询构建器方法</H3>
<p>在模型或 Repository 中封装,便于多处调用:</p>
<font color="#666">
<pre><code>// 在 User.php 模型中添加作用域
public function scopeForLast30Days($query)
{
return $query->whereBetween('created_at', [
now()->subDays(29),
now()
]);
}
// 调用时
$data = User::forLast30Days()
->selectRaw('DATE(created_at) as date, COUNT(*) as count')
->groupBy('date')
->orderBy('date')
->get()
->mapWithKeys(function ($item) {
return [$item->date => (int)$item->count];
})
->pipe(function ($map) {
$result = [];
for ($i = 29; $i >= 0; $i--) {
$date = now()->subDays($i)->format('Y-m-d');
$result[] = [
'date' => $date,
'count' => $map->get($date, 0)
];
}
return $result;
});</code></pre>
</font>
<H3>4. 注意事项与常见问题</H3>
<ul>
<li><strong>时区一致性</strong>:确保 PHP、MySQL 和应用配置(<code>config/app.php</code> 中 <code>'timezone' => 'Asia/Shanghai'</code>)一致,否则日期可能偏移</li>
<li><strong>索引优化</strong>:为 <code>created_at</code> 字段添加索引:<code>$table->index('created_at');</code></li>
<li><strong>空数据处理</strong>:Eloquent 默认不返回 0 值日期,必须手动补全(如示例中用循环或 CTE)</li>
<li><strong>软删除模型</strong>:若使用 <code>SoftDeletes</code>,需加 <code>withTrashed()</code> 或明确 <code>whereNull('deleted_at')</code></li>
</ul>