Ngân hàng câu hỏi SQL
A. Cơ bản
1. SQL là gì? Khác NoSQL chỗ nào?
SQL = Structured Query Language, dùng cho RDBMS. Schema cố định, ACID transaction, JOIN mạnh. NoSQL (MongoDB, Redis...) thường schema-less, scale ngang dễ, không có JOIN/ACID đầy đủ.
2. DDL, DML, DCL, TCL là gì?
- DDL (Define):
CREATE,ALTER,DROP,TRUNCATE. - DML (Manipulate):
SELECT,INSERT,UPDATE,DELETE. - DCL (Control):
GRANT,REVOKE. - TCL (Transaction):
BEGIN,COMMIT,ROLLBACK.
3. Primary Key vs Unique Key?
| Primary Key | Unique Key | |
|---|---|---|
| NULL | ❌ Không cho | ✅ Cho 1 NULL (SQL Server) |
| Số lượng / bảng | 1 | Nhiều |
| Default index | Clustered | Non-clustered |
4. Foreign Key để làm gì?
Tham chiếu PK của bảng khác — đảm bảo referential integrity. Tránh con (orphan) không có cha.
5. CHAR(10) vs VARCHAR(10) vs NVARCHAR(10)?
CHAR(10): cố định 10 byte, pad bằng space.VARCHAR(10): biến đổi tới 10 byte, 1 byte/ký tự ASCII.NVARCHAR(10): Unicode (UTF-16), 2 byte/ký tự — dùng cho tiếng Việt, Nhật, Trung.
6. NULL vs 0 vs '' khác gì?
NULL= không xác định, không bằng bất cứ thứ gì kể cảNULL. So sánh dùngIS NULL.0= số 0 cụ thể.''= chuỗi rỗng, có giá trị.
B. JOIN
7. Các loại JOIN trong SQL?
INNER JOIN: chỉ rows match cả 2.LEFT JOIN: tất cả rows trái + match phải.RIGHT JOIN: ngược lại.FULL OUTER JOIN: tất cả rows hai bên.CROSS JOIN: tích Descartes.SELF JOIN: join với chính nó.
8. Phân biệt JOIN bằng hình Venn?
INNER: phần giao 2 hình.LEFT: hình trái (gồm cả phần giao).RIGHT: hình phải.FULL: cả 2 hình.LEFT EXCLUSIVE: hình trái trừ phần giao (LEFT JOIN ... WHERE right.PK IS NULL).
9. Câu hỏi: tìm khách hàng chưa từng đặt đơn?
SELECT c.*
FROM Customers c
LEFT JOIN Orders o ON o.CustomerId = c.Id
WHERE o.Id IS NULL;
Hoặc dùng NOT EXISTS:
SELECT c.* FROM Customers c
WHERE NOT EXISTS (SELECT 1 FROM Orders o WHERE o.CustomerId = c.Id);
10. Bẫy LEFT JOIN với WHERE?
Điều kiện trên bảng phải đặt vào ON, không phải WHERE, để giữ NULL.
-- ❌ Bị biến thành INNER JOIN
LEFT JOIN Orders o ON o.CustomerId = c.Id
WHERE o.Status = 'Paid';
-- ✅ Đúng
LEFT JOIN Orders o ON o.CustomerId = c.Id AND o.Status = 'Paid';
11. Cross JOIN dùng làm gì?
Tạo tất cả các cặp — ví dụ tạo matrix Date × Product để fill báo cáo có cả ngày không bán.
12. SELF JOIN — ví dụ thực tế?
Bảng Employees(Id, Name, ManagerId) — tìm tên manager của mỗi nhân viên:
SELECT e.Name AS Employee, m.Name AS Manager
FROM Employees e
LEFT JOIN Employees m ON m.Id = e.ManagerId;
C. INDEX
13. INDEX là gì? Hoạt động ra sao?
Cấu trúc dữ liệu phụ (B-tree) giúp tìm row nhanh. Trade-off: tốn space + slow ghi để nhanh đọc.
14. Clustered index vs Non-clustered index?
- Clustered: sắp xếp vật lý dữ liệu theo key. Mỗi bảng 1 clustered. PK mặc định clustered.
- Non-clustered: cấu trúc B-tree riêng trỏ về row gốc. Nhiều index/bảng.
15. Composite index — thứ tự cột quan trọng ra sao?
INDEX(A, B, C) chỉ dùng được nếu query lọc theo prefix bên trái: A, hoặc A+B, hoặc A+B+C. Lọc theo B mình nó → không dùng được index này.
16. Khi nào KHÔNG nên tạo INDEX?
- Bảng nhỏ (vài nghìn rows).
- Cột thay đổi rất thường xuyên.
- Cột có selectivity thấp (Gender, Status với vài giá trị).
- Bảng heavy INSERT, ít SELECT.
17. Query non-sargable là gì?
Query mà engine không dùng được index dù có. Vì áp hàm/biểu thức lên cột:
-- ❌ Non-sargable
WHERE YEAR(OrderDate) = 2026
WHERE UPPER(Email) = 'A@B.COM'
WHERE Total + Tax > 100
-- ✅ Sargable
WHERE OrderDate >= '2026-01-01' AND OrderDate < '2027-01-01'
18. Cover index (covering index)?
Index bao luôn các cột query cần — engine không cần seek về bảng gốc.
CREATE INDEX IX_Orders_Cover ON Orders(CustomerId)
INCLUDE (Total, OrderDate);
-- SELECT Total, OrderDate FROM Orders WHERE CustomerId = ?
-- → query chạy hoàn toàn trong index
D. Aggregate, Window function, Subquery
19. WHERE vs HAVING khác gì?
WHERElọc rows trước GROUP BY.HAVINGlọc group sau GROUP BY (dùng được hàm aggregate).
SELECT City, COUNT(*) cnt
FROM Customers
WHERE Status = 'Active' -- pre-group filter
GROUP BY City
HAVING COUNT(*) > 10; -- post-group filter
20. Các hàm aggregate phổ biến?
COUNT(*), COUNT(DISTINCT col), SUM, AVG, MIN, MAX. Bỏ qua NULL trừ COUNT(*).
21. ROW_NUMBER(), RANK(), DENSE_RANK() khác gì?
Có 3 row giá trị 100, 100, 90:
ROW_NUMBER: 1, 2, 3RANK: 1, 1, 3 (skip)DENSE_RANK: 1, 1, 2 (no skip)
22. Tìm lương cao thứ 2?
-- Cách 1
SELECT MAX(Salary) FROM Employees
WHERE Salary < (SELECT MAX(Salary) FROM Employees);
-- Cách 2 (general top-N)
SELECT Salary FROM (
SELECT Salary, DENSE_RANK() OVER (ORDER BY Salary DESC) rk
FROM Employees
) x WHERE rk = 2;
23. Lấy đơn hàng mới nhất của mỗi khách?
SELECT *
FROM (
SELECT o.*, ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY OrderDate DESC) rn
FROM Orders o
) x WHERE rn = 1;
24. Tìm email trùng?
SELECT Email, COUNT(*) cnt
FROM Users
GROUP BY Email
HAVING COUNT(*) > 1;
25. Đếm Paid vs Pending mỗi tháng?
SELECT FORMAT(OrderDate, 'yyyy-MM') AS Month,
SUM(CASE WHEN Status='Paid' THEN 1 ELSE 0 END) AS Paid,
SUM(CASE WHEN Status='Pending' THEN 1 ELSE 0 END) AS Pending
FROM Orders
GROUP BY FORMAT(OrderDate, 'yyyy-MM');
26. EXISTS vs IN — cái nào nhanh hơn?
IN: tốt với danh sách giá trị nhỏ.EXISTS: tốt với subquery, dừng ngay khi tìm thấy match đầu tiên.- Với subquery lớn → ưu tiên
EXISTS.
E. Normalization
27. 1NF, 2NF, 3NF — định nghĩa?
- 1NF: Mỗi ô atomic (không list, không nested).
- 2NF: 1NF + không có partial dependency vào PK composite.
- 3NF: 2NF + không có transitive dependency (cột non-key phụ thuộc cột non-key khác).
28. BCNF khác 3NF chỗ nào?
Boyce-Codd: chặt hơn 3NF — mọi determinant phải là superkey. Khắc phục trường hợp 3NF chưa xử được khi key composite phụ thuộc lẫn nhau.
29. Khi nào nên de-normalize?
- Báo cáo / OLAP / data warehouse.
- Bảng hot, JOIN nhiều, đọc nhiều hơn ghi.
- Chấp nhận lặp dữ liệu để giảm JOIN cost.
30. Ưu/nhược điểm của normalization?
✅ Tránh dữ liệu trùng, dễ maintain, integrity. ❌ JOIN nhiều → chậm hơn cho query phức tạp.
F. Transaction & ACID
31. ACID là gì?
- Atomicity: tất cả hoặc không.
- Consistency: DB trước-sau transaction vẫn hợp lệ.
- Isolation: transaction không thấy nháp của transaction khác.
- Durability: commit xong, dữ liệu bền (kể cả crash).
32. Dirty read, Non-repeatable read, Phantom read?
- Dirty read: đọc data của transaction chưa commit.
- Non-repeatable read: đọc 1 row 2 lần thấy khác (do transaction khác UPDATE).
- Phantom read: query lại thấy thêm row mới (do transaction khác INSERT).
33. 4 Isolation Level chuẩn?
| Level | Dirty | Non-rep | Phantom |
|---|---|---|---|
| Read Uncommitted | ✅ | ✅ | ✅ |
| Read Committed (default) | ❌ | ✅ | ✅ |
| Repeatable Read | ❌ | ❌ | ✅ |
| Serializable | ❌ | ❌ | ❌ |
34. Deadlock là gì? Tránh thế nào?
2+ transaction giữ lock và chờ nhau → bế tắc. Tránh:
- Luôn lock theo cùng thứ tự.
- Giữ transaction ngắn.
- Dùng isolation thấp hơn nếu được.
- SQL Server tự pick "deadlock victim" và rollback.
G. DELETE / TRUNCATE / DROP
35. DELETE vs TRUNCATE vs DROP?
| DELETE | TRUNCATE | DROP | |
|---|---|---|---|
| Loại | DML | DDL | DDL |
| WHERE | ✅ | ❌ | ❌ |
| Reset IDENTITY | ❌ | ✅ | N/A |
| Trigger | ✅ chạy | ❌ | N/A |
| Tốc độ | Chậm (log mỗi row) | Nhanh (deallocate page) | Nhanh nhất |
| Bảng còn sau khi chạy | ✅ | ✅ | ❌ (xoá luôn) |
| Rollback trong transaction | ✅ | ✅ (SQL Server) | ✅ |
H. View, SP, Function, Trigger
36. View là gì? Khi nào dùng?
Query lưu sẵn, dùng như table. Dùng để:
- Abstract query phức tạp.
- Phân quyền (expose 1 phần bảng).
- Indexed view: cải thiện performance (SQL Server).
37. Stored Procedure vs Function?
- Stored Procedure: thực thi logic, có thể trả nhiều resultset, INSERT/UPDATE/DELETE, gọi bằng
EXEC. - Function: trả về 1 giá trị scalar hoặc table, dùng được trong SELECT, không SIDE-EFFECT.
38. Trigger là gì? Loại?
Code chạy tự động khi event trên bảng:
- AFTER trigger: sau INSERT/UPDATE/DELETE.
- INSTEAD OF trigger: thay thế event (thường cho View).
Nên hạn chế — debug khó, dễ tạo logic ẩn.
I. Query tối ưu
39. Vì sao tránh SELECT *?
- Lấy thừa cột → tốn băng thông, RAM.
- Phá covering index (engine phải seek về bảng).
- Khi bảng thêm cột mới, query thay đổi hành vi bất ngờ.
40. UNION vs UNION ALL — cái nào nhanh hơn?
UNION: loại trùng (giống SELECT DISTINCT). Tốn extra cost.UNION ALL: gộp thẳng, nhanh hơn. Dùng khi biết chắc không trùng.
41. Pagination — cách hiệu quả?
SQL Server 2012+:
SELECT * FROM Orders
ORDER BY Id
OFFSET 100 ROWS FETCH NEXT 20 ROWS ONLY;
Với bảng cực lớn → keyset pagination hiệu quả hơn:
SELECT TOP 20 * FROM Orders
WHERE Id > @lastId
ORDER BY Id;
42. Cách điều tra query chậm?
- Bật Actual Execution Plan trong SSMS.
- Tìm operator có cost cao (Table Scan, Key Lookup nhiều).
- Check thiếu index — SSMS gợi ý.
- Kiểm tra statistics có outdated không (
UPDATE STATISTICS). - Xem có parameter sniffing không.
43. CTE là gì?
Common Table Expression — query tạm tên có thể tham chiếu trong cùng câu:
WITH TopCustomers AS (
SELECT TOP 10 CustomerId, SUM(Total) total
FROM Orders GROUP BY CustomerId
ORDER BY total DESC
)
SELECT c.* FROM Customers c
JOIN TopCustomers t ON t.CustomerId = c.Id;
Hỗ trợ recursive (tree, graph).
44. MERGE statement?
Upsert atomic — INSERT nếu chưa có, UPDATE nếu đã có, DELETE nếu không match.
MERGE INTO Target t
USING Source s ON s.Id = t.Id
WHEN MATCHED THEN UPDATE SET t.Name = s.Name
WHEN NOT MATCHED THEN INSERT (Id, Name) VALUES (s.Id, s.Name);
➡️ Tiếp theo: Ngân hàng câu hỏi ASP.NET Core & EF Core