需求:從文章表 Article(id, category, insert_date) 中,查詢每個分類下最新的前 N 篇文章。本文提供經典的「自連接 + 計數」單條 SQL 寫法,並解析其執行原理、適用場景與優化方向。
SELECT A1.*
FROM Article AS A1
INNER JOIN (
SELECT A.category, A.insert_date
FROM Article AS A
LEFT JOIN Article AS B
ON A.category = B.category
AND A.insert_date <= B.insert_date
GROUP BY A.category, A.insert_date
HAVING COUNT(B.insert_date) <= @N
) AS B1
ON A1.category = B1.category
AND A1.insert_date = B1.insert_date
ORDER BY A1.category, A1.insert_date DESC;
@N 為所需前 N 條(如 7)。
A LEFT JOIN B ON A.cat=B.cat AND A.dt <= B.dt,對於 A 表的每一行,B 表匹配「同分類且日期不早於它」的所有行。COUNT(B.insert_date) 等於「比當前行晚或同時間的行數」,即倒序排名。COUNT(...) <= N 保留排名前 N 的行(含並列同時間行)。category, insert_date,外層 JOIN 回主表取 *,避免內層攜帶大字段導致臨時表膨脹。SELECT A1.*
FROM prcKx AS A1
INNER JOIN (
SELECT A.superId, A.prcid
FROM prcKx AS A
LEFT JOIN prcKx AS B
ON A.superId = B.superId
AND A.prcid <= B.prcid
GROUP BY A.superId, A.prcid
HAVING COUNT(B.prcid) <= 7
) AS B1
ON A1.superId = B1.superId
AND A1.prcid = B1.prcid
ORDER BY A1.superId, A1.prcid;
| 維度 | 特徵 | 優化方向 |
|---|---|---|
| 時間複雜度 | O(M²) 級(M=分類內行數) | 數據量大時極慢,百萬級分類慎用 |
| 索引需求 | 需 (category, insert_date) 複合索引 | 覆蓋索引加速內層掃描與計數 |
| 並列時間處理 | 同時間視為同一排名,可能返回 > N 行 | 若需嚴格 N 行,改用 ROW_NUMBER() (8.0+) |
WITH ranked AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY insert_date DESC) AS rn
FROM Article
)
SELECT * FROM ranked WHERE rn <= @N;
(category, insert_date DESC) 支援 Index Skip Scan 加速分區排序。「自連接計數」是 MySQL 5.7 及之前獲取分組 Top-N 的經典寫法,邏輯清晰、兼容性好,但不適合大分類大數據量。MySQL 8.0 應優先使用 ROW_NUMBER() 窗口函數;舊版本若數據量大,建議應用層分批查詢或引入 Redis 預計算緩存。