Giới thiệu
Khi chuẩn bị cho các buổi phỏng vấn SQL, một trong những câu hỏi phổ biến nhất xoay quanh sự khác biệt giữa WHERE và HAVING. Mặc dù chúng có vẻ tương tự ban đầu, nhưng chúng phục vụ các mục đích khác nhau trong việc lọc dữ liệu. Việc hiểu khi nào và tại sao sử dụng mỗi câu lệnh là điều quan trọng để viết các truy vấn SQL chính xác và hiệu quả.
Sự khác biệt cơ bản
WHERElọc các hàng đơn lẻ trước khi bất kỳ việc nhóm nào xảy ra. Bạn không thể sử dụng các hàm tổng hợp nhưSUM,AVG, hoặcCOUNTtrong mệnh đềWHERE.HAVINGlọc các nhóm hàng sau khi tổng hợp. Đây là nơi bạn sử dụng các hàm tổng hợp để lọc dữ liệu đã được nhóm.
Ví dụ hình ảnh
Hãy tưởng tượng dữ liệu của bạn như một chồng giấy:
WHERE= “Tôi chỉ muốn những giấy tờ mà nhân viên kiếm được hơn \$60k.” ✅ Lọc hàng đơn lẻGROUP BY= “Bây giờ tôi sẽ xếp chồng giấy theo phòng ban.”HAVING= “Tôi chỉ muốn những chồng giấy mà tổng hoặc trung bình lương vượt quá \$60k.” ✅ Lọc nhóm
Thiết lập cơ sở dữ liệu ví dụ
Hãy làm việc với một ví dụ thực tế sử dụng cơ sở dữ liệu công ty đơn giản:
sql
CREATE DATABASE CompanyDB;
GO
USE CompanyDB;
GO
CREATE TABLE Employees (
EmployeeId INT IDENTITY(1,1) PRIMARY KEY,
FirstName NVARCHAR(50) NOT NULL,
LastName NVARCHAR(50) NOT NULL,
Email NVARCHAR(100) UNIQUE NOT NULL,
HireDate DATE NOT NULL,
DepartmentId INT,
Salary DECIMAL(10,2) NOT NULL
);
GO
CREATE TABLE Departments (
DepartmentId INT IDENTITY(1,1) PRIMARY KEY,
DepartmentName NVARCHAR(50) NOT NULL,
ManagerId INT NULL
);
GO
CREATE TABLE Projects (
ProjectId INT IDENTITY(1,1) PRIMARY KEY,
ProjectName NVARCHAR(100) NOT NULL,
StartDate DATE NOT NULL,
EndDate DATE NULL,
Budget DECIMAL(12,2) NOT NULL
);
GO
CREATE TABLE EmployeeProjects (
EmployeeId INT NOT NULL,
ProjectId INT NOT NULL,
AssignedDate DATE NOT NULL,
Role NVARCHAR(50) NULL,
PRIMARY KEY (EmployeeId, ProjectId),
FOREIGN KEY (EmployeeId) REFERENCES Employees(EmployeeId),
FOREIGN KEY (ProjectId) REFERENCES Projects(ProjectId)
);
GO
CREATE TABLE Cities (
CityId INT IDENTITY(1,1) PRIMARY KEY,
CityName NVARCHAR(100) NOT NULL,
Country NVARCHAR(100) NOT NULL
);
GO
ALTER TABLE Employees
ADD CityId INT NULL,
FOREIGN KEY (CityId) REFERENCES Cities(CityId);
GO
INSERT INTO Departments (DepartmentName) VALUES
('Engineering'), ('HR'), ('Marketing'), ('Finance'), ('Operations');
INSERT INTO Cities (CityName, Country) VALUES
('New York', 'USA'), ('London', 'UK'), ('Berlin', 'Germany'), ('Tokyo', 'Japan'), ('Madrid', 'Spain');
INSERT INTO Employees (FirstName, LastName, Email, HireDate, DepartmentId, Salary, CityId) VALUES
('Alice','Smith','alice.smith@example.com','2020-03-15',1,70000,1),
('Bob','Johnson','bob.johnson@example.com','2019-07-22',2,50000,2),
('Charlie','Brown','charlie.brown@example.com','2021-01-10',1,80000,3),
('Diana','King','diana.king@example.com','2018-11-05',3,60000,4),
('Ethan','White','ethan.white@example.com','2022-06-20',4,55000,5);
INSERT INTO Projects (ProjectName, StartDate, EndDate, Budget) VALUES
('Project Apollo','2023-01-01','2023-12-31',500000),
('Project Zeus','2022-05-01','2023-04-30',300000),
('Project Hera','2023-03-01',NULL,200000),
('Project Poseidon','2023-06-15',NULL,150000);
INSERT INTO EmployeeProjects (EmployeeId, ProjectId, AssignedDate, Role) VALUES
(1,1,'2023-01-05','Lead Developer'),
(1,2,'2022-05-10','Developer'),
(2,2,'2022-05-12','HR Support'),
(3,3,'2023-03-10','Developer'),
(4,4,'2023-06-20','Marketing Lead'),
(5,1,'2023-01-15','Finance Analyst');
GO
Lọc hàng với WHERE
Nếu bạn muốn tìm nhân viên có lương hơn \$60k:
sql
SELECT FirstName, LastName, Salary
FROM Employees
WHERE Salary > 60000;
Giải thích:
- Lọc các hàng đơn lẻ trước khi bất kỳ việc nhóm nào xảy ra.
- Kết quả: Alice (70k) và Charlie (80k), giả sử dữ liệu mẫu của chúng ta.
Nhóm dữ liệu với HAVING
Giả sử bạn muốn tìm các phòng ban với hơn một nhân viên có lương trên \$60k:
sql
SELECT DepartmentId, COUNT(EmployeeId) AS NumEmployees
FROM Employees
WHERE Salary > 60000
GROUP BY DepartmentId
HAVING COUNT(EmployeeId) > 1;
Giải thích:
WHERE Salary > 60000→ lọc những nhân viên đơn lẻ.GROUP BY DepartmentId→ nhóm nhân viên theo phòng ban.HAVING COUNT(EmployeeId) > 1→ chỉ lọc những phòng ban có hơn 1 nhân viên thỏa mãn điều kiện.
Mẹo: Nếu truy vấn này không trả về hàng nào, có thể đơn giản là không có phòng ban nào có hơn 1 nhân viên trên \$60k.
Ví dụ tổng hợp với HAVING
Tìm các phòng ban mà trung bình lương vượt quá \$60k:
sql
SELECT DepartmentId, AVG(Salary) AS AvgSalary
FROM Employees
GROUP BY DepartmentId
HAVING AVG(Salary) > 60000;
Điểm chính:
HAVINGlà cần thiết vì bộ lọc phụ thuộc vàoAVG(Salary)(một hàm tổng hợp).- Sử dụng
WHERE AVG(Salary) > 60000sẽ thất bại vì việc tổng hợp chưa xảy ra.
Bài tập thực hành: Tổng lương theo phòng ban
Tìm các phòng ban mà tổng lương vượt quá \$200k và hiển thị cả tổng lương và số lượng nhân viên:
sql
SELECT DepartmentId, SUM(Salary) AS TotalSalary, COUNT(EmployeeId) AS NumEmployees
FROM Employees
GROUP BY DepartmentId
HAVING SUM(Salary) > 200000;
Tại sao sử dụng HAVING:
- Bộ lọc phụ thuộc vào
SUM(Salary), được tính toán sau khi nhóm. WHEREkhông thể được sử dụng ở đây vì nó chỉ nhìn thấy các hàng đơn lẻ.
Kiểm tra nhanh cho các buổi phỏng vấn
- Tôi đang lọc hàng đơn lẻ hay nhóm?
- Hàng →
WHERE - Nhóm →
HAVING
- Tôi có đang sử dụng các hàm tổng hợp không?
- Có → có thể là
HAVING - Không →
WHERE
- Luồng của SQL:
- Bắt đầu với tất cả các hàng
- Áp dụng
WHERE→ lọc hàng GROUP BY→ tạo nhóm- Áp dụng
HAVING→ lọc nhóm
Hiểu WHERE và HAVING không chỉ là về cú pháp. Nó còn về biết khi nào mỗi bộ lọc áp dụng trong thứ tự xử lý SQL. Sử dụng những khái niệm này một cách chính xác sẽ giúp bạn giải quyết các truy vấn thực tế và gây ấn tượng trong các buổi phỏng vấn kỹ thuật.
Cảm ơn bạn đã đọc bài viết này, và hãy nhớ tham gia GoConnect.dev nơi bạn có thể học hỏi thêm và kết nối với các nhà phát triển khác.