Lấy lương cao thứ hai: câu hỏi phỏng vấn có bốn cách sai

Nâng cao 5 phút đọc Cập nhật 30/07/2026 UPA AI Partner

LIMIT 1 OFFSET 1 trông rất hợp lý. Nó sai ngay khi có hai người cùng lương — và luôn có.

Bài toán

Tìm mức lương cao thứ hai. Đây là LeetCode 176 — câu SQL được hỏi nhiều nhất trong lịch sử phỏng vấn.

LUONGluong5000500040003000BA HÀM XẾP HẠNGluongROW_NUMRANKDENSE_RANK5000111500021140003323000443OVER()

Cách sai phổ biến

SELECT luong FROM nhan_vien ORDER BY luong DESC LIMIT 1 OFFSET 1;

Nếu hai người cùng lương 5000, câu này trả về 5000 lần nữa — nhưng đề hỏi mức lương cao thứ hai, tức 4000.

Cách đúng: DENSE_RANK

WITH xep_hang AS (
  SELECT luong, DENSE_RANK() OVER (ORDER BY luong DESC) AS hang
  FROM nhan_vien
)
SELECT DISTINCT luong FROM xep_hang WHERE hang = 2;

Phân biệt ba hàm — đây là phần interviewer thật sự chấm:

  • ROW_NUMBER — 1, 2, 3, 4. Luôn duy nhất, phá hoà ngẫu nhiên.
  • RANK — 1, 1, 3, 4. Đồng hạng rồi nhảy số.
  • DENSE_RANK — 1, 1, 2, 3. Đồng hạng, không nhảy số.

“Mức lương cao thứ N” luôn là DENSE_RANK.

Câu hỏi phụ: nếu không có hạng thứ N?

Truy vấn trên trả về không dòng nào. Đề bài LeetCode yêu cầu trả về NULL. Bọc thêm một tầng:

SELECT (
  SELECT DISTINCT luong FROM xep_hang WHERE hang = 2
) AS luong_cao_thu_hai;

Subquery vô hướng không có dòng nào sẽ trả về NULL thay vì rỗng. Đây là chi tiết khiến rất nhiều người trượt bài 176.

Lỗi thường gặp

1. Lọc hàm window ngay trong WHERE

-- Lỗi cú pháp
WHERE DENSE_RANK() OVER (ORDER BY luong DESC) = 2

Hàm window chạy sau WHERE. Bắt buộc bọc trong CTE hoặc subquery.

2. Quên DISTINCT

Hai người cùng hạng 2 sẽ ra hai dòng giống nhau.

3. Không xử lý NULL trong ORDER BY

PostgreSQL mặc định xếp NULL lên đầu khi DESC. Thêm ORDER BY luong DESC NULLS LAST nếu cột có thể trống.

Luyện tập

  • LeetCode 176 — Second Highest Salary
  • LeetCode 177 — Nth Highest Salary
  • LeetCode 178 — Rank Scores
  • LeetCode 184 — Department Highest Salary
  • HackerRank — Top Competitors

Tóm lại

  • LIMIT … OFFSET sai ngay khi có giá trị bằng nhau.
  • “Mức cao thứ N” luôn dùng DENSE_RANK.
  • RANK nhảy số, DENSE_RANK thì không.
  • Bọc trong CTE trước khi lọc theo hạng.
  • Subquery vô hướng trả về NULL khi không có dòng nào.

Bạn vừa học xong lấy giá trị cao thứ N

Sẵn sàng luyện tập chưa?

Làm bài tập lấy giá trị cao thứ N trên dữ liệu thật, chấm điểm ngay khi bạn bấm chạy.

Bắt đầu luyện tập →