PostgreSQL
Cảm ơn bạn đã đăng ký khóa học!
Tài liệu về PostgreSQL vẫn đang được cập nhật.
Để tôn trọng công sức của tác giả, vui lòng không chia sẻ tài liệu.
Chúc bạn học tập hiệu quả!
Cài Đặt PostgreSQL
1. Cài Đặt PostgreSQL
Bạn có thể tải PostgreSQL từ trang chính thức:
PostgreSQL là một hệ quản trị cơ sở dữ liệu mã nguồn mở mạnh mẽ, hỗ trợ nhiều nền tảng như Windows, macOS, và Linux. Khi cài đặt, bạn sẽ được cài đặt cả psql (công cụ dòng lệnh chính) và thường kèm theo pgAdmin (giao diện đồ họa để quản lý cơ sở dữ liệu).
Các phiên bản
PostgreSQL không phân chia thành nhiều phiên bản thương mại như SQL Server, nhưng có các bản phát hành thường xuyên (ví dụ: 15, 16). Bạn nên chọn phiên bản mới nhất để tận dụng các tính năng và bản vá lỗi mới nhất.
2. Cài Đặt Công Cụ Quản Lý (pgAdmin)
pgAdmin là công cụ GUI phổ biến để quản lý PostgreSQL. Tải tại:
3. Kết Nối và Làm Việc Với PostgreSQL
Sử dụng psql (dòng lệnh):
- Mở terminal và chạy lệnh:
psql -U postgres - Nhập mật khẩu mặc định của user
postgres(được thiết lập lúc cài đặt). - Khi kết nối thành công, bạn sẽ thấy dấu nhắc lệnh:
postgres=#.
- Mở terminal và chạy lệnh:
Thông tin kết nối:
- Server name:
localhosthoặc địa chỉ IP của máy chủ (mặc định port là5432). - Authentication: PostgreSQL sử dụng cơ chế xác thực dựa trên
pg_hba.conf(có thể là mật khẩu, trust, hoặc các phương thức khác).
- Server name:
Sử dụng pgAdmin:
- Mở pgAdmin, thêm một server mới, nhập thông tin kết nối (host, port, username, password) và nhấn Save.
Quản lý cơ sở dữ liệu (Database)
Tạo cơ sở dữ liệu (CREATE DATABASE)
CREATE DATABASE StudentManagementSystemTest;
Xóa cơ sở dữ liệu (DROP DATABASE)
DROP DATABASE StudentManagementSystemTest;
Lưu ý: PostgreSQL yêu cầu không có kết nối nào đến cơ sở dữ liệu trước khi xóa. Nếu cần, sử dụng:
DROP DATABASE StudentManagementSystemTest WITH (FORCE);
Sao lưu cơ sở dữ liệu
PostgreSQL không có câu lệnh BACKUP DATABASE như SQL Server. Thay vào đó, sử dụng công cụ dòng lệnh pg_dump:
pg_dump -U postgres -F c StudentManagementSystem > StudentManagementSystem.backup
-F c: Định dạng nén tùy chỉnh.StudentManagementSystem: Tên cơ sở dữ liệu.
Khôi phục cơ sở dữ liệu
Sử dụng pg_restore:
pg_restore -U postgres -d StudentManagementSystem StudentManagementSystem.backup
Hoặc nếu dùng file SQL:
psql -U postgres -d StudentManagementSystem -f StudentManagementSystem.sql
Chọn cơ sở dữ liệu để làm việc
\connect StudentManagementSystem
Hoặc trong script:
SET search_path TO public;
Liệt kê cơ sở dữ liệu
SELECT datname FROM pg_database;
Quản lý bảng (Table)
Tạo bảng (CREATE TABLE)
CREATE TABLE Teachers (
TeacherID SERIAL PRIMARY KEY,
Name VARCHAR(50) NOT NULL
);
SERIAL: Tương đương vớiIDENTITYtrong SQL Server, tự động tăng giá trị.
Xóa bảng (DROP TABLE)
DROP TABLE Teachers;
Thay đổi cấu trúc bảng (ALTER TABLE)
Thêm cột mới
ALTER TABLE Students ADD COLUMN Email VARCHAR(100);
Thay đổi kiểu dữ liệu của cột
ALTER TABLE Students ALTER COLUMN Email TYPE VARCHAR(150);
Đổi tên cột
ALTER TABLE Students RENAME COLUMN Email TO StudentEmail;
Xóa cột
ALTER TABLE Students DROP COLUMN StudentEmail;
Đổi tên bảng
ALTER TABLE Students RENAME TO Pupils;
Liệt kê bảng
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public';
Kiểu dữ liệu (Data Types)
Kiểu số (Numeric Data Types)
| Kiểu dữ liệu | Mô tả | Ví dụ |
|---|---|---|
INTEGER | Số nguyên 4 byte, phạm vi từ -2,147,483,648 đến 2,147,483,647. | 10, 100, -50 |
BIGINT | Số nguyên lớn 8 byte, phạm vi từ -9,223,372,036,854,775,808 đến 9,223,372,036,854,775,807. | 9223372036854775807 |
SMALLINT | Số nguyên nhỏ 2 byte, phạm vi từ -32,768 đến 32,767. | 32767 |
NUMERIC(p,s) | Số thập phân có độ chính xác cao. p là tổng số chữ số, s là số chữ số sau dấu thập phân. | NUMERIC(10,2) → 12345.67 |
REAL | Số thực 4 byte, độ chính xác thấp hơn DOUBLE PRECISION. | 3.14159 |
DOUBLE PRECISION | Số thực 8 byte, độ chính xác cao. | 3.14159265359 |
MONEY | Kiểu tiền tệ, chính xác đến 2 chữ số thập phân. | $1,000.99 |
BOOLEAN | Giá trị logic: TRUE hoặc FALSE. | TRUE, FALSE |
Kiểu chuỗi (String Data Types)
| Kiểu dữ liệu | Mô tả | Ví dụ |
|---|---|---|
CHAR(n) | Chuỗi ký tự cố định độ dài n. | 'ABCD ' (5 ký tự, gồm cả khoảng trắng) |
VARCHAR(n) | Chuỗi ký tự độ dài thay đổi, tối đa n. | 'John Doe' |
TEXT | Chuỗi ký tự độ dài không giới hạn (tối đa 1GB). | 'Lorem ipsum dolor sit amet...' |
✅ Dùng CHAR(n) khi: Chuỗi có độ dài cố định (ví dụ: mã bưu chính).
✅ Dùng VARCHAR(n) khi: Chuỗi có độ dài thay đổi (ví dụ: tên người).
✅ Dùng TEXT khi: Chuỗi rất dài (ví dụ: bài viết, nội dung JSON).
Lưu ý: PostgreSQL không có NCHAR hay NVARCHAR như SQL Server vì tất cả chuỗi đều hỗ trợ Unicode mặc định.
Kiểu ngày và thời gian (Date & Time Data Types)
| Kiểu dữ liệu | Mô tả | Ví dụ |
|---|---|---|
DATE | Chỉ lưu ngày, phạm vi từ 4713 BC đến 5874897 AD. | '2025-03-01' |
TIME | Chỉ lưu giờ, phút, giây. | '12:30:45' |
TIMESTAMP | Lưu ngày và giờ, không có múi giờ. | '2025-03-01 12:30:45' |
TIMESTAMPTZ | Lưu ngày và giờ với múi giờ. | '2025-03-01 12:30:45+07' |
INTERVAL | Khoảng thời gian. | '1 year 2 months 3 days' |
Kiểu dữ liệu khác
| Kiểu dữ liệu | Mô tả | Ví dụ |
|---|---|---|
BYTEA | Dữ liệu nhị phân (tương tự VARBINARY). | \xDEADBEEF |
UUID | Định danh duy nhất toàn cục (GUID). | '550e8400-e29b-41d4-a716-446655440000' |
JSON / JSONB | Lưu trữ dữ liệu JSON (JSONB là định dạng nhị phân, hỗ trợ lập chỉ mục). | '{"name": "John"}' |
Ví dụ tạo bảng với kiểu dữ liệu phổ biến
CREATE TABLE Employees (
EmployeeID UUID DEFAULT gen_random_uuid() PRIMARY KEY, -- ID duy nhất, tự động sinh
Name VARCHAR(100) NOT NULL, -- Họ và tên
Birthdate DATE, -- Ngày sinh
WorkStartTime TIME, -- Giờ bắt đầu làm việc
Salary NUMERIC(10,2), -- Lương
ProfilePicture BYTEA, -- Ảnh đại diện
IsActive BOOLEAN DEFAULT TRUE -- Trạng thái hoạt động
);
Biến (Variables)
Trong PostgreSQL, biến thường được sử dụng trong các khối mã PL/pgSQL (ngôn ngữ lập trình tích hợp trong PostgreSQL). Biến không được khai báo trực tiếp trong các truy vấn SQL thông thường mà phải nằm trong một khối DO, hàm, hoặc thủ tục.
Khai báo biến
Sử dụng từ khóa DECLARE trong khối PL/pgSQL:
DO $$
DECLARE
student_name VARCHAR(50);
birth_year INTEGER;
BEGIN
-- Logic xử lý
END $$;
Gán giá trị cho biến
PostgreSQL sử dụng toán tử := để gán giá trị:
Gán giá trị trực tiếp
DO $$
DECLARE
student_name VARCHAR(50);
BEGIN
student_name := 'Hoàng Hiệp';
RAISE NOTICE 'Student Name: %', student_name;
END $$;
Gán giá trị từ truy vấn
DO $$
DECLARE
birth_year INTEGER;
BEGIN
SELECT EXTRACT(YEAR FROM birthdate) INTO birth_year
FROM Students
WHERE student_id = 1;
RAISE NOTICE 'Birth Year: %', birth_year;
END $$;
Sử dụng biến
DO $$
DECLARE
year INTEGER := 2000;
BEGIN
FOR rec IN SELECT * FROM Students WHERE EXTRACT(YEAR FROM birthdate) = year LOOP
RAISE NOTICE 'Student: %', rec.name;
END LOOP;
END $$;
Biến bảng (Record/Row Type)
PostgreSQL không có "biến bảng" như SQL Server, nhưng bạn có thể sử dụng kiểu RECORD hoặc khai báo một cursor:
DO $$
DECLARE
student_rec RECORD;
BEGIN
FOR student_rec IN SELECT student_id, name, birthdate FROM Students LOOP
RAISE NOTICE 'ID: %, Name: %, Birthdate: %',
student_rec.student_id, student_rec.name, student_rec.birthdate;
END LOOP;
END $$;
Biến toàn cục
PostgreSQL không có biến toàn cục như @@VERSION trong SQL Server, nhưng cung cấp các hàm hệ thống:
version(): Lấy thông tin phiên bản PostgreSQL.row_count(): Số dòng bị ảnh hưởng bởi truy vấn gần nhất (trong PL/pgSQL).
DO $$
BEGIN
RAISE NOTICE 'PostgreSQL Version: %', version();
END $$;
Hàm (Functions)
PostgreSQL hỗ trợ tạo các hàm bằng PL/pgSQL hoặc các ngôn ngữ khác như Python, C.
Hàm xử lý chuỗi (String Functions)
| Hàm | Chức năng | Ví dụ |
|---|---|---|
LENGTH(string) | Trả về độ dài chuỗi | LENGTH('Hello') → 5 |
LEFT(string, n) | Lấy n ký tự bên trái | LEFT('PostgreSQL', 4) → 'Post' |
RIGHT(string, n) | Lấy n ký tự bên phải | RIGHT('PostgreSQL', 3) → 'SQL' |
SUBSTRING(string FROM start FOR length) | Cắt chuỗi con | SUBSTRING('PostgreSQL' FROM 5 FOR 3) → 'gre' |
UPPER(string) | Chuyển thành chữ in hoa | UPPER('sql') → 'SQL' |
LOWER(string) | Chuyển thành chữ thường | LOWER('SQL') → 'sql' |
TRIM(string) | Xóa khoảng trắng hai bên | TRIM(' SQL ') → 'SQL' |
REPLACE(string, old, new) | Thay thế chuỗi con | REPLACE('Hello World', 'World', 'SQL') → 'Hello SQL' |
POSITION(substring IN string) | Vị trí của chuỗi con | POSITION('SQL' IN 'Learn SQL') → 7 |
CONCAT(string1, string2, …) | Nối chuỗi | CONCAT('Hello', ' ', 'SQL') → 'Hello SQL' |
Hàm xử lý số (Numeric Functions)
| Hàm | Chức năng | Ví dụ |
|---|---|---|
ABS(number) | Trị tuyệt đối | ABS(-5) → 5 |
CEIL(number) | Làm tròn lên | CEIL(4.2) → 5 |
FLOOR(number) | Làm tròn xuống | FLOOR(4.9) → 4 |
ROUND(number, decimals) | Làm tròn số | ROUND(4.567, 2) → 4.57 |
POWER(base, exponent) | Lũy thừa | POWER(2, 3) → 8 |
SQRT(number) | Căn bậc hai | SQRT(9) → 3 |
RANDOM() | Số ngẫu nhiên từ 0 đến 1 | RANDOM() → 0.684 |
Hàm xử lý ngày tháng (Date Functions)
| Hàm | Chức năng | Ví dụ |
|---|---|---|
NOW() | Ngày giờ hiện tại với múi giờ | NOW() → '2025-03-15 10:15:30+07' |
CURRENT_DATE | Ngày hiện tại | CURRENT_DATE → '2025-03-15' |
EXTRACT(part FROM date) | Trích xuất phần của ngày | EXTRACT(YEAR FROM '2025-03-15'::DATE) → 2025 |
AGE(date) | Khoảng cách từ ngày đó đến hiện tại | AGE('2000-01-01'::DATE) → '25 years 2 months' |
DATE_TRUNC('part', date) | Cắt ngắn đến phần cụ thể | DATE_TRUNC('month', NOW()) → '2025-03-01 00:00:00+07' |
TO_CHAR(date, format) | Định dạng ngày | TO_CHAR(NOW(), 'DD/MM/YYYY') → '15/03/2025' |
Các phần trong EXTRACT
| Phần | Ý nghĩa | Ví dụ |
|---|---|---|
YEAR | Năm | EXTRACT(YEAR FROM '2025-03-15'::DATE) → 2025 |
MONTH | Tháng | EXTRACT(MONTH FROM '2025-03-15'::DATE) → 3 |
DAY | Ngày | EXTRACT(DAY FROM '2025-03-15'::DATE) → 15 |
HOUR | Giờ | EXTRACT(HOUR FROM NOW()) → 10 |
WEEK | Tuần trong năm | EXTRACT(WEEK FROM '2025-03-15'::DATE) → 11 |
Hàm chuyển đổi dữ liệu
| Hàm | Chức năng | Ví dụ |
|---|---|---|
CAST(expression AS type) | Chuyển kiểu dữ liệu | CAST(123 AS TEXT) → '123' |
:: | Toán tử chuyển kiểu ngắn gọn | '2025-03-15'::DATE → '2025-03-15' |
TO_CHAR(expression, format) | Chuyển thành chuỗi có định dạng | TO_CHAR(123.45, '999.99') → '123.45' |
Hàm xử lý giá trị NULL
| Hàm | Chức năng | Ví dụ |
|---|---|---|
COALESCE(val1, val2, …) | Trả về giá trị không NULL đầu tiên | COALESCE(NULL, 'SQL', 'Server') → 'SQL' |
NULLIF(val1, val2) | Trả về NULL nếu hai giá trị bằng nhau | NULLIF(10, 10) → NULL |
Tạo hàm tùy chỉnh
CREATE FUNCTION get_student_count()
RETURNS INTEGER AS $$
BEGIN
RETURN (SELECT COUNT(*) FROM Students);
END;
$$ LANGUAGE plpgsql;
-- Gọi hàm
SELECT get_student_count();
Ràng buộc dữ liệu
Tổng quan các ràng buộc
| Ràng buộc | Chức năng |
|---|---|
NOT NULL | Không cho phép giá trị NULL. |
UNIQUE | Đảm bảo giá trị trong cột là duy nhất. |
PRIMARY KEY | Kết hợp NOT NULL và UNIQUE. |
FOREIGN KEY | Tạo mối quan hệ giữa hai bảng. |
CHECK | Kiểm tra giá trị thỏa mãn điều kiện. |
DEFAULT | Gán giá trị mặc định nếu không nhập. |
NOT NULL
CREATE TABLE Teachers (
TeacherID SERIAL PRIMARY KEY,
Name VARCHAR(50) NOT NULL
);
UNIQUE
CREATE TABLE Doctors (
DoctorID SERIAL PRIMARY KEY,
Email VARCHAR(100) UNIQUE
);
PRIMARY KEY
CREATE TABLE Departments (
DeptID SERIAL PRIMARY KEY,
DeptName VARCHAR(50) NOT NULL
);
FOREIGN KEY
CREATE TABLE Enrollments (
StudentID INTEGER,
CourseID INTEGER,
FOREIGN KEY (StudentID) REFERENCES Students(StudentID)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (CourseID) REFERENCES Courses(CourseID)
ON DELETE SET NULL
);
CHECK
CREATE TABLE Employees (
EmployeeID SERIAL PRIMARY KEY,
Age INTEGER CHECK (Age >= 18)
);
DEFAULT
CREATE TABLE Orders (
OrderID SERIAL PRIMARY KEY,
OrderDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Ví dụ bảng với đầy đủ ràng buộc
CREATE TABLE Departments (
DeptID SERIAL PRIMARY KEY,
DeptName VARCHAR(100) NOT NULL UNIQUE
);
CREATE TABLE Employees (
EmployeeID SERIAL PRIMARY KEY,
Name VARCHAR(100) NOT NULL,
Email VARCHAR(100) UNIQUE,
Birthdate DATE CHECK (Birthdate >= '1990-01-01'),
Salary NUMERIC(10,2) DEFAULT 1000.00,
DepartmentID INTEGER REFERENCES Departments(DeptID)
);
Thao tác dữ liệu
Thêm dữ liệu (INSERT INTO)
Thêm một sinh viên mới vào bảng Students:
INSERT INTO Students (name, birthdate)
VALUES ('Hoàng Hiệp', '2001-05-20');
Cập nhật dữ liệu (UPDATE)
Cập nhật ngày sinh cho sinh viên có student_id = 4:
UPDATE Students
SET birthdate = '2000-06-15'
WHERE student_id = 4;
Xóa dữ liệu (DELETE)
Xóa sinh viên có student_id = 2:
DELETE FROM Students
WHERE student_id = 2;
Lưu ý:
- Nếu không có
WHERE, toàn bộ dữ liệu trong bảng sẽ bị xóa. DELETEcó thể kích hoạt trigger nếu được định nghĩa.
Xóa toàn bộ dữ liệu (TRUNCATE)
Xóa toàn bộ dữ liệu trong bảng, nhanh hơn DELETE:
TRUNCATE TABLE Students;
Lưu ý:
TRUNCATEkhông ghi log từng dòng, không kích hoạt trigger.- Để reset giá trị
SERIAL, dùngTRUNCATE TABLE Students RESTART IDENTITY.
Sao chép dữ liệu vào bảng mới (CREATE TABLE AS)
Tạo bảng StudentsBackup và sao chép dữ liệu từ Students:
CREATE TABLE StudentsBackup AS
SELECT * FROM Students;
Chèn dữ liệu từ bảng khác (INSERT INTO SELECT)
Chèn dữ liệu từ Students vào StudentsBackup:
INSERT INTO StudentsBackup (name, birthdate)
SELECT name, birthdate
FROM Students;
Hợp nhất dữ liệu (MERGE)
PostgreSQL không có lệnh MERGE như SQL Server, nhưng có thể dùng INSERT ... ON CONFLICT:
INSERT INTO StudentsBackup (student_id, name, birthdate)
SELECT student_id, name, birthdate
FROM Students
ON CONFLICT (student_id)
DO UPDATE SET
name = EXCLUDED.name,
birthdate = EXCLUDED.birthdate;
Kiểm tra tồn tại (EXISTS)
Kiểm tra xem sinh viên “Hoàng Hiệp” đã tồn tại chưa:
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM Students WHERE name = 'Hoàng Hiệp') THEN
RAISE NOTICE 'Sinh viên đã tồn tại';
ELSE
INSERT INTO Students (name, birthdate)
VALUES ('Hoàng Hiệp', '2001-05-20');
END IF;
END $$;
Lấy dữ liệu bị ảnh hưởng (RETURNING)
Lấy thông tin các sinh viên bị xóa:
DELETE FROM Students
WHERE birthdate < '2000-01-01'
RETURNING student_id, name;
Toán tử và điều kiện lọc
Toán tử so sánh
| Toán tử | Chức năng |
|---|---|
= | So sánh bằng |
!= hoặc <> | So sánh khác |
> | Lớn hơn |
< | Nhỏ hơn |
>= | Lớn hơn hoặc bằng |
<= | Nhỏ hơn hoặc bằng |
BETWEEN | Trong khoảng |
IN | Trong danh sách |
LIKE | Khớp mẫu ký tự |
AND | Và |
OR | Hoặc |
NOT | Phủ định |
AND
Lấy sinh viên có tên bắt đầu bằng 'P' và sinh sau 2000:
SELECT * FROM Students
WHERE name LIKE 'P%' AND birthdate > '2000-01-01';
OR
Lấy sinh viên có tên là “Trần Nhật Anh” hoặc “Vũ Khánh Linh”:
SELECT * FROM Students
WHERE name = 'Trần Nhật Anh' OR name = 'Vũ Khánh Linh';
NOT
Lấy tất cả sinh viên trừ “Hoàng Hiệp”:
SELECT * FROM Students
WHERE name != 'Hoàng Hiệp';
LIKE
Tìm sinh viên có tên bắt đầu bằng “T”:
SELECT * FROM Students
WHERE name LIKE 'T%';
Wildcards
%: Đại diện cho bất kỳ chuỗi ký tự nào._: Đại diện cho một ký tự duy nhất.
SELECT * FROM Students
WHERE name LIKE '%ng%'; -- Tên chứa “ng”
IN
Lấy sinh viên có tên trong danh sách:
SELECT * FROM Students
WHERE name IN ('Vũ Khánh Linh', 'Trần Nhật Anh', 'Lê Thu Hà');
BETWEEN
Lấy sinh viên sinh từ 2000 đến 2003:
SELECT * FROM Students
WHERE birthdate BETWEEN '2000-01-01' AND '2003-12-31';
IS NULL và IS NOT NULL
Lấy sinh viên chưa có ngày sinh:
SELECT * FROM Students
WHERE birthdate IS NULL;
ANY và ALL
Lấy sinh viên sinh muộn hơn ít nhất một sinh viên trong danh sách:
SELECT * FROM Students
WHERE birthdate > ANY (
SELECT birthdate
FROM Students
WHERE name IN ('Vũ Khánh Linh', 'Trần Nhật Anh')
);
EXISTS
Lấy sinh viên đã đăng ký khóa học:
SELECT name
FROM Students s
WHERE EXISTS (
SELECT 1
FROM Enrollments e
WHERE s.student_id = e.student_id
);
IF ELSE (trong PL/pgSQL)
DO $$
DECLARE
total_students INTEGER;
BEGIN
SELECT COUNT(*) INTO total_students FROM Students;
IF total_students > 100 THEN
RAISE NOTICE 'Có hơn 100 sinh viên.';
ELSE
RAISE NOTICE 'Số lượng sinh viên ít hơn hoặc bằng 100.';
END IF;
END $$;
CASE WHEN
Xác định nhóm tuổi của sinh viên:
SELECT
name,
CASE
WHEN EXTRACT(YEAR FROM AGE(birthdate)) < 18 THEN 'Teen'
WHEN EXTRACT(YEAR FROM AGE(birthdate)) BETWEEN 18 AND 24 THEN 'Young Adult'
ELSE 'Adult'
END AS age_group
FROM Students;
Truy vấn dữ liệu
SELECT
Lấy toàn bộ dữ liệu từ Students:
SELECT * FROM Students;
SELECT DISTINCT
Loại bỏ trùng lặp:
SELECT DISTINCT name
FROM Students;
WHERE
Lọc sinh viên sinh sau 2001:
SELECT * FROM Students
WHERE birthdate > '2001-01-01';
ORDER BY
Sắp xếp theo tên tăng dần:
SELECT * FROM Students
ORDER BY name ASC;
LIMIT và OFFSET
Lấy 3 sinh viên trẻ nhất:
SELECT * FROM Students
ORDER BY birthdate DESC
LIMIT 3;
Kết hợp bảng
INNER JOIN
Lấy sinh viên đã đăng ký khóa học:
SELECT s.student_id, s.name AS student_name, c.course_id, c.name AS course_name
FROM Students s
INNER JOIN Enrollments e ON s.student_id = e.student_id
INNER JOIN Courses c ON e.course_id = c.course_id;
LEFT JOIN
Lấy tất cả sinh viên, kể cả chưa đăng ký:
SELECT s.student_id, s.name AS student_name, c.course_id, c.name AS course_name
FROM Students s
LEFT JOIN Enrollments e ON s.student_id = e.student_id
LEFT JOIN Courses c ON e.course_id = c.course_id;
RIGHT JOIN
Lấy tất cả khóa học, kể cả chưa có sinh viên:
SELECT s.student_id, s.name AS student_name, c.course_id, c.name AS course_name
FROM Students s
RIGHT JOIN Enrollments e ON s.student_id = e.student_id
RIGHT JOIN Courses c ON e.course_id = c.course_id;
FULL JOIN
Lấy tất cả dữ liệu từ cả hai bảng:
SELECT s.student_id, s.name AS student_name, c.course_id, c.name AS course_name
FROM Students s
FULL JOIN Enrollments e ON s.student_id = e.student_id
FULL JOIN Courses c ON e.course_id = c.course_id;
CROSS JOIN
Kết hợp tất cả hàng:
SELECT s.name AS student_name, c.name AS course_name
FROM Students s
CROSS JOIN Courses c;
SELF JOIN
Nối bảng với chính nó:
ALTER TABLE Students
ADD COLUMN mentor_id INTEGER REFERENCES Students(student_id);
SELECT s1.student_id, s1.name AS student_name,
s1.mentor_id, s2.name AS mentor_name
FROM Students s1
LEFT JOIN Students s2 ON s1.mentor_id = s2.student_id;
UNION
Kết hợp dữ liệu, loại bỏ trùng lặp:
SELECT name FROM Students
UNION
SELECT name FROM Courses;
UNION ALL
Kết hợp dữ liệu, giữ trùng lặp:
SELECT name FROM Students
UNION ALL
SELECT name FROM Courses;
EXCEPT
Lấy bản ghi có trong tập đầu nhưng không trong tập sau:
SELECT name FROM Students
EXCEPT
SELECT name FROM Courses;
INTERSECT
Lấy bản ghi xuất hiện trong cả hai tập:
SELECT name FROM Students
INTERSECT
SELECT name FROM Courses;
Hàm tổng hợp
COUNT
Đếm số lượng bản ghi:
SELECT COUNT(*) AS total_students
FROM Students;
SUM
Tính tổng giá trị:
SELECT SUM(fee) AS total_fees
FROM Enrollments;
AVG
Tính giá trị trung bình:
SELECT AVG(fee) AS average_fee
FROM Enrollments;
MIN và MAX
Tìm giá trị nhỏ nhất và lớn nhất:
SELECT MIN(birthdate) AS oldest, MAX(birthdate) AS youngest
FROM Students;
GROUP BY
Nhóm dữ liệu:
SELECT c.course_id, c.name, SUM(e.fee) AS total_fee
FROM Courses c
LEFT JOIN Enrollments e ON c.course_id = e.course_id
GROUP BY c.course_id, c.name;
HAVING
Lọc nhóm dữ liệu:
SELECT c.course_id, c.name, SUM(e.fee) AS total_fee
FROM Courses c
LEFT JOIN Enrollments e ON c.course_id = e.course_id
GROUP BY c.course_id, c.name
HAVING SUM(e.fee) >= 20;
COUNT DISTINCT
Đếm số lượng giá trị không trùng lặp:
SELECT c.course_id, c.name, COUNT(DISTINCT e.student_id) AS enrolled_students
FROM Courses c
LEFT JOIN Enrollments e ON c.course_id = e.course_id
GROUP BY c.course_id, c.name;
STRING_AGG
Ghép các giá trị thành chuỗi:
SELECT class_id, STRING_AGG(name, ', ') AS student_list
FROM Students
GROUP BY class_id;
STDDEV và VARIANCE
Tính độ lệch chuẩn và phương sai:
SELECT STDDEV(score) AS standard_deviation, VARIANCE(score) AS variance
FROM Scores;
Index
Index trong PostgreSQL giúp tăng tốc truy vấn bằng cách tạo chỉ mục trên một hoặc nhiều cột.
Chỉ mục trong PostgreSQL
- B-tree: Chỉ mục mặc định, phù hợp với hầu hết các truy vấn.
- GIN: Dùng cho dữ liệu phức tạp như mảng, JSON.
- GiST: Dùng cho dữ liệu không gian (spatial data).
Tạo chỉ mục
CREATE TABLE Employees (
employee_id SERIAL PRIMARY KEY, -- Tự động tạo B-tree index
name VARCHAR(100) NOT NULL,
email VARCHAR(100)```sql
CREATE INDEX idx_employee_name ON Employees (name);
Chỉ mục duy nhất
CREATE UNIQUE INDEX idx_employee_email ON Employees (email);
Xóa chỉ mục
DROP INDEX idx_employee_name;
So sánh
| Tiêu chí | B-tree Index |
|---|---|
| Sắp xếp dữ liệu? | ❌ Không (chỉ là chỉ mục) |
| Số lượng? | ✅ Nhiều |
| Tốc độ truy vấn? | ⚡ Nhanh |
| Khi dữ liệu thay đổi? | ❌ Có thể chậm do cập nhật index |
Lưu ý:
- Index tăng tốc
WHERE,ORDER BY,JOIN. - Làm chậm
INSERT,UPDATE,DELETEdo phải cập nhật index.
SQL nâng cao
Subquery (Truy vấn con)
Single-row Subquery
Lấy sinh viên có ngày sinh sớm nhất:
SELECT * FROM Students
WHERE birthdate = (SELECT MIN(birthdate) FROM Students);
Multiple-row Subquery
Lấy sinh viên cùng năm sinh với “Trần Nhật Anh” hoặc “Phạm Thanh Hương”:
SELECT * FROM Students
WHERE EXTRACT(YEAR FROM birthdate) IN (
SELECT EXTRACT(YEAR FROM birthdate)
FROM Students
WHERE name IN ('Trần Nhật Anh', 'Phạm Thanh Hương')
);
Correlated Subquery
Lấy sinh viên có ngày sinh sớm nhất trong mỗi khóa học:
SELECT c.course_id, c.name AS course_name, s.student_id, s.birthdate
FROM Courses c
LEFT JOIN Enrollments e ON c.course_id = e.course_id
LEFT JOIN Students s ON e.student_id = s.student_id
WHERE s.birthdate = (
SELECT MIN(s2.birthdate)
FROM Students s2
JOIN Enrollments e2 ON s2.student_id = e2.student_id
WHERE e2.course_id = c.course_id
) OR s.student_id IS NULL;
Derived Table
Sinh viên dưới 23 tuổi:
SELECT *
FROM (
SELECT *, EXTRACT(YEAR FROM AGE(birthdate)) AS age
FROM Students
) AS young_students
WHERE age < 23;
Cursor
Cursor trong PostgreSQL cho phép xử lý từng dòng dữ liệu trong một tập kết quả lớn, thay vì tải toàn bộ dữ liệu vào bộ nhớ. Cursor thường được dùng trong khối PL/pgSQL.
Tạo và sử dụng Cursor
DO $$
DECLARE
student_cursor CURSOR FOR
SELECT student_id, name, birthdate
FROM Students
WHERE birthdate > '2000-01-01';
student_rec RECORD;
BEGIN
OPEN student_cursor;
LOOP
FETCH student_cursor INTO student_rec;
EXIT WHEN NOT FOUND;
RAISE NOTICE 'ID: %, Name: %, Birthdate: %',
student_rec.student_id, student_rec.name, student_rec.birthdate;
END LOOP;
CLOSE student_cursor;
END $$;
Cursor với tham số
DO $$
DECLARE
student_cursor CURSOR (min_year INTEGER) FOR
SELECT student_id, name, birthdate
FROM Students
WHERE EXTRACT(YEAR FROM birthdate) >= min_year;
student_rec RECORD;
BEGIN
OPEN student_cursor(2000);
LOOP
FETCH student_cursor INTO student_rec;
EXIT WHEN NOT FOUND;
RAISE NOTICE 'ID: %, Name: %, Birthdate: %',
student_rec.student_id, student_rec.name, student_rec.birthdate;
END LOOP;
CLOSE student_cursor;
END $$;
REF CURSOR
PostgreSQL không có REF CURSOR như SQL Server, nhưng bạn có thể trả về một cursor từ hàm:
CREATE FUNCTION get_students_after_year(min_year INTEGER)
RETURNS refcursor AS $$
DECLARE
cur refcursor := 'student_cursor';
BEGIN
OPEN cur FOR
SELECT student_id, name, birthdate
FROM Students
WHERE EXTRACT(YEAR FROM birthdate) >= min_year;
RETURN cur;
END;
$$ LANGUAGE plpgsql;
-- Sử dụng hàm
DO $$
DECLARE
student_rec RECORD;
cur refcursor;
BEGIN
cur := get_students_after_year(2000);
LOOP
FETCH cur INTO student_rec;
EXIT WHEN NOT FOUND;
RAISE NOTICE 'ID: %, Name: %, Birthdate: %',
student_rec.student_id, student_rec.name, student_rec.birthdate;
END LOOP;
CLOSE cur;
END $$;
Tip
Tối ưu hóa truy vấn
- Sử dụng Index: Tạo chỉ mục cho các cột thường xuyên xuất hiện trong
WHERE,JOIN, hoặcORDER BY. - EXPLAIN: Phân tích kế hoạch truy vấn:
EXPLAIN SELECT * FROM Students WHERE birthdate > '2000-01-01'; - **Tránh SELECT *** : Chỉ lấy các cột cần thiết:
SELECT student_id, name FROM Students; - Sử dụng CTE hoặc Subquery hợp lý: Giảm lặp lại logic phức tạp.
Quản lý dữ liệu
- Sao lưu thường xuyên: Dùng
pg_dumpđể sao lưu định kỳ. - Kiểm tra ràng buộc: Đảm bảo các khóa ngoại (
FOREIGN KEY) được thiết lập để duy trì tính toàn vẹn dữ liệu. - Sử dụng TRANSACTION: Đảm bảo an toàn khi thao tác dữ liệu:
BEGIN; UPDATE Students SET birthdate = '2001-01-01' WHERE student_id = 1; COMMIT;
Viết code sạch
- Đặt tên bảng, cột rõ ràng (ví dụ:
Students,student_id). - Sử dụng comment trong hàm PL/pgSQL:
-- Tính số sinh viên theo năm sinh CREATE FUNCTION count_students_by_year(birth_year INTEGER) RETURNS INTEGER AS $$ BEGIN RETURN (SELECT COUNT(*) FROM Students WHERE EXTRACT(YEAR FROM birthdate) = birth_year); END; $$ LANGUAGE plpgsql;
Bài tập thực hành
Hệ thống quản lý sinh viên tại trường đại học EZSE
Cơ sở dữ liệu dưới đây được thiết kế để quản lý thông tin về sinh viên, giảng viên, ngành học, môn học, và các lớp học tại trường đại học EZSE. Hệ thống hỗ trợ các chức năng như đăng ký môn học, theo dõi điểm số, và quản lý các lớp học.
Câu lệnh SQL tạo Database và dữ liệu (PostgreSQL)
-- Không có lệnh USE trong PostgreSQL, thay vào đó sử dụng \connect hoặc tạo database trước
DROP DATABASE IF EXISTS EZSEStudentManagementSystem;
CREATE DATABASE EZSEStudentManagementSystem;
\connect EZSEStudentManagementSystem
-- Xóa các bảng nếu đã tồn tại, kèm theo CASCADE để xóa các ràng buộc khóa ngoại
DO $$
DECLARE
r RECORD;
BEGIN
FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = 'public') LOOP
EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE';
END LOOP;
END $$;
---------------------------- Create table ----------------------------------
-- Bảng Ngành học (Majors)
CREATE TABLE Majors (
MajorID SERIAL PRIMARY KEY,
MajorName VARCHAR(100) NOT NULL
);
-- Bảng Sinh viên (Students)
CREATE TABLE Students (
StudentID SERIAL PRIMARY KEY,
FirstName VARCHAR(50) NOT NULL,
LastName VARCHAR(50) NOT NULL,
DateOfBirth DATE NOT NULL,
Gender VARCHAR(15) CHECK (Gender IN ('Male', 'Female', 'Other')),
Email VARCHAR(100) UNIQUE NOT NULL,
Phone VARCHAR(15) UNIQUE,
Address VARCHAR(255),
EnrollmentDate DATE NOT NULL,
MajorID INTEGER REFERENCES Majors(MajorID)
);
-- Bảng Giảng viên (Teachers)
CREATE TABLE Teachers (
TeacherID SERIAL PRIMARY KEY,
FirstName VARCHAR(50) NOT NULL,
LastName VARCHAR(50) NOT NULL,
Email VARCHAR(100) UNIQUE NOT NULL,
Phone VARCHAR(15) UNIQUE,
Address VARCHAR(255)
);
-- Bảng Môn học (Courses)
CREATE TABLE Courses (
CourseID SERIAL PRIMARY KEY,
CourseName VARCHAR(100) NOT NULL,
Credits INTEGER CHECK (Credits > 0)
);
-- Bảng Môn học theo ngành (MajorCourses)
CREATE TABLE MajorCourses (
MajorID INTEGER REFERENCES Majors(MajorID),
CourseID INTEGER REFERENCES Courses(CourseID),
PRIMARY KEY (MajorID, CourseID)
);
-- Bảng Lớp học (Classes)
CREATE TABLE Classes (
ClassID SERIAL PRIMARY KEY,
CourseID INTEGER REFERENCES Courses(CourseID),
TeacherID INTEGER REFERENCES Teachers(TeacherID),
Semester VARCHAR(20) NOT NULL,
Year INTEGER NOT NULL
);
-- Bảng Đăng ký môn học (Enrollments)
CREATE TABLE Enrollments (
StudentID INTEGER REFERENCES Students(StudentID),
ClassID INTEGER REFERENCES Classes(ClassID),
Attempt INTEGER NOT NULL DEFAULT 1,
Grade DECIMAL(4,2) CHECK (Grade >= 0 AND Grade <= 10),
PRIMARY KEY (StudentID, ClassID, Attempt)
);
-- Chèn dữ liệu vào bảng Majors
INSERT INTO Majors (MajorName) VALUES
('Computer Science'),
('Software Engineering'),
('Information Security');
-- Chèn dữ liệu vào bảng Courses
INSERT INTO Courses (CourseName, Credits) VALUES
('Data Structures & Algorithms', 3),
('Object-Oriented Programming', 3),
('Database Systems', 3),
('Computer Networks', 3),
('Web Development', 4),
('Agile Software Development', 3),
('Cyber Security', 3),
('Mobile Application Development', 3);
-- Chèn dữ liệu vào bảng Students
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID) VALUES
('Phạm', 'Gia Bảo', '2002-03-10', 'Male', '[email protected]', '0912345678', '12 Trần Phú, Hà Nội', '2021-09-01', 1),
('Nguyễn', 'Thảo Linh', '2003-07-22', 'Female', '[email protected]', '0987654321', '34 Lê Lợi, TP.HCM', '2021-09-01', 2),
('Trần', 'Quang Vinh', '2001-05-15', 'Male', '[email protected]', '0933123456', '56 Nguyễn Trãi, Đà Nẵng', '2020-09-01', 3),
('Lê', 'Minh Nhật', '2002-09-25', 'Male', '[email protected]', '0977890123', '78 Hai Bà Trưng, Hải Phòng', '2021-09-01', 1),
('Vũ', 'Hồng Ngọc', '2003-01-30', 'Female', '[email protected]', '0966543210', '90 Quang Trung, Cần Thơ', '2022-09-01', 2),
('Đỗ', 'Thanh Huyền', '2004-02-14', 'Female', '[email protected]', '0913456789', '15 Hùng Vương, Huế', '2022-09-01', 3),
('Hoàng', 'Đức Anh', '2003-11-05', 'Male', '[email protected]', '0934567890', '27 Nguyễn Huệ, Quy Nhơn', '2021-09-01', 1),
('Ngô', 'Khánh Duy', '2002-08-19', 'Male', '[email protected]', '0945678901', '39 Lê Đại Hành, Vinh', '2020-09-01', 2),
('Bùi', 'Thục Đoan', '2004-06-25', 'Female', '[email protected]', '0956789012', '51 Phạm Ngũ Lão, Nha Trang', '2023-09-01', 3),
('Phan', 'Tiến Đạt', '2003-04-12', 'Male', '[email protected]', '0967890123', '63 Nguyễn Văn Cừ, Đà Lạt', '2022-09-01', 1),
('Đặng', 'Mỹ Linh', '2004-09-08', 'Female', '[email protected]', '0978901234', '75 Trần Hưng Đạo, Hà Nội', '2023-09-01', 2),
('Võ', 'Hoàng Phúc', '2002-12-30', 'Male', '[email protected]', '0989012345', '87 Bạch Đằng, TP.HCM', '2020-09-01', 3),
('Trương', 'Ngọc Ánh', '2003-03-17', 'Female', '[email protected]', '0990123456', '99 Nguyễn Thị Minh Khai, Đà Nẵng', '2021-09-01', 1),
('Lý', 'Quốc Hưng', '2004-07-21', 'Male', '[email protected]', '0901234567', '111 Lê Lai, Hải Phòng', '2023-09-01', 2),
('Nguyễn', 'Văn Hùng', '2003-05-10', 'Male', '[email protected]', '0912345689', '123 Nguyễn Huệ, Hà Nội', '2022-09-01', 1),
('Trần', 'Thị Mai', '2004-08-15', 'Female', '[email protected]', '0987654332', '45 Lê Lợi, TP.HCM', '2023-09-01', 1),
('Lê', 'Quang Minh', '2002-11-20', 'Male', '[email protected]', '0933123467', '67 Trần Phú, Đà Nẵng', '2021-09-01', 1),
('Phạm', 'Hồng Phúc', '2003-02-25', 'Male', '[email protected]', '0977890134', '89 Hai Bà Trưng, Hải Phòng', '2022-09-01', 1),
('Vũ', 'Thị Lan', '2004-04-12', 'Female', '[email protected]', '0966543221', '101 Quang Trung, Cần Thơ', '2023-09-01', 2),
('Đỗ', 'Minh Tuấn', '2003-09-30', 'Male', '[email protected]', '0913456790', '23 Hùng Vương, Huế', '2022-09-01', 2),
('Hoàng', 'Ngọc Ánh', '2002-07-18', 'Female', '[email protected]', '0934567901', '34 Nguyễn Huệ, Quy Nhơn', '2021-09-01', 2),
('Ngô', 'Văn Long', '2003-12-05', 'Male', '[email protected]', '0945678912', '56 Lê Đại Hành, Vinh', '2022-09-01', 3),
('Bùi', 'Thị Hương', '2004-03-22', 'Female', '[email protected]', '0956789023', '78 Phạm Ngũ Lão, Nha Trang', '2023-09-01', 3),
('Phan', 'Quốc Anh', '2002-06-15', 'Male', '[email protected]', '0967890134', '90 Nguyễn Văn Cừ, Đà Lạt', '2021-09-01', 3);
-- Chèn dữ liệu vào bảng Teachers
INSERT INTO Teachers (FirstName, LastName, Email, Phone, Address) VALUES
('Hoàng', 'Văn Hiệp', '[email protected]', '0944123456', '101 Lý Thường Kiệt, Hà Nội'),
('Lê', 'Thu Hà', '[email protected]', '0933221122', '202 Phạm Văn Đồng, TP.HCM'),
('Hoàng', 'Việt Thắng', '[email protected]', '0922334455', '303 Trường Chinh, Đà Nẵng'),
('Nguyễn', 'Minh Tuấn', '[email protected]', '0911445566', '404 Nguyễn Văn Linh, Hải Phòng'),
('Phạm', 'Quốc Khánh', '[email protected]', '0902345678', '505 Điện Biên Phủ, Huế'),
('Vũ', 'Thị Duyên', '[email protected]', '0913567890', '606 Nguyễn Đình Chiểu, TP.HCM'),
('Trần', 'Đình Phong', '[email protected]', '0924678901', '707 Trần Phú, Hà Nội'),
('Đỗ', 'Ngọc Mai', '[email protected]', '0935789012', '808 Lê Hồng Phong, Đà Nẵng'),
('Bùi', 'Gia Bảo', '[email protected]', '0946890123', '909 Nguyễn Trãi, Hải Phòng'),
('Ngô', 'Thị Lan Anh', '[email protected]', '0957901234', '1010 Phạm Hùng, Cần Thơ');
-- Chèn dữ liệu vào bảng MajorCourses
INSERT INTO MajorCourses (MajorID, CourseID) VALUES
(1, 1), (1, 3), (1, 4), (1, 6),
(2, 2), (2, 5), (2, 6),
(3, 4), (3, 7), (3, 8);
-- Chèn dữ liệu vào bảng Classes
INSERT INTO Classes (CourseID, TeacherID, Semester, Year) VALUES
(1, 1, 'Spring', 2023),
(2, 2, 'Spring', 2023),
(3, 1, 'Summer', 2023),
(4, 3, 'Fall', 2023),
(5, 2, 'Spring', 2023),
(6, 4, 'Summer', 2023),
(7, 3, 'Fall', 2023),
(8, 5, 'Spring', 2023),
(1, 1, 'Spring', 2024),
(2, 2, 'Fall', 2024),
(3, 1, 'Spring', 2024),
(4, 3, 'Fall', 2024),
(5, 2, 'Summer', 2024),
(6, 4, 'Spring', 2024),
(7, 3, 'Fall', 2024),
(8, 5, 'Summer', 2024),
(1, 1, 'Fall', 2024),
(3, 6, 'Fall', 2024),
(1, 1, 'Spring', 2025),
(2, 2, 'Spring', 2025),
(3, 1, 'Summer', 2025),
(4, 3, 'Fall', 2025),
(5, 2, 'Spring', 2025),
(6, 4, 'Summer', 2025),
(7, 3, 'Fall', 2025),
(8, 5, 'Spring', 2025),
(2, 2, 'Fall', 2025),
(4, 3, 'Spring', 2025);
-- Chèn dữ liệu vào bảng Enrollments
INSERT INTO Enrollments (StudentID, ClassID, Attempt, Grade) VALUES
(1, 1, 1, 8.5),
(1, 9, 1, 7.2),
(1, 3, 1, 6.8),
(1, 4, 1, 5.5),
(1, 12, 2, 7.5),
(2, 2, 1, 8.0),
(2, 5, 1, 9.0),
(2, 6, 1, 6.0),
(2, 14, 1, 4.8),
(3, 4, 1, 7.5),
(3, 7, 1, 8.8),
(3, 8, 1, 9.2),
(4, 1, 1, 5.0),
(4, 9, 2, 7.2),
(4, 5, 1, 8.5),
(4, 6, 1, 9.1),
(5, 2, 1, 6.8),
(5, 3, 1, 5.5),
(5, 11, 2, 7.5),
(5, 8, 1, 8.3),
(6, 12, 1, 8.0),
(6, 15, 1, 7.8),
(6, 16, 1, 9.0),
(7, 9, 1, 7.5),
(7, 11, 1, 8.2),
(7, 12, 1, 6.5),
(8, 2, 1, 8.8),
(8, 5, 1, 7.0),
(8, 6, 1, 6.2),
(9, 20, 1, 8.5),
(9, 23, 1, 9.0),
(9, 16, 1, 7.8),
(10, 9, 1, 6.5),
(10, 11, 1, 7.0),
(10, 14, 1, 8.5),
(15, 10, 1, 7.8),
(19, 13, 1, 8.2),
(16, 17, 1, 6.5),
(20, 19, 1, 7.5),
(22, 24, 1, 8.5),
(21, 25, 1, 6.8),
(23, 26, 1, 7.2),
(24, 27, 1, 8.8);
Các bảng trong Database
1. Bảng Majors (Ngành học)
Bảng này lưu thông tin về các ngành học tại trường.
CREATE TABLE Majors (
MajorID SERIAL PRIMARY KEY,
MajorName VARCHAR(100) NOT NULL
);
- MajorID: Mã ngành học, khóa chính, tự động tăng.
- MajorName: Tên ngành học.
2. Bảng Students (Sinh viên)
Bảng này lưu trữ thông tin về sinh viên trong hệ thống.
CREATE TABLE Students (
StudentID SERIAL PRIMARY KEY,
FirstName VARCHAR(50) NOT NULL,
LastName VARCHAR(50) NOT NULL,
DateOfBirth DATE NOT NULL,
Gender VARCHAR(15) CHECK (Gender IN ('Male', 'Female', 'Other')),
Email VARCHAR(100) UNIQUE NOT NULL,
Phone VARCHAR(15) UNIQUE,
Address VARCHAR(255),
EnrollmentDate DATE NOT NULL,
MajorID INTEGER REFERENCES Majors(MajorID)
);
- StudentID: Mã sinh viên, khóa chính, tự động tăng.
- FirstName, LastName: Tên và họ của sinh viên.
- DateOfBirth: Ngày sinh.
- Gender: Giới tính (Male, Female, Other).
- Email: Email của sinh viên.
- Phone: Số điện thoại.
- Address: Địa chỉ của sinh viên.
- EnrollmentDate: Ngày nhập học.
- MajorID: Mã ngành học của sinh viên, tham chiếu đến bảng
Majors.
3. Bảng Teachers (Giảng viên)
Bảng này lưu trữ thông tin về các giảng viên.
CREATE TABLE Teachers (
TeacherID SERIAL PRIMARY KEY,
FirstName VARCHAR(50) NOT NULL,
LastName VARCHAR(50) NOT NULL,
Email VARCHAR(100) UNIQUE NOT NULL,
Phone VARCHAR(15) UNIQUE,
Address VARCHAR(255)
);
- TeacherID: Mã giảng viên, khóa chính, tự động tăng.
- FirstName, LastName: Tên và họ của giảng viên.
- Email: Email của giảng viên.
- Phone: Số điện thoại.
- Address: Địa chỉ của giảng viên.
4. Bảng Courses (Môn học)
Bảng này lưu thông tin về các môn học.
CREATE TABLE Courses (
CourseID SERIAL PRIMARY KEY,
CourseName VARCHAR(100) NOT NULL,
Credits INTEGER CHECK (Credits > 0)
);
- CourseID: Mã môn học, khóa chính, tự động tăng.
- CourseName: Tên môn học.
- Credits: Số tín chỉ của môn học.
5. Bảng MajorCourses (Môn học theo ngành)
Bảng này liên kết các ngành học với các môn học cố định mà sinh viên phải học.
CREATE TABLE MajorCourses (
MajorID INTEGER REFERENCES Majors(MajorID),
CourseID INTEGER REFERENCES Courses(CourseID),
PRIMARY KEY (MajorID, CourseID)
);
- MajorID: Mã ngành học.
- CourseID: Mã môn học.
- Mối quan hệ giữa ngành học và môn học là một-nhiều.
6. Bảng Classes (Lớp học)
Bảng này lưu thông tin về các lớp học, bao gồm thông tin môn học, giảng viên, và học kỳ.
CREATE TABLE Classes (
ClassID SERIAL PRIMARY KEY,
CourseID INTEGER REFERENCES Courses(CourseID),
TeacherID INTEGER REFERENCES Teachers(TeacherID),
Semester VARCHAR(20) NOT NULL,
Year INTEGER NOT NULL
);
- ClassID: Mã lớp học, khóa chính, tự động tăng.
- CourseID: Mã môn học, tham chiếu đến bảng
Courses. - TeacherID: Mã giảng viên, tham chiếu đến bảng
Teachers. - Semester: Học kỳ.
- Year: Năm học.
7. Bảng Enrollments (Đăng ký môn học)
Bảng này lưu trữ thông tin về việc sinh viên đăng ký học các lớp, bao gồm thông tin về lần đăng ký (Attempt) và điểm số của sinh viên.
CREATE TABLE Enrollments (
StudentID INTEGER REFERENCES Students(StudentID),
ClassID INTEGER REFERENCES Classes(ClassID),
Attempt INTEGER NOT NULL DEFAULT 1,
Grade DECIMAL(4,2) CHECK (Grade >= 0 AND Grade <= 10),
PRIMARY KEY (StudentID, ClassID, Attempt)
);
- StudentID: Mã sinh viên, tham chiếu đến bảng
Students. - ClassID: Mã lớp học, tham chiếu đến bảng
Classes. - Attempt: Số lần sinh viên đăng ký học môn này (ví dụ, lần đầu hoặc học lại, cải thiện điểm).
- Grade: Điểm số của sinh viên trong lớp học.
Đề thực hành (Practical Exam)
Câu hỏi 1
Viết truy vấn để hiển thị tất cả các giảng viên.

Câu hỏi 2
Viết truy vấn để hiển thị tất cả các sinh viên thuộc các ngành có MajorID = 2 hoặc MajorID = 3.

Câu hỏi 3
Viết truy vấn để lấy thông tin các lớp học mà giảng viên có họ là "Hoàng" và tên là "Văn Hiệp" phụ trách.
Hiển thị các cột như phía dưới.

Câu hỏi 4
Viết truy vấn để lấy danh sách sinh viên đã đăng ký ít nhất một trong các lớp thuộc các môn học "Data Structures & Algorithms", "Database Systems", hoặc "Web Development".
Hiển thị các cột như phía dưới, không hiển thị kết quả trùng nhau.
Kết quả sắp xếp theo CourseID tăng dần, sau đó theo StudentID tăng dần.

Câu hỏi 5
Viết truy vấn để hiển thị số lượng sinh viên đã đăng ký vào các khóa học trong năm 2025.
Hiển thị các cột như phía dưới.
Kết quả sắp xếp theo NumberOfEnrollments giảm dần, sau đó theo CourseID tăng dần.

Câu hỏi 6
Viết truy vấn để tìm các giảng viên có số lượng lớp dạy nhiều nhất trong từng năm.
Hiển thị các cột như phía dưới.
Kết quả sắp xếp theo Year tăng dần, sau đó theo TeacherID tăng dần.

Câu hỏi 7
Viết truy vấn để đếm số sinh viên duy nhất đã đăng ký môn học theo từng năm của ngành "Computer Science" và "Software Engineering".
Hiển thị các cột như phía dưới.

Câu hỏi 8
Tạo function GetNumberOfStudentsByCourseID để tính số lượng sinh viên duy nhất đã đăng ký một môn học cụ thể theo CourseID.
Sau khi viết xong, hãy kiểm tra lại function bằng câu lệnh SQL dưới.
SELECT CourseID, CourseName, GetNumberOfStudentsByCourseID(CourseID) AS NumberOfStudents
FROM Courses
WHERE CourseID IN (1, 2, 3, 4); -- Kiểm tra với một số CourseID cụ thể

Câu hỏi 9
Tạo trigger để xử lý dữ liệu khi thêm sinh viên mới vào bảng Students có tên trg_InsteadOfInsertStudent đảm bảo hai điều kiện:
- Nếu
Emailkhông chứa ký tự "@", tự động thêm@ezse.netvào cuối. - Nếu
MajorIDbị NULL, tự động đặtMajorIDthành ID của ngành "Software Engineering".
Sau khi viết xong, hãy kiểm tra lại trigger bằng câu SQL lệnh dưới.
-- Trường hợp thiếu @ -> Tự động thêm @ezse.net, MajorID có giá trị nên giữ nguyên
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID)
VALUES ('Nguyễn', 'Văn A', '2002-05-10', 'Male', 'nguyenvana', '0734567890', 'Hà Nội', '2024-04-02', 3);
-- Trường hợp thiếu MajorID -> Tự động gán MajorID của ngành Software Engineering
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID)
VALUES ('Trần', 'Thị B', '2001-08-20', 'Female', '[email protected]', '0678123456', 'HCM', '2024-04-02', NULL);
-- Kiểm tra kết quả
SELECT StudentID, FirstName, LastName, Email, MajorID FROM Students;
-- Chú ý: Vì Phone là Unique, khi test nhiều lần, bạn hãy thay đổi số điện thoại.

Câu hỏi 10
Xóa sinh viên có StudentID = 3 và StudentID = 5 khỏi bảng Students, đồng thời xóa các hàng liên quan trong bảng Enrollments.
Đáp án đề thực hành (PostgreSQL)
Câu hỏi 1
Viết truy vấn để hiển thị tất cả các giảng viên.

Đáp án:
SELECT *
FROM Teachers;
Câu hỏi 2
Viết truy vấn để hiển thị tất cả các sinh viên thuộc các ngành có MajorID = 2 hoặc MajorID = 3.

Đáp án:
SELECT *
FROM Students
WHERE MajorID IN (2, 3);
Câu hỏi 3
Viết truy vấn để lấy thông tin các lớp học mà giảng viên có họ là "Hoàng" và tên là "Văn Hiệp" phụ trách.
Hiển thị các cột như phía dưới.

Đáp án:
SELECT
c.ClassID,
c.CourseID,
co.CourseName,
c.Semester,
c.Year,
t.TeacherID,
t.FirstName,
t.LastName
FROM Classes c
JOIN Teachers t ON c.TeacherID = t.TeacherID
JOIN Courses co ON c.CourseID = co.CourseID
WHERE t.FirstName = 'Hoàng' AND t.LastName = 'Văn Hiệp';
Câu hỏi 4
Viết truy vấn để lấy danh sách sinh viên đã đăng ký ít nhất một trong các lớp thuộc các môn học "Data Structures & Algorithms", "Database Systems", hoặc "Web Development".
Hiển thị các cột như phía dưới, không hiển thị kết quả trùng nhau.
Kết quả sắp xếp theo CourseID tăng dần, sau đó theo StudentID tăng dần.

Đáp án:
SELECT DISTINCT
co.CourseID,
co.CourseName,
s.StudentID,
CONCAT(s.FirstName, ' ', s.LastName) AS StudentFullName,
s.DateOfBirth
FROM Students s
JOIN Enrollments e ON s.StudentID = e.StudentID
JOIN Classes c ON e.ClassID = c.ClassID
JOIN Courses co ON c.CourseID = co.CourseID
WHERE co.CourseName IN ('Data Structures & Algorithms', 'Database Systems', 'Web Development')
ORDER BY co.CourseID, s.StudentID;
Câu hỏi 5
Viết truy vấn để hiển thị số lượng sinh viên đã đăng ký vào các khóa học trong năm 2025.
Hiển thị các cột như phía dưới.
Kết quả sắp xếp theo NumberOfEnrollments giảm dần, sau đó theo CourseID tăng dần.

Đáp án:
SELECT
c.CourseID,
c.CourseName,
COUNT(e.StudentID) AS NumberOfEnrollments
FROM Courses c
LEFT JOIN Classes cl ON c.CourseID = cl.CourseID AND cl.Year = 2025
LEFT JOIN Enrollments e ON cl.ClassID = e.ClassID
GROUP BY c.CourseID, c.CourseName
ORDER BY NumberOfEnrollments DESC, c.CourseID ASC;
Câu hỏi 6
Viết truy vấn để tìm các giảng viên có số lượng lớp dạy nhiều nhất trong từng năm.
Hiển thị các cột như phía dưới.
Kết quả sắp xếp theo Year tăng dần, sau đó theo TeacherID tăng dần.

Đáp án:
WITH TeacherClasses AS (
SELECT
c.Year,
t.TeacherID,
CONCAT(t.FirstName, ' ', t.LastName) AS TeacherFullName,
COUNT(c.ClassID) AS NumberOfClasses
FROM Teachers t
LEFT JOIN Classes c ON t.TeacherID = c.TeacherID
GROUP BY c.Year, t.TeacherID, t.FirstName, t.LastName
),
MaxClasses AS (
SELECT
Year,
MAX(NumberOfClasses) AS MaxClasses
FROM TeacherClasses
GROUP BY Year
)
SELECT
tc.Year,
tc.TeacherID,
tc.TeacherFullName,
tc.NumberOfClasses
FROM TeacherClasses tc
JOIN MaxClasses mc ON tc.Year = mc.Year AND tc.NumberOfClasses = mc.MaxClasses
ORDER BY tc.Year, tc.TeacherID;
Câu hỏi 7
Viết truy vấn để đếm số sinh viên duy nhất đã đăng ký môn học theo từng năm của ngành "Computer Science" và "Software Engineering".
Hiển thị các cột như phía dưới.

Đáp án:
WITH StudentEnrollments AS (
SELECT
m.MajorID,
m.MajorName,
c.Year,
COUNT(DISTINCT e.StudentID) AS TotalStudents
FROM Enrollments e
JOIN Classes c ON e.ClassID = c.ClassID
JOIN Students s ON e.StudentID = s.StudentID
JOIN Majors m ON s.MajorID = m.MajorID
WHERE m.MajorName IN ('Computer Science', 'Software Engineering')
GROUP BY m.MajorID, m.MajorName, c.Year
),
MajorsCTE AS (
SELECT MajorID, MajorName
FROM Majors
WHERE MajorName IN ('Computer Science', 'Software Engineering')
),
Years AS (
SELECT DISTINCT Year FROM Classes
),
AllCombinations AS (
SELECT y.Year, m.MajorID, m.MajorName
FROM MajorsCTE m CROSS JOIN Years y
)
SELECT
ac.Year,
ac.MajorID,
ac.MajorName,
COALESCE(se.TotalStudents, 0) AS TotalStudents
FROM AllCombinations ac
LEFT JOIN StudentEnrollments se
ON ac.MajorID = se.MajorID
AND ac.Year = se.Year
ORDER BY ac.Year, ac.MajorID;
Câu hỏi 8
Tạo function GetNumberOfStudentsByCourseID để tính số lượng sinh viên duy nhất đã đăng ký một môn học cụ thể theo CourseID.
Sau khi viết xong, hãy kiểm tra lại function bằng câu lệnh SQL dưới.
SELECT CourseID, CourseName, GetNumberOfStudentsByCourseID(CourseID) AS NumberOfStudents
FROM Courses
WHERE CourseID IN (1, 2, 3, 4); -- Kiểm tra với một số CourseID cụ thể

Đáp án:
CREATE FUNCTION GetNumberOfStudentsByCourseID(p_course_id INTEGER)
RETURNS INTEGER AS $$
BEGIN
RETURN (
SELECT COUNT(DISTINCT e.StudentID)
FROM Enrollments e
JOIN Classes c ON e.ClassID = c.ClassID
WHERE c.CourseID = p_course_id
);
END;
$$ LANGUAGE plpgsql;
-- Kiểm tra function
SELECT CourseID, CourseName, GetNumberOfStudentsByCourseID(CourseID) AS NumberOfStudents
FROM Courses
WHERE CourseID IN (1, 2, 3, 4);
Chú thích:
- PostgreSQL không dùng
@cho tham số như SQL Server, thay vào đó là tên tham số trực tiếp (p_course_id).
Câu hỏi 9
Tạo trigger để xử lý dữ liệu khi thêm sinh viên mới vào bảng Students có tên trg_InsteadOfInsertStudent đảm bảo hai điều kiện:
- Nếu
Emailkhông chứa ký tự "@", tự động thêm@ezse.netvào cuối. - Nếu
MajorIDbị NULL, tự động đặtMajorIDthành ID của ngành "Software Engineering".
Sau khi viết xong, hãy kiểm tra lại trigger bằng câu SQL lệnh dưới.
-- Trường hợp thiếu @ -> Tự động thêm @ezse.net, MajorID có giá trị nên giữ nguyên
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID)
VALUES ('Nguyễn', 'Văn A', '2002-05-10', 'Male', 'nguyenvana', '0734567890', 'Hà Nội', '2024-04-02', 3);
-- Trường hợp thiếu MajorID -> Tự động gán MajorID của ngành Software Engineering
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID)
VALUES ('Trần', 'Thị B', '2001-08-20', 'Female', '[email protected]', '0678123456', 'HCM', '2024-04-02', NULL);
-- Kiểm tra kết quả
SELECT StudentID, FirstName, LastName, Email, MajorID FROM Students;
-- Chú ý: Vì Phone là Unique, khi test nhiều lần, bạn hãy thay đổi số điện thoại.

Đáp án:
CREATE OR REPLACE FUNCTION trg_InsteadOfInsertStudent()
RETURNS TRIGGER AS $$
DECLARE
v_software_engineering_id INTEGER;
BEGIN
-- Lấy MajorID của ngành "Software Engineering"
SELECT MajorID INTO v_software_engineering_id
FROM Majors
WHERE MajorName = 'Software Engineering';
-- Xử lý Email và MajorID trước khi chèn
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID)
VALUES (
NEW.FirstName,
NEW.LastName,
NEW.DateOfBirth,
NEW.Gender,
CASE
WHEN NEW.Email NOT LIKE '%@%' THEN NEW.Email || '@ezse.net'
ELSE NEW.Email
END,
NEW.Phone,
NEW.Address,
NEW.EnrollmentDate,
COALESCE(NEW.MajorID, v_software_engineering_id)
);
RETURN NULL; -- Với trigger INSTEAD OF, không cần tiếp tục chèn bản gốc
END;
$$ LANGUAGE plpgsql;
-- Tạo trigger
DROP TRIGGER IF EXISTS trg_InsteadOfInsertStudent ON Students;
CREATE TRIGGER trg_InsteadOfInsertStudent
BEFORE INSERT ON Students
FOR EACH ROW
EXECUTE FUNCTION trg_InsteadOfInsertStudent();
-- Kiểm tra trigger
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID)
VALUES ('Nguyễn', 'Văn A', '2002-05-10', 'Male', 'nguyenvana', '0734567890', 'Hà Nội', '2024-04-02', 3);
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID)
VALUES ('Trần', 'Thị B', '2001-08-20', 'Female', '[email protected]', '0678123456', 'HCM', '2024-04-02', NULL);
SELECT StudentID, FirstName, LastName, Email, MajorID FROM Students;
Chú thích:
- PostgreSQL không có trigger
INSTEAD OFtrực tiếp trên bảng cơ sở dữ liệu, chỉ hỗ trợ trên view. Ở đây, tôi sử dụng triggerBEFORE INSERTđể xử lý trước khi chèn. ||là toán tử nối chuỗi trong PostgreSQL thay vìCONCATnhư SQL Server trong trường hợp này.COALESCEthay choISNULL.
Câu hỏi 10
Xóa sinh viên có StudentID = 3 và StudentID = 5 khỏi bảng Students, đồng thời xóa các hàng liên quan trong bảng Enrollments.
Đáp án:
-- Xóa dữ liệu liên quan trong bảng Enrollments trước
DELETE FROM Enrollments
WHERE StudentID IN (3, 5);
-- Xóa sinh viên trong bảng Students
DELETE FROM Students
WHERE StudentID IN (3, 5);
Giải thích:
Xóa dữ liệu liên quan trong
Enrollmentstrước- Bảng
Enrollmentscó khóa ngoạiStudentIDtham chiếu đếnStudents. - Nếu không có
ON DELETE CASCADE, ta phải xóa các bản ghi liên quan trongEnrollmentstrước.
- Bảng
Xóa dữ liệu chính trong
Students- Sau khi xóa các bản ghi trong
Enrollments, ta có thể xóa sinh viên cóStudentID = 3vàStudentID = 5mà không vi phạm ràng buộc.
- Sau khi xóa các bản ghi trong
Trường hợp có ON DELETE CASCADE trên Enrollments.StudentID
Nếu khi tạo bảng Enrollments, ta đã thiết lập:
StudentID INTEGER REFERENCES Students(StudentID) ON DELETE CASCADE
thì chỉ cần chạy:
DELETE FROM Students WHERE StudentID IN (3, 5);
Các bản ghi trong Enrollments sẽ tự động bị xóa.
