SQL cho Interview — Tuần cuối
Nhấn phím → để bắt đầu · T để xem mục lục
Chín tuần qua bạn đã build từng layer kỹ năng. Tuần này không thêm cú pháp mới — mà tích hợp chúng dưới áp lực thời gian thực.
| LeetCode (luyện một mình) | Mock interview (có người ra đề) | |
|---|---|---|
| Áp lực thời gian | Tùy ý — có thể tạm dừng | 45 phút cố định, có người nhìn |
| Đề bài | Đầy đủ schema, sample input/output | Đề thường mơ hồ — phải clarify |
| Phản hồi | Submit → đúng/sai | Interviewer follow-up về trade-off, optimization |
| Kỹ năng đo | Correctness | Correctness + clarity + communication + judgment |
| Sai một bước | Submit lại | Phải debug live, narrate |
Tuần này tập trung vào khoảng cách giữa LeetCode và interview thật.
Bạn: "Cảm ơn đề. Cho tôi confirm hiểu đúng — yêu cầu là [restate]. Trước khi bắt đầu code, tôi có vài câu hỏi:"
"1. Cột [X] có thể NULL không?"
"2. Bảng có khả năng có dòng trùng không?"
"3. Khi có ties (vd cùng salary), output mong đợi thế nào?"
"4. Output cần sort theo cột nào?"
Phase này có vẻ tốn thời gian, nhưng 5 phút clarifying tiết kiệm 20 phút debug. Skip phase này là sai lầm phổ biến nhất.
Bạn: "Đây có vẻ là pattern top-N per group. Tôi sẽ chia thành 3 bước:
1. Filter các đơn paid trong 2024 — CTE paid_2024.
2. Aggregate tổng / customer + rank trong country — CTE ranked.
3. Filter top 3 + tính % country — output cuối.
Tôi viết khung trước, sau đó điền chi tiết từng CTE. Hợp lý chứ?"
WITH
paid_2024 AS (
-- TODO: orders status='paid' và 2024
),
ranked AS (
-- TODO: SUM theo customer + ROW_NUMBER theo country
)
SELECT
-- TODO: top 3 mỗi country với pct
;
Khung trống với TODO comments — interviewer thấy bạn structure trước, code sau.
Bạn: "Tôi đang viết CTE thứ nhất. Filter status='paid' và order_date trong 2024 — nửa-mở để tránh fence-post error..."
Bạn: "OK CTE 1 xong. Test mental: 5 đơn input → 3 đơn paid 2024. Đúng. Sang CTE 2..."
Bạn: "Tôi đã hoàn thành. Để tôi đi qua edge cases nhanh:
1. Cột customer_id ở orders không có NULL theo schema, nên LEFT JOIN không tạo NULL phantom.
2. ROW_NUMBER cần tie-breaker — tôi đã thêm order_id DESC.
3. Bảng rỗng → CTE rỗng → output rỗng. OK.
Về performance: query benefit từ composite index (country, signup_date). Trên dữ liệu 100M dòng, có thể cần thêm partial index trên status='paid'."
Phase này phân biệt candidate junior với senior. Junior viết xong rồi dừng. Senior viết xong rồi chủ động đi qua edge cases và optimization.
Trong interview SQL, đáp số đúng là điều kiện cần, không phải đủ. Interviewer đánh giá:
Tất cả những thứ này chỉ visible qua narration. Code im lặng = interviewer không thấy gì cả. Hai candidate ra cùng đáp án nhưng một người narrate tốt thì sẽ pass, người kia có thể fail.
"Cảm ơn đề bài. Cho tôi confirm hiểu đúng — yêu cầu là [restate].
Trước khi code, tôi có 3 câu hỏi:
1. Cột X có thể NULL không?
2. Có khả năng có dòng trùng trong bảng Y không?
3. Khi có ties, output mong đợi như thế nào?"
"Đây có vẻ là pattern [X] vì có tín hiệu [Y].
Tôi sẽ chia thành N bước:
- Bước 1: ...
- Bước 2: ...
- Bước 3: ...
Tôi viết khung CTE trước, rồi điền chi tiết. Cách tiếp cận này hợp lý chứ?"
"Tôi đang viết CTE [tên]. Mục đích: [input → output].
Logic chính: [tóm tắt 1 câu]."
[Sau khi xong CTE]
"OK, CTE [tên] xong. Test mental: với input mẫu [...], kết quả là [...]. Sang CTE tiếp."
"Tôi đã hoàn thành. Để đi qua edge cases:
- NULL: [xử lý thế nào]
- Ties: [xử lý thế nào]
- Empty: [output gì]
Về performance: query benefit từ index [...]. Trên dữ liệu lớn,
alternative approach là [...] với trade-off [...]."
| Mistake | Cách tránh |
|---|---|
| Silent coding — gõ 5+ phút không nói | Set timer trong đầu; mỗi 1 phút có 1 câu narration |
| Mumbling / unclear | Nói câu hoàn chỉnh, không "ừm... à... thì..." |
| Over-talking — narrate mỗi keystroke | Narrate ở level "intent + result", không phải mỗi dòng |
| Defensive khi sai | "Tôi nghĩ tôi vừa sai bước này. Để tôi sửa..." — interviewer thích self-correction |
| Không hỏi clarifying | Luôn ít nhất 2-3 câu hỏi Phase 1 |
| Skip edge cases | Phase 4 dành riêng cho việc này — không bỏ qua |
| Argumentative khi bị challenge | Nghe → "Bạn đúng, tôi đã không nghĩ tới ..." → fix. Senior là người admit nhanh |
| Bỏ cuộc khi stuck | "Tôi đang stuck ở chỗ này. Cách tiếp cận tôi đã thử là [A]. Tôi đang nghĩ thử [B]." — interviewer có thể hint |
Trước khi submit, đi qua 7 câu hỏi NULL này như reflex:
Bạn: "Cột order_date kiểu DATE hay TIMESTAMP? Nếu TIMESTAMP, có timezone không? Yêu cầu 'tháng 3' theo timezone nào?"
Hỏi câu này là tín hiệu candidate có production experience. Junior thường giả định cột là DATE thuần.
Cho mỗi cohort theo tháng (theo signup_date trong năm 2024), tính:
Sắp xếp theo cohort_month. Round retention 2 chữ số.
Bài này khó vừa phải — Medium-Hard. Tích hợp date arithmetic, LEFT JOIN, conditional aggregation, CTE multi-step. Một interviewer có kinh nghiệm sẽ hỏi follow-up về: ties, NULL, performance, mở rộng thành weekly cohort.
Bạn: "Cảm ơn đề. Cho tôi confirm hiểu đúng: với mỗi cohort tháng signup trong 2024, tính cohort_size và 2 retention rate (Day-7 và Day-28). Đúng chứ?"
Interviewer: "Đúng."
Bạn: "Tôi có 4 câu hỏi:
1. Cột events.user_id luôn match một user trong users chứ? Hay có thể có orphan?"
Interviewer: "Luôn match — referential integrity được enforce."
Bạn: "2. 'Event trong 7 ngày từ signup' — bao gồm ngày signup không? Tức [signup, signup+7) hay (signup, signup+7]?"
Interviewer: "Bao gồm signup. Tức (signup_date, signup_date + 7 days]."
Bạn: "3. event_time là TIMESTAMP. Khi tính 'within 7 days', so với ngày hay datetime?"
Interviewer: "Tính theo datetime. signup_date được coi là 00:00 ngày đó."
Bạn: "4. User signup ngày 2024-12-30 thì rất ít có data Day-28 trong dataset. Vẫn phải xuất hiện chứ? Retention sẽ thấp hoặc 0%?"
Interviewer: "Hỏi tốt. Giả sử dataset đủ — bao gồm events đến hết 2025-01-31. Cohort 12/2024 vẫn tính được Day-28."
4 câu hỏi này lộ ra: orphan handling, half-open interval, datetime semantics, data completeness. Đây là tín hiệu strong.
Bạn: "Đây là pattern cohort retention đã rèn. Tôi sẽ chia thành 4 CTE:
1. cohort_users — user signup 2024 với cohort_month = đầu tháng signup.
2. user_retention — JOIN với events, đánh flag has_w1 và has_w4 cho mỗi user.
3. cohort_metrics — aggregate theo cohort_month: COUNT users + AVG flags.
4. Output cuối — round + format.
Tôi viết khung trước. Hợp lý chứ?"
WITH
cohort_users AS (
-- TODO: users 2024 + cohort_month (first day of month)
),
user_retention AS (
-- TODO: cho mỗi user, flag w1 và w4
-- LEFT JOIN events, dùng EXISTS-style aggregation
),
cohort_metrics AS (
-- TODO: GROUP BY cohort_month
-- COUNT(*) cohort_size, AVG(flag_w1), AVG(flag_w4)
)
SELECT
cohort_month,
cohort_size,
ROUND(100.0 * w1_rate, 2) AS week_1_retention,
ROUND(100.0 * w4_rate, 2) AS week_4_retention
FROM cohort_metrics
ORDER BY cohort_month;
WITH
cohort_users AS (
SELECT
user_id,
signup_date,
DATE_FORMAT(signup_date, '%Y-%m-01') AS cohort_month
-- PostgreSQL: DATE_TRUNC('month', signup_date)
FROM users
WHERE signup_date >= '2024-01-01'
AND signup_date < '2025-01-01'
),
user_retention AS (
SELECT
cu.user_id,
cu.cohort_month,
-- Flag = 1 nếu có ít nhất 1 event trong window
MAX(CASE
WHEN e.event_time > cu.signup_date
AND e.event_time <= cu.signup_date + INTERVAL 7 DAY
THEN 1 ELSE 0
END) AS has_w1,
MAX(CASE
WHEN e.event_time > cu.signup_date
AND e.event_time <= cu.signup_date + INTERVAL 28 DAY
THEN 1 ELSE 0
END) AS has_w4
FROM cohort_users cu
LEFT JOIN events e ON e.user_id = cu.user_id
GROUP BY cu.user_id, cu.cohort_month
),
cohort_metrics AS (
SELECT
cohort_month,
COUNT(*) AS cohort_size,
AVG(has_w1) AS w1_rate,
AVG(has_w4) AS w4_rate
FROM user_retention
GROUP BY cohort_month
)
SELECT
cohort_month,
cohort_size,
ROUND(100.0 * w1_rate, 2) AS week_1_retention,
ROUND(100.0 * w4_rate, 2) AS week_4_retention
FROM cohort_metrics
ORDER BY cohort_month;
~30 dòng cho bài Medium-Hard. CTE đặt tên rõ — đọc top-down dễ. Mỗi bước test riêng được.
Bạn: "Đi qua edge cases:
1. User signup không có event: LEFT JOIN cho NULL events. MAX(CASE...) trả 0 — đúng (retention = 0%).
2. NULL event_time: schema NOT NULL nên không xảy ra.
3. Multiple events per user: MAX(CASE...) đảm bảo 0/1 flag, không over-count.
4. Cohort tháng 12: vẫn tính Day-28 vì interviewer confirm dataset đủ.
5. Empty input: query trả 0 dòng — đúng behavior.
Về performance:
- Index quan trọng: events(user_id, event_time) cho LEFT JOIN.
- users(signup_date) cho WHERE filter.
- Trên dữ liệu lớn (100M events), CTE user_retention có thể nặng vì group theo user_id. Alternative: dùng EXISTS thay vì MAX(CASE) — early exit nhanh hơn:"
-- Alternative cho user_retention với EXISTS
SELECT
cu.user_id, cu.cohort_month,
EXISTS (SELECT 1 FROM events e WHERE e.user_id = cu.user_id
AND e.event_time > cu.signup_date
AND e.event_time <= cu.signup_date + INTERVAL 7 DAY) AS has_w1,
-- tương tự cho has_w4
FROM cohort_users cu;
Bạn: "Mở rộng có thể có: Day-N retention với N là parameter, weekly cohort thay vì monthly, group thêm theo country. Cấu trúc CTE giúp adapt dễ — chỉ thay CTE đầu hoặc thêm cột country vào group keys."
Mỗi scenario dưới đây là độ khó Medium-Hard. Tự làm 45 phút mỗi cái với timer, ghi âm narration, đánh giá lại.
Cho orders(customer_id, total, order_date). Tìm customer ở top 10% spending mỗi tháng (theo tổng amount), kèm rank trong top 10%, tổng tháng đó của họ, và % của họ trên top 10% group.
Pattern: Percentile + Top-N within group + Cumulative metric.
Cho events(user_id, event_time). Định nghĩa session: events của cùng user cách nhau ≤ 30 phút thuộc cùng session. Tính cho mỗi user: số session, độ dài session trung bình, session dài nhất.
Pattern: Gaps and Islands theo time + window function.
Cho events(user_id, event_type, event_time) với event_type ∈ {'view', 'add_to_cart', 'checkout', 'purchase'}. Tính tỷ lệ conversion mỗi bước (view→cart, cart→checkout, checkout→purchase) trong 7 ngày qua. Mỗi user phải làm bước trước trong vòng 24h trước khi bước sau được tính.
Pattern: Self-join + date arithmetic + conditional aggregation.
Cho employees(id, name, manager_id, salary). Tính cho mỗi manager: số subordinate (trực tiếp + gián tiếp), tổng salary tất cả subordinate, salary trung bình. Thêm cột "depth" = độ sâu cây dưới manager đó.
Pattern: Recursive CTE + aggregation.
Cho logins(user_id, login_date). Cho mỗi ngày trong 30 ngày qua, tính: DAU (số user unique login hôm đó), WAU rolling (số user unique login trong 7 ngày qua), DAU/WAU ratio (stickiness).
Pattern: Cumulative + window with frame + cohort-style aggregation.
Sau mỗi scenario tự làm, đánh giá theo 4 chiều (mỗi chiều 1-5 điểm):
| Chiều | Câu hỏi tự đánh giá |
|---|---|
| Correctness | Code chạy đúng? Edge cases được xử lý? Mental test pass? |
| Code quality | CTE đặt tên rõ? Code đọc top-down dễ? Tránh anti-pattern? |
| Communication | Narrate liên tục? Phase 1 clarifying đầy đủ? Phase 4 đề cập optimization? |
| Time management | Phân chia thời gian hợp lý 4 phase? Kết thúc trong 45 phút? |
| Tuần | Focus | Daily routine |
|---|---|---|
| T-4 (foundation refresh) | Easy/Medium volume — refresh kỹ năng, build speed | 10 LeetCode/ngày, mix Easy 70% + Medium 30%. Time mỗi bài. Note pattern. |
| T-3 (pattern depth) | Medium/Hard targeted — luyện 7 pattern (tuần 8 cheatsheet) | 5 problems/ngày, 1 pattern/ngày. Mỗi bài viết 2-3 dòng note pattern. |
| T-2 (mock practice) | 3 mock interview/tuần với peer hoặc tự làm (45 min, ghi âm) | 3 mock + 5 LeetCode/ngày để maintain. Sau mỗi mock, self-eval 4 chiều. |
| T-1 (taper + review) | Giảm volume, tăng review. Đọc lại slide tuần 8 (patterns) và tuần 9 (performance). | 2 mock cuối tuần. 3 LeetCode/ngày. Đọc lại note patterns. Nghỉ ngơi 1 ngày trước phỏng vấn. |
Câu hỏi cho thấy bạn quan tâm production reality, không phải chỉ technical skill.
| Tuần | Chủ đề | Kỹ năng signature |
|---|---|---|
| 1 | Mô hình quan hệ | Đại số quan hệ, SELECT cơ bản |
| 2 | Lọc nâng cao và NULL | 3VL, CASE WHEN, string/date functions |
| 3 | Aggregation | GROUP BY, HAVING, conditional aggregation |
| 4 | JOIN sâu | INNER/OUTER/SELF/CROSS, anti-join |
| 5 | Subquery và Set Ops | Correlated, EXISTS, UNION/INTERSECT/EXCEPT |
| 6 | Window Functions | ROW_NUMBER/RANK, LAG/LEAD, frame clause |
| 7 | CTE và Recursive | WITH, multi-step decomposition, hierarchy traversal |
| 8 | Pattern Interview | 7 patterns + recognition reflex |
| 9 | Performance & Phương ngữ | EXPLAIN, index, anti-patterns, dialect awareness |
| 10 | Mock & Capstone | 4-phase process, communication, edge case checklist |
Bạn đã build từng layer — từ cú pháp cơ bản đến refactor query Hard, từ pattern recognition đến performance optimization, từ code im lặng đến think-aloud có cấu trúc.
Pattern recognition is muscle memory.
Communication is rehearsable.
Edge cases are checklists.
Mỗi cái đều luyện được. Phỏng vấn SQL không phải trắc nghiệm IQ — nó là kỹ năng có thể build.
Bạn đã build trong 10 tuần. Giờ là lúc áp dụng.
SQL cho Interview — Lộ trình 10 tuần · Hết
Nhấn T hoặc Escape để đóng