MySQL

Cảm ơn bạn đã đăng ký khóa học!

Tài liệu về MySQL 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 MySQL và Công Cụ Quản Lý

1. Cài Đặt MySQL

Bạn có thể tải MySQL từ trang chính thức:

MySQL có nhiều phiên bản phù hợp với từng nhu cầu:

Phiên bảnĐặc điểm
MySQL Enterprise EditionDành cho doanh nghiệp lớn, hỗ trợ đầy đủ tính năng, có hỗ trợ kỹ thuật.
MySQL Community EditionMiễn phí, mã nguồn mở, phù hợp cho học tập, phát triển và ứng dụng nhỏ.
MySQL ClusterDành cho hệ thống phân tán, đảm bảo tính sẵn sàng cao.

2. Cài Đặt MySQL Workbench

MySQL Workbench là công cụ quản lý và phát triển cơ sở dữ liệu MySQL hiệu quả. Tải tại:

3. Kết Nối và Làm Việc Với MySQL

  1. Mở MySQL Workbench.
  2. Tạo kết nối mới:
    • Hostname: localhost (nếu chạy trên máy cục bộ).
    • Port: Mặc định là 3306.
    • Username: root (hoặc tài khoản bạn đã tạo).
    • Password: Mật khẩu bạn đã thiết lập khi cài đặt.
  3. Nhấn OK để kết nối.

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;

Sao lưu cơ sở dữ liệu

MySQL không có lệnh BACKUP tích hợp như SQL Server. Sử dụng công cụ mysqldump từ dòng lệnh:

mysqldump -u root -p StudentManagementSystem > StudentManagementSystem.sql

Khôi phục cơ sở dữ liệu

Sử dụng lệnh mysql để khôi phục từ file sao lưu:

mysql -u root -p StudentManagementSystem < StudentManagementSystem.sql

Chọn cơ sở dữ liệu để làm việc (USE)

USE StudentManagementSystem;

Liệt kê cơ sở dữ liệu

Liệt kê tất cả các cơ sở dữ liệu hiện có trong MySQL:

SHOW DATABASES;

Quản lý bảng (Table)

Tạo bảng (CREATE TABLE)

CREATE TABLE Teachers (
    TeacherID INT AUTO_INCREMENT PRIMARY KEY,
    Name VARCHAR(50) NOT NULL
);

Lưu ý: MySQL sử dụng AUTO_INCREMENT thay vì IDENTITY như SQL Server.

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 Email VARCHAR(100);

Thay đổi kiểu dữ liệu của cột

ALTER TABLE Students MODIFY COLUMN Email VARCHAR(150);

Lưu ý: MySQL dùng MODIFY COLUMN thay vì ALTER COLUMN.

Đổi tên cột

ALTER TABLE Students CHANGE Email StudentEmail VARCHAR(100);

Lưu ý: MySQL dùng CHANGE thay vì sp_rename.

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

Liệt kê danh sách các bảng trong cơ sở dữ liệu hiện tại:

SHOW TABLES;

Kiểu dữ liệu (Data Types)

Kiểu số (Numeric Data Types)

Kiểu dữ liệuMô tảVí dụ
INTSố nguyên 4 byte, phạm vi từ -2,147,483,648 đến 2,147,483,647.10, 100, -50
BIGINTSố nguyên 8 byte, phạm vi từ -9,223,372,036,854,775,808 đến 9,223,372,036,854,775,807.9223372036854775807
SMALLINTSố nguyên 2 byte, phạm vi từ -32,768 đến 32,767.32767
TINYINTSố nguyên 1 byte, phạm vi từ 0 đến 255 (hoặc -128 đến 127 nếu có dấu).255
DECIMAL(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.DECIMAL(10,2)12345.67
FLOATSố thực (floating-point), không cố định số chữ số sau dấu thập phân.3.14159
DOUBLESố thực độ chính xác cao hơn FLOAT.3.14159265359

Kiểu chuỗi (String Data Types)

Kiểu dữ liệuMô 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ự có độ dài thay đổi, tối đa n ký tự.'John Doe'
TEXTChuỗi văn bản dài, tối đa 65,535 ký tự.'Lorem ipsum...'
MEDIUMTEXTChuỗi văn bản dài, tối đa 16,777,215 ký tự.'Larger text...'
LONGTEXTChuỗi văn bản rất dài, tối đa 4,294,967,295 ký tự.'Very large text...'

Lưu ý: MySQL không có NVARCHAR như SQL Server. Để hỗ trợ Unicode, thêm CHARACTER SET utf8mb4 khi tạo bảng:

CREATE TABLE Teachers (
    TeacherID INT AUTO_INCREMENT PRIMARY KEY,
    Name VARCHAR(50) CHARACTER SET utf8mb4 NOT NULL
);

Kiểu ngày và thời gian (Date & Time Data Types)

Kiểu dữ liệuMô tảVí dụ
DATELưu ngày tháng, phạm vi từ 1000-01-01 đến 9999-12-31.'2025-03-01'
TIMELưu thời gian, phạm vi từ -838:59:59 đến 838:59:59.'12:30:45'
DATETIMELưu ngày và giờ, phạm vi từ 1000-01-01 00:00:00 đến 9999-12-31 23:59:59.'2025-03-01 12:30:45'
TIMESTAMPLưu ngày giờ, tự động cập nhật khi thay đổi bản ghi, phạm vi từ 1970-01-01 00:00:00 UTC đến 2038-01-19 03:14:07 UTC.'2025-03-01 12:30:45'

Kiểu dữ liệu nhị phân và định danh (Binary & Unique Identifier)

Kiểu dữ liệuMô tảVí dụ
BINARY(n)Chuỗi nhị phân cố định độ dài n.0x1A2B3C
VARBINARY(n)Chuỗi nhị phân có độ dài thay đổi, tối đa n byte.0x1A2B3C
BLOBDữ liệu nhị phân lớn, tối đa 65,535 byte.Hình ảnh, file

Lưu ý: MySQL không có kiểu UNIQUEIDENTIFIER như SQL Server. Để tạo ID duy nhất, sử dụng hàm UUID():

SELECT UUID(); -- Trả về: '550e8400-e29b-41d4-a716-446655440000'

Ví dụ tạo bảng với kiểu dữ liệu phổ biến

CREATE TABLE Employees (
    EmployeeID INT AUTO_INCREMENT PRIMARY KEY,
    Name VARCHAR(100) CHARACTER SET utf8mb4 NOT NULL,
    Birthdate DATE,
    WorkStartTime TIME,
    Salary DECIMAL(10,2),
    ProfilePicture BLOB,
    IsActive TINYINT(1) DEFAULT 1
);

Ví dụ tạo bảng với tất cả kiểu dữ liệu

CREATE TABLE AllDataTypesExample (
    ID CHAR(36) DEFAULT (UUID()) PRIMARY KEY, -- ID duy nhất, sử dụng UUID thay cho UNIQUEIDENTIFIER
    IntNumber INT NOT NULL, -- Số nguyên 4 byte
    BigIntNumber BIGINT, -- Số nguyên lớn 8 byte
    SmallIntNumber SMALLINT, -- Số nguyên nhỏ 2 byte
    TinyIntNumber TINYINT, -- Số nguyên rất nhỏ 1 byte
    DecimalNumber DECIMAL(10,2), -- Số thập phân
    FloatNumber FLOAT, -- Số thực
    DoubleNumber DOUBLE, -- Số thực độ chính xác cao
    IsActive TINYINT(1) DEFAULT 1, -- Boolean (0: false, 1: true)
    FixedText CHAR(10), -- Chuỗi cố định 10 ký tự
    VariableText VARCHAR(255), -- Chuỗi thay đổi tối đa 255 ký tự
    LongText LONGTEXT, -- Văn bản rất dài
    BirthDate DATE, -- Chỉ chứa ngày
    WorkStartTime TIME, -- Chỉ chứa giờ
    FullDateTime DATETIME, -- Ngày và giờ
    ProfilePicture BLOB, -- Lưu trữ dữ liệu nhị phân
    CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP -- Ngày tạo, mặc định thời điểm hiện tại
);

Lưu ý:

  • MySQL không có MONEY như SQL Server, thay vào đó dùng DECIMAL.
  • TEXT trong MySQL không bị lỗi thời như SQL Server, nhưng có các biến thể như MEDIUMTEXT, LONGTEXT.
  • DATETIME2 không tồn tại trong MySQL, chỉ có DATETIME hoặc TIMESTAMP.

Biến (Variable)

Trong MySQL, biến được sử dụng trong các script hoặc stored procedure/function. Có hai loại biến chính: biến người dùng (user-defined) và biến hệ thống (system variables).

Khai báo biến (DECLARE)

Biến trong MySQL được khai báo trong stored procedure hoặc function bằng DECLARE:

DELIMITER //
CREATE PROCEDURE Example()
BEGIN
    DECLARE StudentName VARCHAR(50);
    DECLARE BirthYear INT;
END //
DELIMITER ;

Lưu ý: MySQL yêu cầu thay đổi DELIMITER để viết các khối lệnh như stored procedure.

Gán giá trị cho biến (SET, SELECT)

Gán giá trị bằng SET

SET @StudentName = 'Hoàng Hiệp';
SELECT @StudentName;

Gán giá trị bằng SELECT

SELECT @BirthYear := YEAR(Birthdate)
FROM Students WHERE StudentID = 1;

Lưu ý: MySQL dùng := trong SELECT để gán giá trị, hoặc dùng SET.

Sử dụng biến

SET @Year = 2000;
SELECT * FROM Students WHERE YEAR(Birthdate) = @Year;

Biến bảng (Table Variable)

MySQL không hỗ trợ biến bảng như SQL Server. Thay vào đó, sử dụng bảng tạm (temporary table):

CREATE TEMPORARY TABLE StudentTable (
    StudentID INT PRIMARY KEY,
    Name VARCHAR(50),
    Birthdate DATE
);

INSERT INTO StudentTable (StudentID, Name, Birthdate)
VALUES (1, 'John Doe', '2000-05-15');

SELECT * FROM StudentTable;

DROP TEMPORARY TABLE StudentTable;

Biến hệ thống

MySQL có các biến hệ thống bắt đầu bằng @@:

  • @@version - Phiên bản MySQL.
  • @@row_count - Số dòng bị ảnh hưởng bởi truy vấn trước đó.
SELECT @@version;
SELECT ROW_COUNT();

Lưu ý: MySQL dùng ROW_COUNT() thay vì @@ROWCOUNT.

Hàm (Functions)

Hàm xử lý chuỗi (String Functions)

HàmChức năngVí dụ
LENGTH(string)Trả về độ dài chuỗi (byte)LENGTH('Hello')5
CHAR_LENGTH(string)Trả về số ký tựCHAR_LENGTH('Xin chào')8
LEFT(string, n)Lấy n ký tự bên tráiLEFT('MySQL', 2)'My'
RIGHT(string, n)Lấy n ký tự bên phảiRIGHT('MySQL', 3)'SQL'
SUBSTRING(string, start, length)Cắt chuỗi conSUBSTRING('MySQL', 3, 3)'SQL'
UPPER(string)Chuyển thành chữ in hoaUPPER('mysql')'MYSQL'
LOWER(string)Chuyển thành chữ thườngLOWER('MySQL')'mysql'
TRIM(string)Xóa khoảng trắng hai bênTRIM(' MySQL ')'MySQL'
REPLACE(string, old, new)Thay thế chuỗi conREPLACE('Hello MySQL', 'MySQL', 'World')'Hello World'
LOCATE(substring, string)Vị trí của chuỗi conLOCATE('SQL', 'MySQL')3
CONCAT(string1, string2, …)Nối chuỗiCONCAT('My', 'SQL')'MySQL'

Lưu ý: MySQL không có CHARINDEX, dùng LOCATE thay thế.

Hàm xử lý số (Numeric Functions)

HàmChức năngVí dụ
ABS(number)Trị tuyệt đốiABS(-5)5
CEIL(number)Làm tròn lênCEIL(4.2)5
FLOOR(number)Làm tròn xuốngFLOOR(4.9)4
ROUND(number, decimals)Làm tròn sốROUND(4.567, 2)4.57
POW(base, exponent)Lũy thừaPOW(2, 3)8
SQRT(number)Căn bậc haiSQRT(9)3
RAND()Tạo số ngẫu nhiên từ 0 đến 1RAND()0.684

Lưu ý: MySQL dùng POW thay vì POWER.

Hàm xử lý ngày tháng (Date Functions)

HàmChức năngVí dụ
NOW()Trả về ngày giờ hiện tạiNOW()'2025-03-15 10:15:30'
CURDATE()Trả về ngày hiện tạiCURDATE()'2025-03-15'
CURTIME()Trả về giờ hiện tạiCURTIME()'10:15:30'
YEAR(date)Trả về nămYEAR('2025-03-15')2025
MONTH(date)Trả về thángMONTH('2025-03-15')3
DAY(date)Trả về ngàyDAY('2025-03-15')15
DATE_ADD(date, INTERVAL value unit)Cộng thêm giá trịDATE_ADD('2025-03-15', INTERVAL 10 DAY)'2025-03-25'
DATEDIFF(date1, date2)Khoảng cách giữa hai ngày (ngày)DATEDIFF('2025-03-10', '2025-03-01')9
DATE_FORMAT(date, format)Định dạng ngàyDATE_FORMAT(NOW(), '%d/%m/%Y')'15/03/2025'

Lưu ý:

  • MySQL không có EOMONTH, nhưng có thể dùng LAST_DAY:
    SELECT LAST_DAY('2025-03-15'); -- Trả về '2025-03-31'
    

Hàm nâng cao (Advanced Functions)

Hàm chuyển đổi dữ liệu

HàmChức năngVí dụ
CAST(expression AS type)Chuyển kiểu dữ liệuCAST(123 AS CHAR)'123'
CONVERT(expression, type)Chuyển kiểu dữ liệuCONVERT(123, CHAR)'123'

Hàm xử lý giá trị NULL

HàmChức năngVí dụ
COALESCE(val1, val2, …)Trả về giá trị không NULL đầu tiênCOALESCE(NULL, 'MySQL', 'World')'MySQL'
IFNULL(expression, replacement)Thay thế giá trị NULLIFNULL(NULL, 'N/A')'N/A'
NULLIF(val1, val2)Trả về NULL nếu hai giá trị bằng nhauNULLIF(10, 10)NULL

Ràng buộc dữ liệu

Ràng buộc giúp duy trì tính toàn vẹn dữ liệu bằng cách kiểm soát các giá trị được nhập vào bảng.

Tổng quan các ràng buộc

Ràng buộcPhân loạiChức năng
NOT NULLThuộc tính cộtKhông cho phép giá trị NULL trong cột.
UNIQUERàng buộcĐảm bảo tất cả giá trị trong cột là duy nhất.
PRIMARY KEYRàng buộcKết hợp NOT NULLUNIQUE, mỗi bảng chỉ có một khóa chính.
FOREIGN KEYRàng buộcTạo mối quan hệ giữa hai bảng, đảm bảo dữ liệu hợp lệ.
CHECKRàng buộcKiểm tra giá trị nhập vào thỏa mãn điều kiện cụ thể.
DEFAULTThuộc tính cộtGán giá trị mặc định cho cột nếu không có giá trị nhập.
AUTO_INCREMENTThuộc tính cộtTạo cột tự động tăng giá trị cho mỗi bản ghi.

NOT NULL

Bắt buộc cột phải có giá trị, không được để trống.

CREATE TABLE Teachers (
    TeacherID INT AUTO_INCREMENT PRIMARY KEY,
    Name VARCHAR(50) NOT NULL
);

UNIQUE

Đảm bảo giá trị trong cột không bị trùng lặp.

CREATE TABLE Doctors (
    DoctorID INT AUTO_INCREMENT PRIMARY KEY,
    Email VARCHAR(100) UNIQUE
);

PRIMARY KEY

Định nghĩa khóa chính để xác định duy nhất mỗi bản ghi.

CREATE TABLE Departments (
    DeptID INT PRIMARY KEY,
    DeptName VARCHAR(50) NOT NULL
);

Hoặc trên nhiều cột:

CREATE TABLE CourseRegistrations (
    StudentID INT,
    CourseID INT,
    PRIMARY KEY (StudentID, CourseID)
);

FOREIGN KEY

Tạo khóa ngoại, đảm bảo dữ liệu giữa các bảng có liên kết hợp lệ.

CREATE TABLE Enrollments (
    StudentID INT,
    CourseID INT,
    FOREIGN KEY (StudentID) REFERENCES Students(StudentID) 
        ON DELETE CASCADE 
        ON UPDATE CASCADE,
    FOREIGN KEY (CourseID) REFERENCES Courses(CourseID) 
        ON DELETE SET NULL 
        ON UPDATE NO ACTION
);
  • ON DELETE CASCADE: Xóa bản ghi trong bảng cha sẽ xóa các bản ghi liên quan trong bảng con.
  • ON UPDATE CASCADE: Cập nhật khóa chính trong bảng cha sẽ cập nhật khóa ngoại trong bảng con.
  • ON DELETE SET NULL: Xóa bản ghi trong bảng cha sẽ đặt giá trị khóa ngoại trong bảng con thành NULL.
  • ON UPDATE NO ACTION: Từ chối cập nhật nếu vi phạm ràng buộc khóa ngoại.

CHECK

Đảm bảo dữ liệu nhập vào thỏa mãn điều kiện.

CREATE TABLE Employees (
    EmployeeID INT AUTO_INCREMENT PRIMARY KEY,
    Age INT CHECK (Age >= 18)
);

Lưu ý: Một số engine như MyISAM không hỗ trợ CHECK. Dùng engine InnoDB để hỗ trợ đầy đủ.

DEFAULT

Đặt giá trị mặc định cho cột khi không có giá trị nhập.

CREATE TABLE Orders (
    OrderID INT PRIMARY KEY,
    OrderDate DATETIME DEFAULT CURRENT_TIMESTAMP
);

AUTO_INCREMENT

Tạo cột có giá trị tự động tăng, thường dùng cho khóa chính.

CREATE TABLE Employees (
    EmployeeID INT AUTO_INCREMENT PRIMARY KEY,
    Name VARCHAR(100) NOT NULL
);
  • AUTO_INCREMENT bắt đầu từ 1 và tăng 1 mặc định. Có thể thay đổi:
    ALTER TABLE Employees AUTO_INCREMENT = 100; -- Bắt đầu từ 100
    

Lấy giá trị AUTO_INCREMENT mới nhất

SELECT LAST_INSERT_ID(); -- Lấy giá trị AUTO_INCREMENT vừa chèn

Đặt lại giá trị AUTO_INCREMENT

ALTER TABLE Employees AUTO_INCREMENT = 1; -- Đặt lại về 1

Ví dụ tạo bảng có đầy đủ ràng buộc

CREATE TABLE Departments (
    DeptID INT PRIMARY KEY,
    DeptName VARCHAR(100) NOT NULL UNIQUE
);

CREATE TABLE Employees (
    EmployeeID INT AUTO_INCREMENT PRIMARY KEY,
    Name VARCHAR(100) NOT NULL,
    Email VARCHAR(100) UNIQUE,
    Birthdate DATE CHECK (Birthdate >= '1990-01-01'),
    Salary DECIMAL(10,2) DEFAULT 1000.00,
    DepartmentID INT,
    FOREIGN KEY (DepartmentID) 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ó StudentID = 4:

UPDATE Students 
SET Birthdate = '2000-06-15' 
WHERE StudentID = 4;

Xóa dữ liệu (DELETE)

Xóa sinh viên có StudentID = 2:

DELETE FROM Students 
WHERE StudentID = 2;

Lưu ý: Nếu không có WHERE, toàn bộ dữ liệu trong bảng sẽ bị xóa.

Xóa toàn bộ dữ liệu (TRUNCATE TABLE)

Xóa toàn bộ dữ liệu trong bảng và đặt lại giá trị AUTO_INCREMENT:

TRUNCATE TABLE Students;

Sao chép dữ liệu vào bảng mới (CREATE TABLE AS)

Tạo bảng mới và sao chép dữ liệu từ bảng hiện có:

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 (INSERT ... ON DUPLICATE KEY UPDATE)

MySQL không có MERGE như SQL Server, thay vào đó dùng INSERT ... ON DUPLICATE KEY UPDATE:

INSERT INTO StudentsBackup (StudentID, Name, Birthdate)
SELECT StudentID, Name, Birthdate FROM Students
ON DUPLICATE KEY UPDATE 
    Name = VALUES(Name), 
    Birthdate = VALUES(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 trước khi thêm mới:

IF EXISTS (SELECT 1 FROM Students WHERE Name = 'Hoàng Hiệp') THEN
    SELECT 'Sinh viên đã tồn tại';
ELSE
    INSERT INTO Students (Name, Birthdate) VALUES ('Hoàng Hiệp', '2001-05-20');
END IF;

Lưu ý: Cú pháp này dùng trong stored procedure hoặc script với DELIMITER.

Lấy dữ liệu bị ảnh hưởng (RETURNING)

MySQL không có OUTPUT như SQL Server. Để lấy dữ liệu bị ảnh hưởng, sử dụng truy vấn riêng:

SELECT StudentID, Name 
FROM Students 
WHERE Birthdate < '2000-01-01';

DELETE FROM Students 
WHERE Birthdate < '2000-01-01';

Toán tử và điều kiện lọc

Toán tử so sánh

Các toán tử phổ biến trong MySQL:

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
BETWEENTrong khoảng
INTrong danh sách
LIKEKhớp mẫu ký tự
AND
ORHoặc
NOTPhủ đị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%';

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;

IS NULL và IS NOT NULL (tiếp tục từ phần trước)

Lấy danh sách sinh viên đã có ngày sinh:

SELECT * FROM Students WHERE Birthdate IS NOT NULL;

IF ELSE

IF ELSE trong MySQL được sử dụng trong stored procedure hoặc function để điều khiển luồng thực thi.

Cú pháp

DELIMITER //
CREATE PROCEDURE CheckStudentCount()
BEGIN
    DECLARE TotalStudents INT;

    SELECT COUNT(*) INTO TotalStudents FROM Students;

    IF TotalStudents > 100 THEN
        SELECT 'Có hơn 100 sinh viên trong hệ thống.';
    ELSE
        SELECT 'Số lượng sinh viên ít hơn hoặc bằng 100.';
    END IF;
END //
DELIMITER ;

Lưu ý: MySQL không hỗ trợ IF ELSE trực tiếp trong truy vấn SELECT như SQL Server. Thay vào đó, dùng IF() hoặc CASE.

IF (Hàm)

Hàm IF() trong MySQL dùng trong truy vấn để thay thế IIF của SQL Server.

Cú pháp

IF(điều_kiện, giá_trị_nếu_đúng, giá_trị_nếu_sai)

Kiểm tra sinh viên đủ 18 tuổi:

SELECT 
    StudentID, 
    Name, 
    IF(DATEDIFF(CURDATE(), Birthdate) / 365 >= 18, 'Đủ tuổi', 'Chưa đủ tuổi') AS AgeStatus
FROM Students;

CASE WHEN

CASE WHEN hỗ trợ nhiều điều kiện và có thể dùng trong SELECT, WHERE, ORDER BY, v.v.

Cú pháp 1

CASE 
    WHEN điều_kiện_1 THEN giá_trị_1
    WHEN điều_kiện_2 THEN giá_trị_2
    ELSE giá_trị_mặc_định
END

Xác định nhóm tuổi của sinh viên:

SELECT 
    Name, 
    CASE 
        WHEN DATEDIFF(CURDATE(), Birthdate) / 365 < 18 THEN 'Teen'
        WHEN DATEDIFF(CURDATE(), Birthdate) / 365 BETWEEN 18 AND 24 THEN 'Young Adult'
        ELSE 'Adult'
    END AS AgeGroup
FROM Students;

Cú pháp 2

CASE biểu_thức
    WHEN giá_trị_1 THEN kết_quả_1
    WHEN giá_trị_2 THEN kết_quả_2
    ELSE kết_quả_mặc_định
END

Xác định phân loại khóa học dựa trên CourseID:

SELECT 
    CourseID, 
    Name,
    CASE CourseID
        WHEN 1 THEN 'Toán học'
        WHEN 2 THEN 'Lập trình'
        ELSE 'Ngôn ngữ'
    END AS CourseCategory
FROM Courses;

Truy vấn dữ liệu

SELECT

Truy vấn dữ liệu từ bảng:

SELECT * FROM Students;

Lấy tên và ngày sinh:

SELECT Name, Birthdate FROM Students;

SELECT DISTINCT

Loại bỏ các giá trị trùng lặp:

SELECT DISTINCT Name FROM Students;

WHERE

Lọc dữ liệu theo điều kiện:

SELECT * FROM Students WHERE Birthdate > '2001-01-01';

ORDER BY

Sắp xếp kết quả:

SELECT * FROM Students ORDER BY Name ASC;
SELECT * FROM Students ORDER BY Birthdate DESC;

LIMIT

Giới hạn số lượng kết quả (tương đương TOP trong SQL Server):

SELECT * FROM Students ORDER BY Birthdate DESC LIMIT 3; -- Lấy 3 sinh viên trẻ nhất

Kết hợp bảng

INNER JOIN

Lấy dữ liệu chỉ khi có sự liên kết giữa các bảng:

SELECT s.StudentID, s.Name AS StudentName, c.CourseID, c.Name AS CourseName
FROM Students s
INNER JOIN Enrollments e ON s.StudentID = e.StudentID
INNER JOIN Courses c ON e.CourseID = c.CourseID;

LEFT JOIN

Lấy tất cả dữ liệu từ bảng bên trái:

SELECT s.StudentID, s.Name AS StudentName, c.CourseID, c.Name AS CourseName
FROM Students s
LEFT JOIN Enrollments e ON s.StudentID = e.StudentID
LEFT JOIN Courses c ON e.CourseID = c.CourseID;

RIGHT JOIN

Lấy tất cả dữ liệu từ bảng bên phải:

SELECT s.StudentID, s.Name AS StudentName, c.CourseID, c.Name AS CourseName
FROM Students s
RIGHT JOIN Enrollments e ON s.StudentID = e.StudentID
RIGHT JOIN Courses c ON e.CourseID = c.CourseID;

FULL JOIN

MySQL không hỗ trợ FULL JOIN trực tiếp. Dùng LEFT JOIN kết hợp RIGHT JOIN với UNION:

SELECT s.StudentID, s.Name AS StudentName, c.CourseID, c.Name AS CourseName
FROM Students s
LEFT JOIN Enrollments e ON s.StudentID = e.StudentID
LEFT JOIN Courses c ON e.CourseID = c.CourseID
UNION
SELECT s.StudentID, s.Name AS StudentName, c.CourseID, c.Name AS CourseName
FROM Students s
RIGHT JOIN Enrollments e ON s.StudentID = e.StudentID
RIGHT JOIN Courses c ON e.CourseID = c.CourseID
WHERE s.StudentID IS NULL;

CROSS JOIN

Kết hợp tất cả các hàng của hai bảng:

SELECT s.Name AS StudentName, c.Name AS CourseName
FROM Students s
CROSS JOIN Courses c;

SELF JOIN

Nối bảng với chính nó:

ALTER TABLE Students 
ADD MentorID INT NULL;

INSERT INTO Students (Name, MentorID) VALUES 
('Nguyen Van A', 1),
('Tran Thi B', 2);

SELECT s1.StudentID, s1.Name AS StudentName, 
       s1.MentorID AS MentorID, s2.Name AS MentorName
FROM Students s1
LEFT JOIN Students s2 ON s1.MentorID = s2.StudentID;

UNION

Kết hợp dữ liệu từ nhiều truy vấn, 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, không loại bỏ trùng lặp:

SELECT Name FROM Students
UNION ALL
SELECT Name FROM Courses;

EXCEPT và INTERSECT

MySQL không hỗ trợ EXCEPT hoặc INTERSECT trực tiếp. Dùng subquery hoặc LEFT JOIN để thay thế:

Thay thế EXCEPT

Lấy tên trong Students không có trong Courses:

SELECT Name FROM Students
WHERE Name NOT IN (SELECT Name FROM Courses);

Thay thế INTERSECT

Lấy tên xuất hiện trong cả hai bảng:

SELECT Name FROM Students
WHERE Name IN (SELECT Name FROM Courses);

Hàm tổng hợp

COUNT

Đếm số lượng bản ghi:

SELECT COUNT(*) AS TotalStudents FROM Students;

SUM

Tính tổng giá trị:

ALTER TABLE Enrollments ADD Fee INT;

SELECT SUM(Fee) AS TotalFees FROM Enrollments;

AVG

Tính giá trị trung bình:

SELECT AVG(Fee) AS AverageFee 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.CourseID, c.Name, SUM(e.Fee) AS TotalFee
FROM Courses c
LEFT JOIN Enrollments e ON c.CourseID = e.CourseID
GROUP BY c.CourseID, c.Name;

HAVING

Lọc dữ liệu sau khi tổng hợp:

SELECT c.CourseID, c.Name, SUM(e.Fee) AS TotalFee
FROM Courses c
LEFT JOIN Enrollments e ON c.CourseID = e.CourseID
GROUP BY c.CourseID, c.Name
HAVING SUM(e.Fee) >= 20;

COUNT DISTINCT

Đếm số lượng giá trị không trùng lặp:

SELECT c.CourseID, c.Name, COUNT(DISTINCT e.StudentID) AS EnrolledStudents
FROM Courses c
LEFT JOIN Enrollments e ON c.CourseID = e.CourseID
GROUP BY c.CourseID, c.Name;

STRING_AGG

MySQL không có hàm STRING_AGG như SQL Server. Thay vào đó, sử dụng GROUP_CONCAT để nối các chuỗi trong nhóm.

Cú pháp

GROUP_CONCAT(cột [SEPARATOR 'ký_tự_phân_cách'])

Ví dụ: Nối danh sách tên sinh viên theo khóa học:

SELECT 
    c.CourseID, 
    c.Name AS CourseName, 
    GROUP_CONCAT(s.Name SEPARATOR ', ') AS StudentNames
FROM Courses c
LEFT JOIN Enrollments e ON c.CourseID = e.CourseID
LEFT JOIN Students s ON e.StudentID = s.StudentID
GROUP BY c.CourseID, c.Name;

Lưu ý:

  • Mặc định, GROUP_CONCAT phân cách bằng dấu phẩy (,).
  • Giới hạn độ dài mặc định là 1024 ký tự, có thể tăng bằng cách thay đổi biến hệ thống:
    SET SESSION group_concat_max_len = 1000000;
    

Index (Chỉ mục)

Tổng quan

Chỉ mục tăng tốc độ truy vấn nhưng có thể làm chậm thao tác ghi (INSERT, UPDATE, DELETE).

Tạo chỉ mục (CREATE INDEX)

Tạo chỉ mục trên cột Name trong bảng Students:

CREATE INDEX idx_name ON Students(Name);

Tạo chỉ mục duy nhất (UNIQUE INDEX)

Đảm bảo không có giá trị trùng lặp:

CREATE UNIQUE INDEX idx_email ON Students(Email);

Tạo chỉ mục trên nhiều cột

CREATE INDEX idx_student_course ON Enrollments(StudentID, CourseID);

Xóa chỉ mục (DROP INDEX)

DROP INDEX idx_name ON Students;

Xem danh sách chỉ mục

SHOW INDEX FROM Students;

Chỉ mục tự động

  • Cột PRIMARY KEYUNIQUE tự động được tạo chỉ mục.
  • Không cần tạo chỉ mục thủ công cho các cột này.

View (Khung nhìn)

Tạo View (CREATE VIEW)

Tạo khung nhìn chứa thông tin sinh viên và khóa học:

CREATE VIEW StudentCourseView AS
SELECT 
    s.StudentID,
    s.Name AS StudentName,
    c.CourseID,
    c.Name AS CourseName
FROM Students s
LEFT JOIN Enrollments e ON s.StudentID = e.StudentID
LEFT JOIN Courses c ON e.CourseID = c.CourseID;

Sử dụng View

SELECT * FROM StudentCourseView WHERE CourseName = 'Toán học';

Cập nhật View (CREATE OR REPLACE VIEW)

CREATE OR REPLACE VIEW StudentCourseView AS
SELECT 
    s.StudentID,
    s.Name AS StudentName,
    c.CourseID,
    c.Name AS CourseName,
    e.Fee
FROM Students s
LEFT JOIN Enrollments e ON s.StudentID = e.StudentID
LEFT JOIN Courses c ON e.CourseID = e.CourseID;

Xóa View (DROP VIEW)

DROP VIEW StudentCourseView;

Lưu ý: View trong MySQL chỉ đọc (read-only) trừ khi đáp ứng các điều kiện cụ thể (ví dụ: không dùng JOIN, GROUP BY).

Stored Procedure

Tạo Stored Procedure (CREATE PROCEDURE)

Tạo thủ tục lưu trữ để thêm sinh viên mới:

DELIMITER //
CREATE PROCEDURE AddStudent(IN studentName VARCHAR(100), IN birthDate DATE)
BEGIN
    INSERT INTO Students (Name, Birthdate)
    VALUES (studentName, birthDate);
END //
DELIMITER ;

Gọi Stored Procedure

CALL AddStudent('Nguyen Van A', '2001-01-15');

Stored Procedure với tham số đầu ra

Lấy số lượng sinh viên:

DELIMITER //
CREATE PROCEDURE GetStudentCount(OUT total INT)
BEGIN
    SELECT COUNT(*) INTO total FROM Students;
END //
DELIMITER ;

CALL GetStudentCount(@total);
SELECT @total AS TotalStudents;

Xóa Stored Procedure (DROP PROCEDURE)

DROP PROCEDURE AddStudent;

Xem danh sách Stored Procedure

SHOW PROCEDURE STATUS WHERE Db = 'StudentManagementSystem';

Function (Hàm người dùng)

Tạo Function (CREATE FUNCTION)

Tạo hàm tính tuổi từ ngày sinh:

DELIMITER //
CREATE FUNCTION CalculateAge(birthDate DATE)
RETURNS INT DETERMINISTIC
BEGIN
    RETURN DATEDIFF(CURDATE(), birthDate) / 365;
END //
DELIMITER ;
  • DETERMINISTIC: Hàm luôn trả về cùng kết quả với cùng đầu vào.

Sử dụng Function

SELECT Name, CalculateAge(Birthdate) AS Age FROM Students;

Xóa Function (DROP FUNCTION)

DROP FUNCTION CalculateAge;

Xem danh sách Function

SHOW FUNCTION STATUS WHERE Db = 'StudentManagementSystem';

Trigger

Tạo Trigger (CREATE TRIGGER)

Tạo trigger tự động cập nhật thời gian chỉnh sửa khi cập nhật sinh viên:

ALTER TABLE Students ADD LastModified DATETIME;

DELIMITER //
CREATE TRIGGER UpdateLastModified
BEFORE UPDATE ON Students
FOR EACH ROW
BEGIN
    SET NEW.LastModified = NOW();
END //
DELIMITER ;

Kiểm tra Trigger

UPDATE Students SET Name = 'Nguyen Van B' WHERE StudentID = 1;
SELECT * FROM Students WHERE StudentID = 1;

Xóa Trigger (DROP TRIGGER)

DROP TRIGGER UpdateLastModified;

Xem danh sách Trigger

SHOW TRIGGERS FROM StudentManagementSystem;

Transaction (Giao dịch)

Bắt đầu giao dịch (START TRANSACTION)

START TRANSACTION;
INSERT INTO Students (Name, Birthdate) VALUES ('Tran Thi C', '2002-03-10');
INSERT INTO Enrollments (StudentID, CourseID, Fee) VALUES (LAST_INSERT_ID(), 1, 500);

Xác nhận giao dịch (COMMIT)

COMMIT;

Hoàn tác giao dịch (ROLLBACK)

Nếu có lỗi, hoàn tác:

START TRANSACTION;
INSERT INTO Students (Name, Birthdate) VALUES ('Le Van D', '2003-04-12');
-- Giả sử có lỗi xảy ra
ROLLBACK;

Điểm lưu trữ (SAVEPOINT)

START TRANSACTION;
INSERT INTO Students (Name, Birthdate) VALUES ('Pham Van E', '2004-05-20');
SAVEPOINT savepoint1;
INSERT INTO Enrollments (StudentID, CourseID, Fee) VALUES (LAST_INSERT_ID(), 2, 600);
-- Hoàn tác đến savepoint
ROLLBACK TO savepoint1;
COMMIT;

Cursor (Con trỏ)

Sử dụng Cursor

Duyệt qua danh sách sinh viên và in tên:

DELIMITER //
CREATE PROCEDURE PrintStudentNames()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE sName VARCHAR(100);
    DECLARE studentCursor CURSOR FOR SELECT Name FROM Students;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

    OPEN studentCursor;

    read_loop: LOOP
        FETCH studentCursor INTO sName;
        IF done THEN
            LEAVE read_loop;
        END IF;
        SELECT sName;
    END LOOP;

    CLOSE studentCursor;
END //
DELIMITER ;

CALL PrintStudentNames();

Quản lý người dùng

Tạo người dùng (CREATE USER)

CREATE USER 'student_admin'@'localhost' IDENTIFIED BY 'secure_password';

Phân quyền (GRANT)

Cấp quyền cho người dùng:

GRANT SELECT, INSERT, UPDATE ON StudentManagementSystem.* TO 'student_admin'@'localhost';

Thu hồi quyền (REVOKE)

REVOKE INSERT, UPDATE ON StudentManagementSystem.* FROM 'student_admin'@'localhost';

Xóa người dùng (DROP USER)

DROP USER 'student_admin'@'localhost';

Xem danh sách người dùng

SELECT User, Host FROM mysql.user;

Tối ưu hóa truy vấn (Optimization)

EXPLAIN

MySQL sử dụng EXPLAIN để phân tích cách thực thi truy vấn (tương đương Execution Plan trong SQL Server):

EXPLAIN SELECT s.Name, c.Name AS CourseName
FROM Students s
INNER JOIN Enrollments e ON s.StudentID = e.StudentID
INNER JOIN Courses c ON e.CourseID = c.CourseID
WHERE s.Birthdate > '2000-01-01';

Kết quả hiển thị:

  • id: ID của truy vấn.
  • select_type: Loại truy vấn (SIMPLE, SUBQUERY, v.v.).
  • table: Bảng được truy vấn.
  • type: Cách truy cập dữ liệu (ALL, INDEX, RANGE, v.v.).
  • possible_keys: Các chỉ mục có thể dùng.
  • key: Chỉ mục thực sự được dùng.
  • rows: Số hàng dự kiến quét.
  • Extra: Thông tin bổ sung (Using where, Using index, v.v.).

Tối ưu hóa với chỉ mục

Thêm chỉ mục để cải thiện hiệu suất:

CREATE INDEX idx_birthdate ON Students(Birthdate);

Sử dụng biến tạm (Temporary Tables) trong truy vấn

MySQL tự động tạo bảng tạm trong một số trường hợp (ví dụ: GROUP BY, ORDER BY). Để kiểm soát, bạn có thể tạo bảng tạm thủ công:

CREATE TEMPORARY TABLE TempStudents AS
SELECT StudentID, Name FROM Students WHERE Birthdate > '2000-01-01';

SELECT * FROM TempStudents;

DROP TEMPORARY TABLE TempStudents;

Tối ưu hóa bằng cách giảm tải

Sử dụng LIMIT để giảm số lượng bản ghi trả về:

SELECT * FROM Students WHERE Birthdate > '2000-01-01' LIMIT 100;

Full-Text Search (Tìm kiếm toàn văn)

MySQL hỗ trợ tìm kiếm toàn văn với chỉ mục FULLTEXT, chỉ khả dụng trên engine InnoDB hoặc MyISAM.

Tạo chỉ mục FULLTEXT

CREATE TABLE Articles (
    ArticleID INT AUTO_INCREMENT PRIMARY KEY,
    Title VARCHAR(255),
    Content TEXT,
    FULLTEXT(Title, Content)
);

Tìm kiếm với MATCH ... AGAINST

Tìm kiếm bài viết chứa từ "MySQL":

SELECT ArticleID, Title
FROM Articles
WHERE MATCH(Title, Content) AGAINST('MySQL' IN NATURAL LANGUAGE MODE);
  • IN NATURAL LANGUAGE MODE: Chế độ tìm kiếm tự nhiên.
  • IN BOOLEAN MODE: Hỗ trợ toán tử (+, -, *, v.v.):
    SELECT ArticleID, Title
    FROM Articles
    WHERE MATCH(Title, Content) AGAINST('+MySQL -Tutorial' IN BOOLEAN MODE);
    

Xóa chỉ mục FULLTEXT

DROP INDEX idx_fulltext ON Articles;
ALTER TABLE Articles ADD FULLTEXT(Title, Content);

Lưu ý: FULLTEXT không hỗ trợ tất cả các kiểu dữ liệu và có giới hạn từ tối thiểu (mặc định là 4 ký tự).

Temporary Tables (Bảng tạm)

MySQL hỗ trợ bảng tạm tồn tại trong phiên làm việc (session).

Tạo bảng tạm

CREATE TEMPORARY TABLE TempEnrollments (
    StudentID INT,
    CourseID INT,
    Fee INT
);

INSERT INTO TempEnrollments (StudentID, CourseID, Fee)
SELECT StudentID, CourseID, Fee FROM Enrollments WHERE Fee > 500;

SELECT * FROM TempEnrollments;

Xóa bảng tạm

Bảng tạm tự động bị xóa khi session kết thúc, hoặc xóa thủ công:

DROP TEMPORARY TABLE TempEnrollments;

Lưu ý: MySQL không có bảng tạm toàn cục như SQL Server (##Table).

Partitioning (Phân vùng)

MySQL hỗ trợ phân vùng bảng để quản lý dữ liệu lớn.

Tạo bảng phân vùng theo phạm vi (RANGE)

Phân vùng theo năm sinh:

CREATE TABLE StudentsPartitioned (
    StudentID INT AUTO_INCREMENT,
    Name VARCHAR(100),
    Birthdate DATE,
    PRIMARY KEY (StudentID, Birthdate)
)
PARTITION BY RANGE (YEAR(Birthdate)) (
    PARTITION p0 VALUES LESS THAN (2000),
    PARTITION p1 VALUES LESS THAN (2005),
    PARTITION p2 VALUES LESS THAN (2010),
    PARTITION pmax VALUES LESS THAN MAXVALUE
);

Truy vấn phân vùng

SELECT * FROM StudentsPartitioned WHERE Birthdate < '2005-01-01';

Xóa phân vùng

ALTER TABLE StudentsPartitioned DROP PARTITION p0;

Lưu ý: Chỉ InnoDB và một số engine khác hỗ trợ phân vùng.

Event Scheduler (Lập lịch sự kiện)

MySQL hỗ trợ chạy tác vụ định kỳ bằng Event Scheduler.

Kích hoạt Event Scheduler

SET GLOBAL event_scheduler = ON;

Tạo Event

Xóa sinh viên chưa đăng ký khóa học nào mỗi ngày:

DELIMITER //
CREATE EVENT DeleteInactiveStudents
ON SCHEDULE EVERY 1 DAY
STARTS '2025-04-09 00:00:00'
DO
BEGIN
    DELETE FROM Students
    WHERE StudentID NOT IN (SELECT StudentID FROM Enrollments);
END //
DELIMITER ;

Xem danh sách Event

SHOW EVENTS FROM StudentManagementSystem;

Xóa Event

DROP EVENT DeleteInactiveStudents;

Backup và Restore nâng cao

Backup với mysqldump (nâng cao)

Sao lưu với điều kiện:

mysqldump -u root -p --where="Birthdate > '2000-01-01'" StudentManagementSystem Students > students_backup.sql

Restore với file lớn

Tăng giới hạn bộ nhớ khi khôi phục file lớn:

mysql -u root -p --max_allowed_packet=512M StudentManagementSystem < students_backup.sql

Performance Monitoring (Giám sát hiệu suất)

Xem truy vấn đang chạy

SHOW PROCESSLIST;

Kiểm tra trạng thái hệ thống

SHOW STATUS LIKE 'Threads%';

Profiling truy vấn

Kích hoạt profiling:

SET profiling = 1;
SELECT * FROM Students WHERE Birthdate > '2000-01-01';
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;

JSON Support (Hỗ trợ JSON)

MySQL hỗ trợ kiểu dữ liệu JSON từ phiên bản 5.7.

Tạo bảng với cột JSON

CREATE TABLE StudentDetails (
    StudentID INT PRIMARY KEY,
    Details JSON
);

INSERT INTO StudentDetails (StudentID, Details)
VALUES (1, '{"name": "Hoang Hiep", "scores": [85, 90, 88]}');

Truy vấn JSON

Lấy tên từ cột JSON:

SELECT StudentID, JSON_EXTRACT(Details, '$.name') AS Name
FROM StudentDetails;

Hoặc dùng toán tử ->:

SELECT StudentID, Details->'$.name' AS Name
FROM StudentDetails;

Cập nhật JSON

UPDATE StudentDetails 
SET Details = JSON_SET(Details, '$.scores[0]', 95)
WHERE StudentID = 1;

Common Table Expressions (CTE)

MySQL hỗ trợ CTE từ phiên bản 8.0 (tương đương WITH trong SQL Server).

Sử dụng CTE

Lấy sinh viên và số khóa học đã đăng ký:

WITH EnrollmentCount AS (
    SELECT s.StudentID, s.Name, COUNT(e.CourseID) AS CourseCount
    FROM Students s
    LEFT JOIN Enrollments e ON s.StudentID = e.StudentID
    GROUP BY s.StudentID, s.Name
)
SELECT * FROM EnrollmentCount WHERE CourseCount > 2;

CTE đệ quy

Tạo danh sách số từ 1 đến 10:

WITH RECURSIVE Numbers AS (
    SELECT 1 AS n
    UNION ALL
    SELECT n + 1 FROM Numbers WHERE n < 10
)
SELECT * FROM Numbers;

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

-- Sử dụng database (MySQL không cần USE [master])
CREATE DATABASE IF NOT EXISTS EZSEStudentManagementSystem;
USE EZSEStudentManagementSystem;

-- Xóa database nếu đã tồn tại
DROP DATABASE IF EXISTS EZSEStudentManagementSystem;
CREATE DATABASE EZSEStudentManagementSystem;
USE EZSEStudentManagementSystem;

-- Xóa tất cả các bảng nếu tồn tại (MySQL không có sysdatabases, dùng DROP TABLE trực tiếp)
SET FOREIGN_KEY_CHECKS = 0; -- Tạm thời tắt kiểm tra khóa ngoại

-- Xóa các bảng theo thứ tự phù hợp để tránh lỗi khóa ngoại
DROP TABLE IF EXISTS Enrollments;
DROP TABLE IF EXISTS Classes;
DROP TABLE IF EXISTS MajorCourses;
DROP TABLE IF EXISTS Courses;
DROP TABLE IF EXISTS Teachers;
DROP TABLE IF EXISTS Students;
DROP TABLE IF EXISTS Majors;

SET FOREIGN_KEY_CHECKS = 1; -- Bật lại kiểm tra khóa ngoại

-- ---------------------------- Create table ----------------------------------

-- Bảng Ngành học (Major)
CREATE TABLE Majors (
    MajorID INT AUTO_INCREMENT PRIMARY KEY,
    MajorName VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL
);

-- Bảng Sinh viên (Student)
CREATE TABLE Students (
    StudentID INT AUTO_INCREMENT PRIMARY KEY,
    FirstName VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    LastName VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci 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) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
    EnrollmentDate DATE NOT NULL,
    MajorID INT,
    FOREIGN KEY (MajorID) REFERENCES Majors(MajorID)
);

-- Bảng Giảng viên (Teacher)
CREATE TABLE Teachers (
    TeacherID INT AUTO_INCREMENT PRIMARY KEY,
    FirstName VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    LastName VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    Email VARCHAR(100) UNIQUE NOT NULL,
    Phone VARCHAR(15) UNIQUE,
    Address VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
);

-- Bảng Môn học (Course)
CREATE TABLE Courses (
    CourseID INT AUTO_INCREMENT PRIMARY KEY,
    CourseName VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    Credits INT CHECK (Credits > 0)
);

-- Bảng Môn học theo ngành (MajorCourses)
CREATE TABLE MajorCourses (
    MajorID INT,
    CourseID INT,
    PRIMARY KEY (MajorID, CourseID),
    FOREIGN KEY (MajorID) REFERENCES Majors(MajorID),
    FOREIGN KEY (CourseID) REFERENCES Courses(CourseID)
);

-- Bảng Lớp học (Class)
CREATE TABLE Classes (
    ClassID INT AUTO_INCREMENT PRIMARY KEY,
    CourseID INT,
    TeacherID INT,
    Semester VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    Year INT NOT NULL,
    FOREIGN KEY (CourseID) REFERENCES Courses(CourseID),
    FOREIGN KEY (TeacherID) REFERENCES Teachers(TeacherID)
);

-- Bảng Đăng ký môn học (Enrollment)
CREATE TABLE Enrollments (
    StudentID INT,
    ClassID INT,
    Attempt INT NOT NULL DEFAULT 1,
    Grade DECIMAL(4,2) CHECK (Grade >= 0 AND Grade <= 10),
    PRIMARY KEY (StudentID, ClassID, Attempt),
    FOREIGN KEY (StudentID) REFERENCES Students(StudentID),
    FOREIGN KEY (ClassID) REFERENCES Classes(ClassID)
);

-- Chèn dữ liệu vào bảng Majors
INSERT INTO Majors (MajorName) VALUES
('Computer Science'), -- Khoa học máy tính
('Software Engineering'), -- Kỹ thuật phần mềm
('Information Security'); -- An toàn thông tin

-- 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);

Chuyển đổi chính:

  • MySQL không có [master] hay dbo.sysdatabases, thay bằng CREATE DATABASE IF NOT EXISTSDROP DATABASE IF EXISTS.
  • IDENTITY(1,1) được thay bằng AUTO_INCREMENT.
  • NVARCHAR được thay bằng VARCHAR với CHARACTER SET utf8mb4 để hỗ trợ Unicode.
  • Tắt/bật kiểm tra khóa ngoại bằng SET FOREIGN_KEY_CHECKS thay vì ALTER DATABASE ... SET OFFLINE.
  • MySQL không hỗ trợ script động phức tạp như SQL Server để xóa ràng buộc, nên thay bằng cách xóa bảng trực tiếp theo thứ tự tránh vi phạm khóa ngoại.

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 INT AUTO_INCREMENT PRIMARY KEY,
    MajorName VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL
);
  • MajorID: Mã ngành học, là 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 INT AUTO_INCREMENT PRIMARY KEY,
    FirstName VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    LastName VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci 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) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
    EnrollmentDate DATE NOT NULL,
    MajorID INT,
    FOREIGN KEY (MajorID) REFERENCES Majors(MajorID)
);
  • StudentID: Mã sinh viên, là 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, duy nhất.
  • Phone: Số điện thoại, duy nhất.
  • 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 INT AUTO_INCREMENT PRIMARY KEY,
    FirstName VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    LastName VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    Email VARCHAR(100) UNIQUE NOT NULL,
    Phone VARCHAR(15) UNIQUE,
    Address VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
);
  • TeacherID: Mã giảng viên, là 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, duy nhất.
  • Phone: Số điện thoại, duy nhất.
  • 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 INT AUTO_INCREMENT PRIMARY KEY,
    CourseName VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    Credits INT CHECK (Credits > 0)
);
  • CourseID: Mã môn học, là 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 INT,
    CourseID INT,
    PRIMARY KEY (MajorID, CourseID),
    FOREIGN KEY (MajorID) REFERENCES Majors(MajorID),
    FOREIGN KEY (CourseID) REFERENCES Courses(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 INT AUTO_INCREMENT PRIMARY KEY,
    CourseID INT,
    TeacherID INT,
    Semester VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    Year INT NOT NULL,
    FOREIGN KEY (CourseID) REFERENCES Courses(CourseID),
    FOREIGN KEY (TeacherID) REFERENCES Teachers(TeacherID)
);
  • ClassID: Mã lớp học, là 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 INT,
    ClassID INT,
    Attempt INT NOT NULL DEFAULT 1,
    Grade DECIMAL(4,2) CHECK (Grade >= 0 AND Grade <= 10),
    PRIMARY KEY (StudentID, ClassID, Attempt),
    FOREIGN KEY (StudentID) REFERENCES Students(StudentID),
    FOREIGN KEY (ClassID) REFERENCES Classes(ClassID)
);
  • 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.

Chuyển đổi chính:

  • IDENTITY(1,1) được thay bằng AUTO_INCREMENT.
  • NVARCHAR được thay bằng VARCHAR với CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci để hỗ trợ Unicode (tương đương NVARCHAR trong SQL Server).
  • MySQL yêu cầu định nghĩa khóa ngoại trực tiếp trong khai báo cột hoặc sau cùng, không cần GO phân tách.
  • CHECK trong MySQL được hỗ trợ từ phiên bản 8.0.16 với engine InnoDB; nếu dùng phiên bản cũ hơn, bạn cần bỏ ràng buộc CHECK hoặc dùng trigger để thay thế.

Đề 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.

Question 1


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.

Question 2


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.

Question 3


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.

Question 4


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.

Question 5


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.

Question 6


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""Software Engineering".

Hiển thị các cột như phía dưới.

Question 7


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ể

Question 8


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:

  1. Nếu Email không chứa ký tự "@", tự động thêm @ezse.net vào cuối.
  2. Nếu MajorID bị NULL, tự động đặt MajorID thà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.

Question 9


Câu hỏi 10

Xóa sinh viên có StudentID = 3StudentID = 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

Câu hỏi 1

Viết truy vấn để hiển thị tất cả các giảng viên.

Question 1

Đá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.

Question 2

Đá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.

Question 3

Đá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.

Question 4

Đá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;

Chuyển đổi:

  • MySQL không có CONCAT trong SQL Server dạng mặc định, nhưng CONCAT hoạt động tốt trong MySQL.

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.

Question 5

Đáp án:

SELECT C.CourseID, C.CourseName, 
       COUNT(E.StudentID) AS NumberOfEnrollments
FROM Courses C
LEFT JOIN Classes Cl ON C.CourseID = Cl.CourseID
LEFT JOIN Enrollments E ON Cl.ClassID = E.ClassID
WHERE Cl.Year = 2025 OR Cl.Year IS NULL
GROUP BY C.CourseID, C.CourseName
ORDER BY NumberOfEnrollments DESC, C.CourseID ASC;

Chuyển đổi:

  • MySQL không yêu cầu AND trong LEFT JOIN, thay bằng WHERE để lọc năm 2025.
  • Thêm OR Cl.Year IS NULL để đảm bảo các khóa học không có lớp nào trong năm 2025 vẫn xuất hiện với số lượng 0.

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.

Question 6

Đá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;

Chuyển đổi:

  • MySQL hỗ trợ CTE (WITH) từ phiên bản 8.0. Nếu dùng phiên bản cũ hơn, cần thay bằng subquery.

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""Software Engineering".

Hiển thị các cột như phía dưới.

Question 7

Đá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;

Chuyển đổi:

  • MySQL dùng COALESCE thay vì ISNULL.
  • CTE được giữ nguyên, nhưng nếu dùng MySQL < 8.0, cần thay bằng subquery.

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ể

Question 8

Đáp án:

DELIMITER //
CREATE FUNCTION GetNumberOfStudentsByCourseID(p_CourseID INT)
RETURNS INT
DETERMINISTIC
BEGIN
    DECLARE NumberOfStudents INT;

    SELECT COUNT(DISTINCT e.StudentID) INTO NumberOfStudents
    FROM Enrollments e
    JOIN Classes c ON e.ClassID = c.ClassID
    WHERE c.CourseID = p_CourseID;

    RETURN NumberOfStudents;
END //
DELIMITER ;

-- Kiểm tra function
SELECT CourseID, CourseName, GetNumberOfStudentsByCourseID(CourseID) AS NumberOfStudents
FROM Courses
WHERE CourseID IN (1, 2, 3, 4);

Chuyển đổi:

  • MySQL không dùng @ cho tham số như SQL Server, thay bằng tên tham số trực tiếp (p_CourseID).
  • Thêm DELIMITER để định nghĩa function.
  • Thêm DETERMINISTIC để chỉ hàm luôn trả về cùng kết quả với cùng đầu vào.
  • Biến trong MySQL được khai báo bằng DECLARE và gán giá trị bằng INTO.

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:

  1. Nếu Email không chứa ký tự "@", tự động thêm @ezse.net vào cuối.
  2. Nếu MajorID bị NULL, tự động đặt MajorID thà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.

Question 9

Đáp án:

DELIMITER //
CREATE TRIGGER trg_InsteadOfInsertStudent
BEFORE INSERT ON Students
FOR EACH ROW
BEGIN
    DECLARE SoftwareEngineeringID INT;
    
    -- Lấy MajorID của ngành "Software Engineering"
    SELECT MajorID INTO SoftwareEngineeringID 
    FROM Majors 
    WHERE MajorName = 'Software Engineering';

    -- Xử lý Email: Nếu không chứa @ thì thêm @ezse.net
    IF NEW.Email NOT LIKE '%@%' THEN
        SET NEW.Email = CONCAT(NEW.Email, '@ezse.net');
    END IF;

    -- Xử lý MajorID: Nếu NULL thì gán MajorID của "Software Engineering"
    IF NEW.MajorID IS NULL THEN
        SET NEW.MajorID = SoftwareEngineeringID;
    END IF;
END //
DELIMITER ;

-- 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;

Chuyển đổi:

  • MySQL không hỗ trợ INSTEAD OF trigger, thay bằng BEFORE INSERT.
  • Sử dụng NEW.column_name để truy cập giá trị mới.
  • Thêm DELIMITER để định nghĩa trigger.
  • NOT LIKE '%@%' thay cho NOT LIKE '%@%' trong SQL Server.
  • ISNULL thay bằng IS NULL.
  • CONCAT thay cho + để nối chuỗi.

Câu hỏi 10

Xóa sinh viên có StudentID = 3StudentID = 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:

  1. Xóa dữ liệu liên quan trong Enrollments trước

    • Bảng Enrollments có khóa ngoại StudentID tham chiếu đến Students.
    • Nếu không có ON DELETE CASCADE, ta phải xóa các bản ghi liên quan trong Enrollments trước.
  2. 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 = 3StudentID = 5 mà không vi phạm ràng buộc.

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:

FOREIGN KEY (StudentID) 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.