Querying Data with SQL
Objective: Use operators and functions to build query conditions; write SELECT to filter, sort, and group data; combine data from multiple tables with JOIN; use UNION/INTERSECT/EXCEPT; write nested queries with IN, ANY, ALL, EXISTS, NOT EXISTS.
01 · OPERATORS IN SQL
Operators
The examples below assume the following table:
NhanVien
| MaNV | HoTen | Luong |
|---|---|---|
| NV01 | Nguyễn An | 10000000 |
| NV02 | Trần Bình | 15000000 |
| NV03 | Lê Cường | 12000000 |
Arithmetic Operators
| Operator | Meaning |
|---|---|
+ | Addition |
- | Subtraction |
* | Multiplication |
/ | Division |
% | Modulo (remainder) |
Calculate salary after a 10% raise:
SELECT HoTen, Luong, Luong * 1.1 AS LuongMoi
FROM NhanVien;| HoTen | Luong | LuongMoi |
|---|---|---|
| Nguyễn An | 10000000 | 11000000 |
| Trần Bình | 15000000 | 16500000 |
| Lê Cường | 12000000 | 13200000 |
Note: Arithmetic operators can be used directly inside SELECT to produce a computed value without saving a new column to the table.
Comparison Operators
| Operator | Meaning |
|---|---|
= | Equal to |
<> or != | Not equal to |
> | Greater than |
< | Less than |
>= | Greater than or equal to |
<= | Less than or equal to |
Find employees earning over 10 million:
SELECT HoTen, Luong
FROM NhanVien
WHERE Luong > 10000000;Find employees in department PB01:
SELECT *
FROM NhanVien
WHERE MaPB = 'PB01';Logical Operators
| Operator | Meaning |
|---|---|
AND | All conditions must be true |
OR | Only one condition needs to be true |
NOT | Negates a condition |
AND - the employee must satisfy both conditions:
SELECT *
FROM NhanVien
WHERE Luong > 5000000
AND MaPB = 'PB01';OR - only one of the two conditions needs to be true:
SELECT *
FROM NhanVien
WHERE Luong > 15000000
OR MaPB = 'PB01';NOT - get employees not in PB01:
SELECT *
FROM NhanVien
WHERE NOT MaPB = 'PB01';Special Operators
| Operator | Meaning |
|---|---|
LIKE | Matches a string against a pattern |
IN | Value is one of a given list |
BETWEEN | Value falls within a range |
IS NULL | Value is undefined (empty) |
IS NOT NULL | Value is present (not empty) |
ANY | True for at least one value in the result set |
ALL | True for every value in the result set |
LIKE is used to search by pattern, combined with two wildcard characters:
| Wildcard | Meaning |
|---|---|
% | Represents any sequence of characters, including an empty one (0, 1, or many characters) |
_ | Represents exactly one character |
Starts with "Nguyễn" - Use % at the end:
SELECT HoTen
FROM NhanVien
WHERE HoTen LIKE N'Nguyễn%';All of these would match: Nguyễn An, Nguyễn Bình, Nguyễn Văn Nam.
Ends with "An" - Use % at the start:
SELECT HoTen
FROM NhanVien
WHERE HoTen LIKE N'%An';Contains "Văn" - Use % on both ends:
SELECT HoTen
FROM NhanVien
WHERE HoTen LIKE N'%Văn%';_ replaces exactly one character - for example, finding a department code shaped like PB0 followed by exactly one digit:
SELECT *
FROM NhanVien
WHERE MaPB LIKE 'PB0_';Matches PB01, PB02,... but not PB010, since the pattern PB0_ only accepts exactly 4 characters.
BETWEEN:
SELECT *
FROM NhanVien
WHERE Luong BETWEEN 10000000 AND 15000000;IN:
SELECT *
FROM NhanVien
WHERE MaPB IN ('PB01', 'PB02');02 · FUNCTIONS IN SQL
Functions in SQL
NOW(), CURDATE(), LENGTH(), DATE_ADD(), CEIL()...). This practical course uses Microsoft SQL Server, so the functions below have been standardized to T-SQL - they run as-is, without copying the raw MySQL syntax.
| MySQL / Generic | T-SQL (SQL Server) |
|---|---|
NOW() | GETDATE() |
CURDATE() | CAST(GETDATE() AS DATE) |
LENGTH() | LEN() |
CEIL() | CEILING() |
DATE_ADD(date, INTERVAL n unit) | DATEADD(unit, n, date) |
DATEDIFF(date1, date2) - number of days | DATEDIFF(DAY, date1, date2) - unit must be specified |
GROUP_CONCAT(x) | STRING_AGG(x, ', ') - a separator must be specified |
Numeric Functions
| Function | Purpose |
|---|---|
ABS() | Absolute value |
ROUND() | Rounds a number |
CEILING() | Rounds up |
FLOOR() | Rounds down |
SQRT() | Square root |
POWER() | Exponent |
SELECT
ABS(-10) AS GiaTriTuyetDoi,
ROUND(3.14159, 2) AS LamTron,
SQRT(16) AS CanBacHai;String Functions
| Function | Purpose |
|---|---|
CONCAT() | Concatenates strings |
LEN() | Counts characters |
SUBSTRING() | Extracts part of a string |
TRIM() | Strips leading/trailing whitespace |
UPPER() | Converts to uppercase |
LOWER() | Converts to lowercase |
REPLACE() | Replaces part of a string |
SELECT
HoTen,
UPPER(HoTen) AS HoTenInHoa
FROM NhanVien;Concatenate employee ID and name:
SELECT
CONCAT(MaNV, ' - ', HoTen) AS ThongTinNhanVien
FROM NhanVien;Date and Time Functions
| Function | Purpose |
|---|---|
GETDATE() | Returns the current date and time |
YEAR() | Extracts the year |
MONTH() | Extracts the month |
DAY() | Extracts the day |
DATEADD() | Adds/subtracts an interval to/from a date |
DATEDIFF() | Computes the difference between two dates |
Get the year an employee was hired:
SELECT
HoTen,
YEAR(NgVL) AS NamVaoLam
FROM NhanVien;Find how many days an employee has worked (T-SQL's DATEDIFF requires the unit DAY to be specified):
SELECT
HoTen,
DATEDIFF(DAY, NgVL, GETDATE()) AS SoNgayLamViec
FROM NhanVien;Aggregate Functions
Unlike the 3 function groups above (which compute per row), aggregate functions compute over a set of many rows and return a single value - usually paired with GROUP BY in section 04.
| Function | Purpose |
|---|---|
COUNT(*) | Counts total rows (including rows with NULL) |
COUNT(x) | Counts rows where column x is not NULL |
SUM(x) | Sums column x |
AVG(x) | Averages column x |
MAX(x) | Largest value |
MIN(x) | Smallest value |
Count employees and compute the sum/average salary:
SELECT
COUNT(*) AS SoNhanVien,
SUM(Luong) AS TongLuong,
AVG(Luong) AS LuongTrungBinh,
MAX(Luong) AS LuongCaoNhat,
MIN(Luong) AS LuongThapNhat
FROM NhanVien;03 · THE SELECT CLAUSE
SELECT
SELECT is one of the most important parts of SQL, used to retrieve data from one or more tables.General Syntax
SELECT [DISTINCT] <ColumnList>
FROM <TableName>
[WHERE <Condition>]
[GROUP BY <ColumnList>]
[HAVING <GroupCondition>]
[ORDER BY <ColumnList> [ASC | DESC]];Basic SELECT
Get all data:
SELECT *
FROM NhanVien;Get only specific columns:
SELECT MaNV, HoTen, Luong
FROM NhanVien;DISTINCT
Removes duplicate rows:
SELECT DISTINCT MaPB
FROM NhanVien;Data PB01, PB01, PB02, PB02, PB03 ➜ result becomes just PB01, PB02, PB03.
WHERE
Used to filter rows based on a condition:
SELECT *
FROM NhanVien
WHERE Luong > 10000000
AND MaPB = 'PB01';ORDER BY
SELECT HoTen, Luong
FROM NhanVien
ORDER BY Luong DESC;ASC - ascending, DESC - descending.
TOP
Example - get the most recently hired employee:
SELECT TOP 1 HoTen, NgVL
FROM NhanVien
ORDER BY NgVL DESC;GROUP BY
Used to group rows sharing the same value. Example - number of employees in each department:
SELECT
MaPB,
COUNT(*) AS SoLuong
FROM NhanVien
GROUP BY MaPB;HAVING
WHERE filters individual rows, while HAVING filters groups after GROUP BY. Example - only show salary levels shared by at least 3 employees:
SELECT
Luong,
COUNT(*) AS SoLuong
FROM NhanVien
GROUP BY Luong
HAVING COUNT(*) >= 3;QUICK CHECK
Which clause is used to filter GROUPS of data after GROUP BY?
GROUP BY, while WHERE filters individual rows before grouping.04 · JOIN OPERATIONS
JOIN
JOIN is used to combine data from two or more tables based on a related condition between them.| NhanVien |
|---|
| MaNV |
| HoTen |
| MaPB |
| PhongBan |
|---|
| MaPB |
| TenPB |
NhanVien.MaPB = PhongBan.MaPB
INNER JOIN
Only returns rows that have matching data in both tables:
SELECT
nv.HoTen,
pb.TenPB
FROM NhanVien nv
INNER JOIN PhongBan pb
ON nv.MaPB = pb.MaPB;LEFT JOIN
Returns all data from the left table, even when there's no matching data in the right table:
SELECT
nv.HoTen,
pb.TenPB
FROM NhanVien nv
LEFT JOIN PhongBan pb
ON nv.MaPB = pb.MaPB;| HoTen | TenPB |
|---|---|
| Nguyễn An | Phòng KT |
| Trần Bình | Phòng KD |
| Lê Cường | NULL |
RIGHT JOIN
The opposite of LEFT JOIN: keeps all data from the right table:
SELECT
nv.HoTen,
pb.TenPB
FROM NhanVien nv
RIGHT JOIN PhongBan pb
ON nv.MaPB = pb.MaPB;FULL OUTER JOIN
Keeps data from both tables, whether or not there's a match:
SELECT
nv.HoTen,
pb.TenPB
FROM NhanVien nv
FULL OUTER JOIN PhongBan pb
ON nv.MaPB = pb.MaPB;CROSS JOIN
Produces a Cartesian product - if NhanVien has 5 rows and PhongBan has 3 rows, the result has 5 × 3 = 15 rows:
SELECT
nv.HoTen,
pb.TenPB
FROM NhanVien nv
CROSS JOIN PhongBan pb;SELF JOIN
A table is used twice to express a relationship between rows within the same table - for example, employees and their managers:
SELECT
nv.HoTen AS NhanVien,
ql.HoTen AS QuanLy
FROM NhanVien nv
JOIN NhanVien ql
ON nv.MaQL = ql.MaNV;EQUI JOIN & Recommended Syntax
An EQUI JOIN compares related columns with =. Prefer the JOIN ... ON syntax:
FROM NhanVien nv
INNER JOIN PhongBan pb
ON nv.MaPB = pb.MaPBinstead of putting the join condition in WHERE (still works, but not recommended):
FROM NhanVien nv, PhongBan pb
WHERE nv.MaPB = pb.MaPB;NATURAL JOIN - automatically joining on columns with matching names between two tables. T-SQL does not support this syntax - if you see it in other material, rewrite it with an explicit INNER JOIN ... ON condition as shown above.05 · SET OPERATIONS
Set Operations
There are 3 main operations used to combine the results of multiple queries:
UNION
Takes all results and removes duplicate rows. Example - employees assigned to DA01 or DA02:
SELECT MaNV
FROM PhanCong
WHERE MaDA = 'DA01'
UNION
SELECT MaNV
FROM PhanCong
WHERE MaDA = 'DA02';INTERSECT
Takes the intersection. Example - employees assigned to both DA01 and DA02:
SELECT MaNV
FROM PhanCong
WHERE MaDA = 'DA01'
INTERSECT
SELECT MaNV
FROM PhanCong
WHERE MaDA = 'DA02';EXCEPT
Takes results from the first query that are not present in the second. Example - employees assigned to DA01 but not DA02:
SELECT MaNV
FROM PhanCong
WHERE MaDA = 'DA01'
EXCEPT
SELECT MaNV
FROM PhanCong
WHERE MaDA = 'DA02';06 · NESTED QUERIES - SUBQUERY
Subquery
SELECT TenSP
FROM SanPham
WHERE MaSP IN
(
SELECT MaSP
FROM HoaDon
);Non-Correlated Subquery
An inner query that does not depend on the outer query. Example - find products sold in January 2024:
SELECT TenSP
FROM SanPham
WHERE MaSP IN
(
SELECT MaSP
FROM HoaDon
WHERE MONTH(NgayBan) = 1
AND YEAR(NgayBan) = 2024
);IN / NOT IN
IN - checks whether a value is present in the subquery's result set:
SELECT TenSP
FROM SanPham
WHERE MaSP IN
(
SELECT MaSP
FROM HoaDon
);NOT IN - find products that have never appeared on an invoice:
SELECT TenSP
FROM SanPham
WHERE MaSP NOT IN
(
SELECT MaSP
FROM HoaDon
);ANY / ALL
ANY compares against at least one value in the subquery's result. Example - salary greater than any employee in PB01, PB02, or PB03:
SELECT HoTen, Luong
FROM NhanVien
WHERE Luong > ANY
(
SELECT Luong
FROM NhanVien
WHERE MaPB IN ('PB01', 'PB02', 'PB03')
);ALL requires the condition to hold against every value in the subquery. Example - products priced higher than every product in the Laptop category:
SELECT MaSP, TenSP, GiaBan
FROM SanPham
WHERE DanhMuc = 'Phu Kien'
AND GiaBan > ALL
(
SELECT GiaBan
FROM SanPham
WHERE DanhMuc = 'Laptop'
);EXISTS / NOT EXISTS
EXISTS checks whether a subquery returns at least one row. Example - employees with at least one order:
SELECT HoTen
FROM NhanVien nv
WHERE EXISTS
(
SELECT *
FROM DonHang dh
WHERE dh.MaNV = nv.MaNV
);Here, the subquery references the current row of the outer query (nv.MaNV) - this is called a Correlated Subquery.
NOT EXISTS - find employees with no orders at all:
SELECT HoTen
FROM NhanVien nv
WHERE NOT EXISTS
(
SELECT *
FROM DonHang dh
WHERE dh.MaNV = nv.MaNV
);07 · SUMMARY - WHICH TOOL TO USE?
Which Tool to Use?
A quick-reference table: knowing the syntax is one thing, but knowing when to use what is what matters.
| Task | Tool |
|---|---|
| Filter data | WHERE |
| Multiple conditions | AND, OR, NOT |
| Search by pattern | LIKE |
| Search within a list | IN |
| Sort results | ORDER BY |
| Remove duplicates | DISTINCT |
| Group rows | GROUP BY |
| Filter groups | HAVING |
| Combine tables | JOIN |
| Combine query results | UNION |
| Common part | INTERSECT |
| Has A but not B | EXCEPT |
| Compare against a result set | ANY, ALL |
| Check data exists | EXISTS |
| Check data doesn't exist | NOT EXISTS |
08 · PRACTICE EXERCISES
Comprehensive Practice
Sales Management - Questions 1 ➜ 25
See the full assignment, attached files, and detailed answers (access code required) for BTTH2.
