
本文介绍一种高效、无重复行的 sql 方案,使用两次 left join 配合子查询,仅用一条 select 语句即可为每个餐厅关联其最新一条评论(若存在),适用于首页列表渲染等场景。
本文介绍一种高效、无重复行的 sql 方案,使用两次 left join 配合子查询,仅用一条 select 语句即可为每个餐厅关联其最新一条评论(若存在),适用于首页列表渲染等场景。
在构建餐厅展示页(如首页)时,常需「列出所有餐厅,并附带其最新一条用户评论」——看似简单,但若直接 LEFT JOIN reviews,会因一个餐厅有多条评论而产生重复餐厅行;若用子查询在 SELECT 中嵌套 LIMIT 1,MySQL 又不支持该语法(尤其在非派生表上下文中)。理想的解法是:先聚合出每个餐厅的最新评论时间,再精准回连具体评论记录。
以下为推荐的单查询解决方案:
SELECT
r.id AS restaurant_id,
r.name,
r.cuisine,
r.address,
r.image_file,
rev.id AS review_id,
rev.header,
rev.content,
rev.posted_at,
u.username AS reviewer_name,
u.email AS reviewer_email
FROM restaurants r
LEFT JOIN (
SELECT
restaurant_id AS restId,
MAX(posted_at) AS lastTime
FROM reviews
GROUP BY restaurant_id
) AS last_reviews ON last_reviews.restId = r.id
LEFT JOIN reviews rev
ON rev.restaurant_id = r.id
AND rev.posted_at = last_reviews.lastTime
LEFT JOIN users u ON u.id = rev.user_id;
✅ 关键设计说明:
- 第一层
LEFT JOIN的子查询(last_reviews)按restaurant_id分组,计算每个餐厅的MAX(posted_at),确保每家餐厅至多一行; - 第二层
LEFT JOIN reviews同时匹配restaurant_id和posted_at,精准定位到该时间点的那条评论(注意:若同一餐厅在完全相同时间发布多条评论,仍可能重复;此时建议改用id+ 窗口函数方案,见下方提示); - 最后
LEFT JOIN users补全评论者信息,保持数据完整性。
⚠️ 注意事项与优化建议:
- 若存在
posted_at完全相同的多条评论(如批量导入或毫秒级时间精度不足),上述方案可能返回多行。更健壮的做法是使用窗口函数(MySQL 8.0+):SELECT * FROM ( SELECT r.*, rev.*, u.username, u.email, ROW_NUMBER() OVER (PARTITION BY r.id ORDER BY rev.posted_at DESC, rev.id DESC) AS rn FROM restaurants r LEFT JOIN reviews rev ON rev.restaurant_id = r.id LEFT JOIN users u ON u.id = rev.user_id ) ranked WHERE rn = 1; - 确保
reviews(restaurant_id, posted_at)上建立联合索引,大幅提升子查询和连接性能:CREATE INDEX idx_rev_rest_time ON reviews(restaurant_id, posted_at);
- 在 PHP 中可直接遍历结果集(
foreach ($stmt->fetchAll() as $row)),无需额外去重或嵌套循环——每行即代表一家餐厅及其最新评论(或 NULL 值表示无评论)。
该方案兼顾兼容性(支持 MySQL 5.7+)、可读性与执行效率,是生产环境中推荐的标准实践。











