
本文介绍如何将 laravel 中按钻石、vip 和普通等级分类的三组广告查询合并为单个 eloquent 查询,并支持原生分页,避免多次数据库请求与内存拼接,提升性能与可维护性。
本文介绍如何将 laravel 中按钻石、vip 和普通等级分类的三组广告查询合并为单个 eloquent 查询,并支持原生分页,避免多次数据库请求与内存拼接,提升性能与可维护性。
在 Laravel 开发中,常见业务场景需要根据广告等级(如 ad_diamond=1、ad_vip=1 且非钻石、其余为普通)分别获取数据,并统一展示与分页。若按原始方式分别执行三次 get() 查询(如 diamond_ads、vip_ads、default_ads),不仅造成冗余 SQL 请求,更无法直接使用 Laravel 的 paginate()——因为 get() 返回的是集合(Collection),后续手动合并再分页会丢失数据库层的分页优势(如 OFFSET/LIMIT 优化、游标稳定性、大数据量下的内存安全)。
✅ 正确解法是在数据库层面完成条件聚合,用一个带嵌套 WHERE 的 Eloquent 查询覆盖全部逻辑,并直接调用 paginate():
$data['ads'] = AdModel::where('status', 1)
->where('moderation', 0)
->where('category_id', $this_category_id)
->where(function ($query) {
// 优先级逻辑:钻石广告 > VIP广告 > 普通广告(互斥)
$query->where('ad_diamond', 1)
->orWhere(function ($q) {
$q->where('ad_vip', 1)
->where('ad_diamond', 0);
})
->orWhere(function ($q) {
$q->where('ad_vip', 0)
->where('ad_diamond', 0);
});
})
->orderByDesc('date_added')
->paginate(20); // 每页20条,支持 ?page=2 自动解析? 关键说明:
-
where(...)中的闭包确保三个条件组为 OR 关系,且每组内部为 AND,精准对应原始三类筛选逻辑; -
paginate()直接返回LengthAwarePaginator实例,模板中可无缝使用{{ $ads->links() }}渲染分页控件; - 排序字段
date_added在最外层统一指定,避免各子查询排序冲突; - 数据库原生分页显著降低内存占用,尤其当某类广告达数千条时,
get()->merge()->forPage()方案极易 OOM。
⚠️ 注意事项:
- 若需在视图中区分广告类型(例如添加「? 钻石」标签),请在 Blade 中直接判断:
@foreach($ads as $ad) <div class="ad-item"> @if($ad->ad_diamond) ? Diamond @elseif($ad->ad_vip) ⭐ VIP @else ? Standard @endif <span>{{ $ad->title }}</span> </div> @endforeach {{ $ads->links() }} - 确保
status、moderation、category_id、ad_diamond、ad_vip字段均已添加数据库索引(尤其是组合查询高频字段),推荐复合索引:CREATE INDEX idx_ad_status_mod_cat_diamond_vip ON ads (status, moderation, category_id, ad_diamond, ad_vip);
- 如需严格保持“钻石 > VIP > 普通”的前端显示顺序(而非仅按
date_added全局排序),可在查询中引入权重字段:->selectRaw('*, CASE WHEN ad_diamond = 1 THEN 3 WHEN ad_vip = 1 THEN 2 ELSE 1 END as priority') ->orderByRaw('priority DESC, date_added DESC')
通过此方案,你用一次查询替代三次,获得真正高效的分页体验,代码更简洁,扩展性更强——未来新增 ad_premium 类型时,只需扩展闭包内的 orWhere 子句即可。


















