SQL Server
Cảm ơn bạn đã đăng ký khóa học!
Để 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 SQL Server và SSMS
1. Cài Đặt SQL Server
Bạn có thể tải SQL Server từ trang chính thức của Microsoft:
SQL Server có nhiều phiên bản khác nhau, phù hợp với từng nhu cầu:
| Phiên bản | Đặc điểm |
|---|---|
| Enterprise | Dành cho doanh nghiệp lớn, hỗ trợ đầy đủ tính năng |
| Standard | Dành cho doanh nghiệp vừa và nhỏ, có giới hạn tính năng |
| Express | Miễn phí, nhẹ, phù hợp cho học tập và phát triển nhỏ |
| Developer | Đầy đủ tính năng như Enterprise, nhưng chỉ được sử dụng cho phát triển và thử nghiệm, không được dùng trong môi trường sản xuất |
2. Cài Đặt SQL Server Management Studio (SSMS)
SSMS là công cụ quản lý, học tập SQL Server hiệu quả, bạn có thể tải tại:
3. Kết Nối và Làm Việc Với SQL Server
- Mở SSMS.
- Nhập thông tin kết nối:
- Server name:
localhost,.\SQLEXPRESS, ... - Authentication:
- Windows Authentication (Sử dụng tài khoản Windows).
- SQL Server Authentication (Sử dụng tài khoản SQL, cần
nhập
UsernamevàPassword).
- Server name:
- Nhấn Connect để 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 (BACKUP DATABASE)
BACKUP DATABASE StudentManagementSystem
TO DISK = 'C:\\Backup\\StudentManagementSystem.bak';
Khôi phục cơ sở dữ liệu (RESTORE DATABASE)
Dùng để khôi phục cơ sở dữ liệu từ bản sao lưu.
RESTORE DATABASE StudentManagementSystem
FROM DISK = 'C:\\Backup\\StudentManagementSystem.bak';
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 SQL Server:
SELECT name FROM sys.databases;
Quản lý bảng (Table)
Tạo bảng (CREATE TABLE)
CREATE TABLE Teachers (
TeacherID INT IDENTITY(1,1) PRIMARY KEY,
Name NVARCHAR(50) NOT NULL
);
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 ALTER COLUMN Email NVARCHAR(150);
Đổi tên cột
EXEC sp_rename 'Students.Email', 'StudentEmail', 'COLUMN';
Xóa cột
ALTER TABLE Students DROP COLUMN StudentEmail;
Đổi tên bảng
EXEC sp_rename 'Students', '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.
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES;
Kiểu dữ liệu (Data Types)
Kiểu số (Numeric Data Types)
| Kiểu dữ liệu | Mô tả | Ví dụ |
|---|---|---|
INT |
Kiểu số nguyên có kích thước 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 |
TINYINT |
Số nguyên rất nhỏ 1 byte, phạm vi từ 0 đến 255. | 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 |
FLOAT |
Số thực (floating-point), dùng để lưu các giá trị có dấu phẩy động, không cố định số chữ số sau dấu thập phân, có thể có lỗi làm tròn. | 3.14159 |
MONEY |
Kiểu dữ liệu lưu trữ giá trị tiền tệ, có thể chứa tối đa 4 chữ số thập phân. Kích thước lưu trữ 4 byte. | 1000.99 |
BIT |
Kiểu Boolean, chỉ chứa giá trị 0 (false) hoặc 1 (true). | 1 (true), 0 (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 có độ dài n, luôn chiếm n byte
ngay cả khi không đủ ký tự. |
'ABCD ' (5 ký tự, gồm cả khoảng trắng) |
VARCHAR(n) |
Chuỗi ký tự có độ dài thay đổi, chiếm dung lượng đúng bằng số ký tự thực tế + 2 byte. | 'John Doe' |
NCHAR(n) |
Chuỗi Unicode cố định, tương tự CHAR nhưng hỗ trợ đa ngôn ngữ, mỗi
ký tự chiếm 2 byte. |
N'Hoàng Hiệp' |
NVARCHAR(n) |
Chuỗi Unicode có độ dài thay đổi. | N'Hoàng Hiệp' |
TEXT |
Kiểu dữ liệu lưu văn bản dài, đã lỗi thời, thay bằng VARCHAR(MAX),
NVARCHAR(MAX). |
'Lorem ipsum dolor sit amet...' |
✅ Dùng CHAR(n), NCHAR(n) khi:
- Chuỗi có độ dài cố định (ví dụ: mã môn học, mã bưu chính).
- Cần hiệu suất đọc cao hơn (vì SQL Server không cần kiểm tra độ dài từng giá trị).
✅ Dùng VARCHAR(n), NVARCHAR(n) khi:
- Chuỗi có độ dài thay đổi (ví dụ: tên người, tên khóa học).
- Cần tiết kiệm bộ nhớ vì không lưu khoảng trắng thừa.
✅ Dùng VARCHAR(MAX), NVARCHAR(MAX) khi:
- Chuỗi rất dài (bài viết, nội dung JSON/XML).
Kiểu ngày và thời gian (Date & Time Data Types)
| Kiểu dữ liệu | Mô tả | Ví dụ |
|---|---|---|
DATE |
Lưu trữ ngày tháng, không có thông tin giờ. Phạm vi từ 0001-01-01
đến 9999-12-31. |
'2025-03-01' |
TIME |
Lưu trữ thời gian trong ngày, không có thông tin ngày tháng. Phạm vi từ
00:00:00 đến 23:59:59.9999999. |
'12:30:45' |
DATETIME |
Lưu trữ ngày và giờ với độ chính xác đến mili-giây. Phạm vi từ
1753-01-01 00:00:00 đến 9999-12-31 23:59:59.997. |
'2025-03-01 12:30:45' |
DATETIME2 |
Phiên bản cải tiến của DATETIME, có độ chính xác cao hơn với tối đa
7 chữ số thập phân cho phần giây. |
'2025-03-01 12:30:45.1234567' |
Kiểu dữ liệu nhị phân và định danh (Binary & Unique Identifier)
| Kiểu dữ liệu | Mô tả | Ví dụ |
|---|---|---|
VARBINARY(n) |
Lưu trữ dữ liệu nhị phân như hình ảnh, file. n là số byte tối đa.
Nếu dùng VARBINARY(MAX), có thể lưu tối đa 2GB. |
0x1A2B3C |
UNIQUEIDENTIFIER |
Kiểu GUID (Globally Unique Identifier), thường dùng để tạo ID duy nhất trên toàn hệ thống. | NEWID() → 3F2504E0-4F89-41D3-9A0C-0305E82C3301 |
Ví dụ tạo bảng với kiểu dữ liệu phổ biến
CREATE TABLE Employees (
EmployeeID UNIQUEIDENTIFIER DEFAULT NEWID() PRIMARY KEY, -- ID duy nhất, tự động sinh
Name NVARCHAR(100) NOT NULL, -- Họ và tên (chuỗi Unicode, tối đa 100 ký tự)
Birthdate DATE, -- Ngày sinh
WorkStartTime TIME, -- Giờ bắt đầu làm việc
Salary DECIMAL(10,2), -- Lương (số thập phân, 10 chữ số, 2 chữ số sau dấu thập phân)
ProfilePicture VARBINARY(MAX), -- Ảnh đại diện (dữ liệu nhị phân)
IsActive BIT DEFAULT 1 -- Trạng thái hoạt động (1: Đang làm việc, 0: Không làm việc)
);
Ví dụ tạo bảng với tất cả kiểu dữ liệu
CREATE TABLE AllDataTypesExample (
ID UNIQUEIDENTIFIER DEFAULT NEWID() PRIMARY KEY, -- ID duy nhất, tự động sinh
IntNumber INT NOT NULL, -- Số nguyên 4 byte (-2,147,483,648 đến 2,147,483,647)
BigIntNumber BIGINT, -- Số nguyên lớn 8 byte (-9,223,372,036,854,775,808 đến 9,223,372,036,854,775,807)
SmallIntNumber SMALLINT, -- Số nguyên nhỏ 2 byte (-32,768 đến 32,767)
TinyIntNumber TINYINT, -- Số nguyên rất nhỏ 1 byte (0 đến 255)
DecimalNumber DECIMAL(10,2), -- Số thập phân có 10 chữ số, trong đó 2 chữ số sau dấu phẩy
FloatNumber FLOAT, -- Số thực (dùng cho số có phần thập phân dài)
MoneyAmount MONEY, -- Kiểu dữ liệu tiền tệ (tự động định dạng số tiền)
IsActive BIT DEFAULT 1, -- Boolean (0: false, 1: true)
FixedText CHAR(10), -- Chuỗi cố định 10 ký tự (ít thay đổi độ dài)
VariableText VARCHAR(255), -- Chuỗi thay đổi độ dài tối đa 255 ký tự
UnicodeText NVARCHAR(255), -- Chuỗi Unicode thay đổi (hỗ trợ nhiều ngôn ngữ)
LongText TEXT, -- Văn bản dài (ĐÃ LỖI THỜI, không khuyến khích dùng, thay bằng NVARCHAR(MAX))
BirthDate DATE, -- Kiểu dữ liệu ngày (chỉ chứa ngày, không có giờ)
WorkStartTime TIME, -- Kiểu dữ liệu thời gian (chỉ chứa giờ, không có ngày)
FullDateTime DATETIME, -- Ngày và giờ (độ chính xác đến giây)
FullDateTime2 DATETIME2, -- Ngày và giờ chính xác cao (tối đa 7 chữ số sau dấu thập phân)
ProfilePicture VARBINARY(MAX), -- Lưu trữ dữ liệu nhị phân (ảnh, file, tài liệu)
CreatedAt DATETIME DEFAULT GETDATE() -- Ngày tạo, mặc định là thời điểm hiện tại
);
Biến (Variable)
Biến là một đối tượng được sử dụng để lưu trữ dữ liệu tạm thời trong quá trình thực thi truy vấn. Biến thường được sử dụng trong Stored Procedure, Function, hoặc Batch Script để lưu trữ và thao tác dữ liệu một cách linh hoạt.
Khai báo biến (DECLARE)
Để khai báo một biến trong SQL Server, sử dụng từ khóa DECLARE:
DECLARE @VariableName DataType;
Ví dụ:
DECLARE @StudentName NVARCHAR(50);
DECLARE @BirthYear INT;
Gán giá trị cho biến (SET, SELECT)
Có hai cách để gán giá trị cho biến:
- Sử dụng
SET - Sử dụng
SELECT
Gán giá trị bằng
SET
DECLARE @StudentName NVARCHAR(50);
SET @StudentName = N'Hoàng Hiệp';
PRINT @StudentName;
Gán giá trị
bằng SELECT
DECLARE @BirthYear INT;
SELECT @BirthYear = YEAR(Birthdate)
FROM Students WHERE StudentID = 1;
DECLARE @StudentName NVARCHAR(50);
DECLARE @BirthYear INT;
SELECT @StudentName = Name, @BirthYear = YEAR(Birthdate)
FROM Students WHERE StudentID = 1;
Sử dụng biến
Sau khi khai báo và gán giá trị, có thể sử dụng biến trong câu lệnh (SELECT,
INSERT, UPDATE, DELETE, hoặc WHERE):
DECLARE @Year INT;
SET @Year = 2000;
SELECT * FROM Students WHERE YEAR(Birthdate) = @Year;
Biến bảng (Table Variable)
Ngoài các biến kiểu dữ liệu thông thường, SQL Server còn hỗ trợ biến bảng
(TABLE VARIABLE), giúp lưu trữ tập dữ liệu tạm thời.
Khai báo biến bảng
DECLARE @StudentTable TABLE (
StudentID INT PRIMARY KEY,
Name NVARCHAR(50),
Birthdate DATE
);
Thêm dữ liệu vào biến bảng
INSERT INTO @StudentTable (StudentID, Name, Birthdate)
VALUES (1, 'John Doe', '2000-05-15');
Truy vấn dữ liệu từ biến bảng
SELECT * FROM @StudentTable;
Biến toàn cục
SQL Server có một số biến toàn cục (System Variables) bắt đầu bằng @@, dùng để
truy xuất thông tin hệ thống.
Ví dụ:
@@VERSION- Lấy thông tin phiên bản SQL Server@@ROWCOUNT- Số dòng bị ảnh hưởng bởi truy vấn gần nhất@@ERROR- Mã lỗi của truy vấn gần nhất
SELECT @@VERSION;
SELECT @@ROWCOUNT;
Hàm (Functions)
Hàm xử lý chuỗi (String Functions)
| Hàm | Chức năng | Ví dụ |
|---|---|---|
LEN(string) |
Trả về độ dài chuỗi (không tính khoảng trắng cuối chuỗi) | LEN('Hello') → 5 |
LEFT(string, n) |
Lấy n ký tự bên trái |
LEFT('SQL Server', 3) → 'SQL' |
RIGHT(string, n) |
Lấy n ký tự bên phải |
RIGHT('SQL Server', 6) → 'Server' |
SUBSTRING(string, start, length) |
Cắt chuỗi con từ vị trí start với độ dài length |
SUBSTRING('SQL Server', 5, 6) →
'Server' |
UPPER(string) |
Chuyển thành chữ in hoa | UPPER('sql') → 'SQL' |
LOWER(string) |
Chuyển thành chữ thường | LOWER('SQL') → 'sql' |
LTRIM(string) |
Xóa khoảng trắng đầu chuỗi | LTRIM(' SQL') → 'SQL' |
RTRIM(string) |
Xóa khoảng trắng cuối chuỗi | RTRIM('SQL ') → 'SQL' |
TRIM(string) |
Xóa khoảng trắng 2 bên chuỗi | TRIM(' SQL ') → 'SQL' |
REPLACE(string, old, new) |
Thay thế chuỗi con | REPLACE('Hello World', 'World', 'SQL') →
'Hello SQL' |
CHARINDEX(substring, string) |
Vị trí của chuỗi con | CHARINDEX('SQL', 'Learn SQL') → 7 |
CONCAT(string1, string2, …) |
Nối chuỗi | CONCAT('Hello', ' ', 'SQL') →
'Hello SQL' (Tự động chuyển đổi kiểu dữ liệu, Bỏ qua giá
trị NULL) |
Hàm xử lý số (Numeric Functions)
| Hàm | Chức năng | Ví dụ |
|---|---|---|
ABS(number) |
Trị tuyệt đối | ABS(-5) → 5 |
CEILING(number) |
Làm tròn lên | CEILING(4.2) → 5 |
FLOOR(number) |
Làm tròn xuống | FLOOR(4.9) → 4 |
ROUND(number, decimals) |
Làm tròn số với số chữ số thập phân | 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 |
RAND(seed) |
Tạo số ngẫu nhiên nhỏ hơn hoặc bằng 1 | RAND() → 0.684 |
SIGN(number) |
Trả về -1, 0, 1 tùy theo số âm, 0 hay dương |
SIGN(-10) → -1 |
Hàm xử lý ngày tháng (Date Functions)
| Hàm | Chức năng | Ví dụ |
|---|---|---|
GETDATE() |
Trả về ngày giờ hiện tại | GETDATE() → '2025-03-15 10:15:30' |
SYSDATETIME() |
Trả về ngày giờ hiện tại với độ chính xác cao hơn | SYSDATETIME() → '2025-03-15 10:15:30.1234567'
|
YEAR(date) |
Trả về năm từ một ngày | YEAR('2025-03-15') → 2025 |
MONTH(date) |
Trả về tháng từ một ngày | MONTH('2025-03-15') → 3 |
DAY(date) |
Trả về ngày trong tháng | DAY('2025-03-15') → 15 |
DATEADD(part, value, date) |
Cộng thêm giá trị vào ngày | DATEADD(DAY, 10, '2025-03-15') →
'2025-03-25' |
DATEDIFF(part, date1, date2) |
Khoảng cách giữa hai ngày | DATEDIFF(DAY, '2025-03-01', '2025-03-10') →
9 |
DATENAME(part, date) |
Lấy tên của phần ngày | DATENAME(WEEKDAY, '2025-03-15') →
'Saturday' |
DATEPART(part, date) |
Lấy giá trị số của phần ngày | DATEPART(YEAR, '2025-03-15') → 2025 |
FORMAT(date, format) |
Định dạng ngày | FORMAT(GETDATE(), 'dd/MM/yyyy') →
'15/03/2025' |
EOMONTH(date) |
Lấy ngày cuối cùng của tháng | EOMONTH('2025-03-15') → '2025-03-31'
|
Hàm DATEPART
DATEPART |
Ý nghĩa | Ví dụ |
|---|---|---|
YEAR |
Năm | DATEPART(YEAR, '2025-03-15') hoặc
YEAR('2025-03-15') → 2025 |
MONTH |
Tháng | DATEPART(MONTH, '2025-03-15') hoặc
MONTH('2025-03-15') → 3 |
DAY |
Ngày | DATEPART(DAY, '2025-03-15') hoặc
DAY('2025-03-15') → 15 |
HOUR |
Giờ | DATEPART(HOUR, '2025-03-15 10:15:30') → 10
|
MINUTE |
Phút | DATEPART(MINUTE, '2025-03-15 10:15:30') → 15
|
SECOND |
Giây | DATEPART(SECOND, '2025-03-15 10:15:30') → 30
|
WEEKDAY |
Ngày trong tuần | DATENAME(WEEKDAY, '2025-03-15') →
'Saturday' |
WEEK |
Tuần thứ bao nhiêu trong năm | DATEPART(WEEK, '2025-03-15') → 11 |
QUARTER |
Quý trong năm | DATEPART(QUARTER, '2025-03-15') → 1 |
Hàm nâng cao (Advanced Functions)
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 VARCHAR(10)) → '123' |
CAST('2025-08-25 10:30:45' AS DATE) →
'2025-08-25' |
||
CAST(3.14 AS INT) → 3 (Làm tròn xuống) |
||
CAST(GETDATE() AS DATE) → '2025-03-15' (Chỉ
lấy phần ngày) |
||
CONVERT(type, expression, style) |
Chuyển kiểu dữ liệu (có style) | CONVERT(VARCHAR, GETDATE(), 103) →
'15/03/2025' |
CONVERT(DATE, '2025-08-25 10:30:45') →
'2025-08-25' |
||
CONVERT(DECIMAL(10,2), 3.14159) → 3.14 |
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' |
ISNULL(expression, replacement) |
Thay thế giá trị NULL. | ISNULL(NULL, 'N/A') → 'N/A' |
NULLIF(val1, val2) |
Trả về NULL nếu hai giá trị bằng nhau. | NULLIF(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ộc | Phân loại | Chức năng |
|---|---|---|
NOT NULL |
Thuộc tính cột | Không cho phép giá trị NULL trong cột. |
UNIQUE |
Ràng buộc | Đảm bảo tất cả giá trị trong cột là duy nhất. |
PRIMARY KEY |
Ràng buộc | Kết hợp NOT NULL và UNIQUE, mỗi bảng chỉ có một khóa
chính. |
FOREIGN KEY |
Ràng buộc | Tạo mối quan hệ giữa hai bảng, đảm bảo dữ liệu hợp lệ. |
CHECK |
Ràng buộc | Kiểm tra giá trị nhập vào thỏa mãn điều kiện cụ thể. |
DEFAULT |
Thuộc tính cột | Gán giá trị mặc định cho cột nếu không có giá trị nhập. |
IDENTITY |
Thuộc tính cột | Tạo cột tự động tăng giá trị cho mỗi bản ghi. |
NOT NULL
Dùng để bắt buộc cột phải có giá trị, không được để trống.
CREATE TABLE Teachers (
TeacherID INT IDENTITY(1,1) PRIMARY KEY,
Name NVARCHAR(50) NOT NULL
);
UNIQUE
Dùng để đảm bảo giá trị trong cột không bị trùng lặp.
CREATE TABLE Doctors (
DoctorID INT IDENTITY(1,1) PRIMARY KEY,
Email VARCHAR(100) UNIQUE
);
PRIMARY KEY
Dùng để định nghĩa khóa chính, giúp xác định duy nhất mỗi bản ghi trong bảng.
CREATE TABLE Departments (
DeptID INT PRIMARY KEY,
DeptName NVARCHAR(50) NOT NULL
);
Hoặc trên nhiều cột:
CREATE TABLE CourseRegistrations (
StudentID INT,
CourseID INT,
PRIMARY KEY (StudentID, CourseID)
);
FOREIGN KEY
Dùng để 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: Nếu bản ghi trong bảngStudentsbị xóa, tất cả các bản ghi liên quan trongEnrollmentscũng bị xóa.ON UPDATE CASCADE: NếuStudentIDbị cập nhật trongStudents, nó sẽ tự động cập nhật trongEnrollments.ON DELETE SET NULL: Nếu một khóa chính trong bảngCoursesbị xóa, các khóa ngoại tham chiếu đến nó trongEnrollmentssẽ được đặt thànhNULL.ON UPDATE NO ACTION: NếuCourseIDbị cập nhật trongCourses, thao tác sẽ bị từ chối nếu có ràng buộc khóa ngoại.- Mặc định của FOREIGN KEY trong SQL Server:
ON DELETE NO ACTION,ON UPDATE NO ACTION. SQL Server sẽ từ chối thao tác nếu có ràng buộc khóa ngoại.
CHECK
Dùng để đảm bảo dữ liệu nhập vào phải thỏa mãn điều kiện.
CREATE TABLE Employees (
EmployeeID INT IDENTITY(1,1) PRIMARY KEY,
Age INT CHECK (Age >= 18) -- Nhân viên phải từ 18 tuổi trở lên
);
DEFAULT
Dùng để đặt giá trị mặc định cho cột khi không có giá trị nào được nhập.
CREATE TABLE Orders (
OrderID INT PRIMARY KEY,
OrderDate DATETIME DEFAULT GETDATE() -- Mặc định ngày hiện tại
);
IDENTITY
Dùng để 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 IDENTITY(1,1) PRIMARY KEY,
Name NVARCHAR(100) NOT NULL
);
IDENTITY(1,1): Giá trị khởi đầu từ1, mỗi lần thêm dữ liệu sẽ tăng1.- Có thể tùy chỉnh bước nhảy, ví dụ
IDENTITY(100,10)sẽ bắt đầu từ100và tăng10mỗi lần.
Lấy giá trị IDENTITY mới nhất
SELECT SCOPE_IDENTITY(); -- Lấy giá trị IDENTITY vừa chèn gần nhất
Đặt lại giá trị IDENTITY
DBCC CHECKIDENT ('Employees', RESEED, 1); -- Đặt lại giá trị IDENTITY về 1
Lưu ý
IDENTITYchỉ áp dụng cho cột số nguyên (INT,BIGINT,SMALLINT,TINYINT).NOT NULLthường nên đặt trướcUNIQUEhoặcCHECKđể tránh lỗi logic.DEFAULTcó thể khai báo trước hoặc sauCHECK, nhưng SQL Server sẽ luôn áp dụngDEFAULTtrước nếu không có giá trị nhập vào.FOREIGN KEYthường được đặt cuối cùng để dễ đọc, nhưng vẫn có thể đặt ở vị trí khác.
Ví dụ tạo bảng có đầy đủ ràng buộc
CREATE TABLE Departments (
DeptID INT PRIMARY KEY, -- Khóa chính
DeptName NVARCHAR(100) NOT NULL UNIQUE -- Tên phòng ban không được trùng
);
CREATE TABLE Employees (
EmployeeID INT IDENTITY(1,1) PRIMARY KEY, -- ID tự động tăng
Name NVARCHAR(100) NOT NULL, -- Không được để trống
Email VARCHAR(100) UNIQUE, -- Email không được trùng
Birthdate DATE CHECK (Birthdate >= '1990-01-01'), -- Ngày sinh phải sau năm 1990
Salary DECIMAL(10,2) DEFAULT 1000.00, -- Mặc định lương là 1000
DepartmentID INT,
FOREIGN KEY (DepartmentID) REFERENCES Departments(DeptID) -- Khóa ngoại
);
Thao tác dữ liệu
Thêm dữ liệu (INSERT INTO)
Dùng để thêm dữ liệu vào bảng.
Thêm một sinh viên mới vào bảng Students:
INSERT INTO Students (Name, Birthdate) VALUES (N'Hoàng Hiệp', '2001-05-20');
Cập nhật dữ liệu (UPDATE)
Dùng để cập nhật dữ liệu trong bảng.
Cập nhật ngày sinh cho sinh viên có StudentID là 4:
UPDATE Students SET Birthdate = '2000-06-15' WHERE StudentID = 4;
Xóa dữ liệu (DELETE)
Dùng để xóa dữ liệu trong bảng.
Xóa sinh viên có StudentID là 2:
DELETE FROM Students WHERE StudentID = 2;
Lưu ý:
DELETEcó thể kết hợp vớiWHEREđể xóa có điều kiện.- 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ó trên bảng.- Không reset giá trị
IDENTITY.
Xóa toàn bộ dữ liệu (TRUNCATE TABLE)
Dùng để xóa toàn bộ dữ liệu trong bảng mà không ghi log từng dòng, nhanh hơn
DELETE.
TRUNCATE TABLE Students;
Lưu ý:
- Không thể sử dụng
WHERE. - Không kích hoạt trigger.
- Reset giá trị
IDENTITY.
Sao chép dữ liệu vào bảng mới (SELECT INTO)
Dùng để tạo bảng mới và sao chép dữ liệu từ bảng hiện có.
Tạo bảng StudentsBackup và sao chép toàn bộ dữ liệu từ Students:
SELECT * INTO StudentsBackup FROM Students;
Chèn dữ liệu từ bảng khác (INSERT INTO SELECT)
Dùng để chèn dữ liệu từ một bảng vào một bảng khác.
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)
Dùng để hợp nhất dữ liệu từ hai bảng (INSERT, UPDATE, DELETE dựa trên điều kiện).
MERGE INTO StudentsBackup AS target
USING Students AS source
ON target.StudentID = source.StudentID
WHEN MATCHED THEN
UPDATE SET target.Name = source.Name, target.Birthdate = source.Birthdate
WHEN NOT MATCHED THEN
INSERT (Name, Birthdate) VALUES (source.Name, source.Birthdate)
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
Câu lệnh trên:
- Cập nhật thông tin nếu
StudentIDđã tồn tại. - Thêm dữ liệu mới nếu
StudentIDchưa có. - Xóa sinh viên nếu không có trong bảng nguồn.
Kiểm tra tồn tại (EXISTS)
Dùng để kiểm tra dữ liệu trước khi thực hiện thao tác.
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 = N'Hoàng Hiệp')
PRINT N'Sinh viên đã tồn tại';
ELSE
INSERT INTO Students (Name, Birthdate) VALUES (N'Hoàng Hiệp', '2001-05-20');
Lấy dữ liệu bị ảnh hưởng (OUTPUT)
Dùng để lấy dữ liệu bị ảnh hưởng sau thao tác INSERT, UPDATE,
DELETE.
Xóa các sinh viên sinh trước năm 2000 và hiển thị danh sách sinh viên bị xóa:
DELETE FROM Students
OUTPUT deleted.StudentID, deleted.Name
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 SQL:
| 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
Dùng để kết hợp nhiều điều kiện trong câu lệnh WHERE.
Lấy danh sách sinh viên có tên bắt đầu bằng chữ 'P' và có ngày sinh sau 01/01/2000:
SELECT * FROM Students
WHERE Name LIKE N'P%' AND Birthdate > '2000-01-01';
OR
Dùng để lọc dữ liệu với một trong các điều kiện đúng.
Lấy danh sách sinh viên có tên là “Trần Nhật Anh” hoặc “Vũ Khánh Linh”:
SELECT * FROM Students
WHERE Name = N'Trần Nhật Anh' OR Name = N'Vũ Khánh Linh';
NOT
Dùng để phủ định điều kiện.
Lấy danh sách tất cả sinh viên trừ sinh viên có tên là “Hoàng Hiệp”:
SELECT * FROM Students
WHERE NOT Name = N'Trần Nhật Anh';
LIKE
Dùng để tìm kiếm dữ liệu theo mẫu ký tự.
Lấy danh sách sinh viên có tên bắt đầu bằng chữ “T”:
SELECT * FROM Students
WHERE Name LIKE N'T%';
Wildcards (Ký tự đại diện)
Dùng để tìm kiếm linh hoạt với các ký tự đại diện (%, _).
Tìm sinh viên có tên chứa chữ “ng”:
SELECT * FROM Students
WHERE Name LIKE '%ng%';
Tìm sinh viên có tên bắt đầu bằng “Vũ Khánh” và có 5 ký tự tiếp theo:
SELECT * FROM Students
WHERE Name LIKE N'Vũ Khánh_____';
IN
Dùng để lọc dữ liệu theo danh sách giá trị cụ thể.
Lấy thông tin của sinh viên có tên là “Vũ Khánh Linh”, “Trần Nhật Anh” hoặc “Lê Thu Hà”:
SELECT * FROM Students
WHERE Name IN (N'Vũ Khánh Linh', N'Trần Nhật Anh', N'Lê Thu Hà');
BETWEEN
Dùng để lọc dữ liệu theo khoảng giá trị.
Lấy danh sách sinh viên sinh trong khoảng từ 2000 đến 2003:
SELECT * FROM Students
WHERE Birthdate BETWEEN '2000-01-01' AND '2003-12-31';
IS NULL và IS NOT NULL
Dùng để kiểm tra giá trị NULL trong bảng.
Lấy danh sách sinh viên chưa có ngày sinh:
SELECT * FROM Students WHERE Birthdate IS NULL;
Lấy danh sách sinh viên đã có ngày sinh:
SELECT * FROM Students WHERE Birthdate IS NOT NULL;
ANY và ALL
Dùng để so sánh với tập hợp con của dữ liệu.
ANYtrả vềTRUEnếu có ít nhất một giá trị thỏa mãn điều kiện.ALLchỉ trả vềTRUEnếu tất cả các giá trị đều thỏa mãn điều kiện.
Lấy danh sách sinh viên có ngày sinh muộn hơn ít nhất một sinh viên trong danh sách tên "Vũ Khánh Linh" hoặc "Trần Nhật Anh":
SELECT * FROM Students
WHERE Birthdate > ANY (SELECT Birthdate FROM Students WHERE Name IN (N'Vũ Khánh Linh', N'Trần Nhật Anh'));
Lấy danh sách sinh viên có ngày sinh muộn hơn ít nhất một sinh viên trong danh sách tên "Vũ Khánh Linh" hoặc "Trần Nhật Anh":
SELECT * FROM Students
WHERE Birthdate > ALL (SELECT Birthdate FROM Students WHERE Name IN (N'Vũ Khánh Linh', N'Trần Nhật Anh'));
EXISTS
Dùng để kiểm tra sự tồn tại của dữ liệu.
Lấy danh sách sinh viên đã đăng ký khóa học:
SELECT Name FROM Students
WHERE EXISTS (SELECT 1 FROM Enrollments WHERE Students.StudentID = Enrollments.StudentID);
IF ELSE
IF ELSE là cấu trúc điều kiện điều khiển luồng thực thi, thường được sử dụng
trong stored procedures,
functions.IF ELSE không thể dùng trong
SELECT.
Cú pháp
IF <điều kiện>
BEGIN
-- Khối lệnh thực thi nếu điều kiện đúng
END
ELSE
BEGIN
-- Khối lệnh thực thi nếu điều kiện sai
END
Kiểm tra nếu số lượng sinh viên trong bảng Students có lớn hơn 100 hay không:
DECLARE @TotalStudents INT;
SELECT @TotalStudents = COUNT(*) FROM Students;
IF @TotalStudents > 100
BEGIN
PRINT N'Có hơn 100 sinh viên trong hệ thống.';
END
ELSE
BEGIN
PRINT N'Số lượng sinh viên ít hơn hoặc bằng 100.';
END
IIF
IIF hỗ trợ một điều kiện và trả về hai giá
trị.IIF có thể dùng trong SELECT, UPDATE,
WHERE, ORDER BY, GROUP BY, HAVING,...
Cú pháp
IIF(<điều kiện>, <giá trị nếu đúng>, <giá trị nếu sai>)
Kiểm tra sinh viên có đủ 18 tuổi không:
SELECT
StudentID,
Name,
IIF(DATEDIFF(YEAR, Birthdate, GETDATE()) >= 18, N'Đủ tuổi', N'Chưa đủ tuổi') AS AgeStatus
FROM Students;
CASE WHEN
CASE WHEN hỗ trợ nhiều điều kiện.CASE WHEN có
thể dùng trong SELECT, UPDATE, WHERE,
ORDER BY, GROUP BY, HAVING,...
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(YEAR, Birthdate, GETDATE()) < 18 THEN 'Teen'
WHEN DATEDIFF(YEAR, Birthdate, GETDATE()) 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 N'Toán học'
WHEN 2 THEN N'Lập trình'
ELSE N'Ngôn ngữ'
END AS CourseCategory
FROM Courses;
Truy vấn dữ liệu
SELECT
Câu lệnh SELECT được sử dụng để truy vấn dữ liệu từ bảng.
Lấy toàn bộ dữ liệu từ bảng Students:
SELECT * FROM Students;
Lấy tên và ngày sinh từ bảng Students:
SELECT Name, Birthdate FROM Students;
SELECT DISTINCT
Dùng để loại bỏ các giá trị trùng lặp trong kết quả truy vấn.
Lấy danh sách tên các sinh viên không trùng lặp:
SELECT DISTINCT Name FROM Students;
WHERE
Dùng để lọc dữ liệu theo điều kiện.
Lấy danh sách sinh viên có ngày sinh sau ngày 01/01/2001:
SELECT * FROM Students WHERE Birthdate > '2001-01-01';
ORDER BY
Dùng để sắp xếp kết quả theo thứ tự tăng dần hoặc giảm dần.
Sắp xếp danh sách sinh viên theo tên theo thứ tự tăng dần:
SELECT * FROM Students ORDER BY Name ASC;
Sắp xếp danh sách sinh viên theo ngày sinh giảm dần:
SELECT * FROM Students ORDER BY Birthdate DESC;
SELECT TOP
Dùng để giới hạn số lượng kết quả trả về.
Lấy 3 sinh viên trẻ tuổi nhất:
SELECT TOP 3 * FROM Students ORDER BY Birthdate DESC;
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.
Chỉ lấy những sinh viên đã đăng ký khóa học:
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 và dữ liệu khớp từ bảng bên phải.
Lấy tất cả sinh viên, kể cả những sinh viên chưa đăng ký khóa học:
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
Tương tự LEFT JOIN, nhưng lấy tất cả dữ liệu từ bảng bên phải.
Lấy tất cả khóa học, kể cả khóa học chưa có sinh viên đăng ký:
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
Lấy tất cả dữ liệu từ cả hai bảng, kể cả khi không có bản ghi tương ứng.
SELECT s.StudentID, s.Name AS StudentName, c.CourseID, c.Name AS CourseName
FROM Students s
FULL JOIN Enrollments e ON s.StudentID = e.StudentID
FULL JOIN Courses c ON e.CourseID = c.CourseID;
CROSS JOIN
Kết hợp tất cả các hàng của hai bảng (tích Descartes).
SELECT s.Name AS StudentName, c.Name AS CourseName
FROM Students s
CROSS JOIN Courses c;
Câu lệnh trên sẽ tạo tất cả các cặp kết hợp giữa sinh viên và khóa học.
SELF JOIN
Dùng để nối một bảng với chính nó.
Giả sử bảng Students có thêm cột MentorID để chỉ định người hướng
dẫ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;
Câu lệnh trên lấy danh sách sinh viên cùng với tên người hướng dẫn của họ.
UNION
Kết hợp dữ liệu từ nhiều truy vấn SELECT, loại bỏ bản ghi trùng lặp.
SELECT Name FROM Students
UNION
SELECT Name FROM Courses;
Câu lệnh trên lấy tất cả tên từ bảng Students và Courses mà không
trùng lặp.
UNION ALL
Tương tự UNION, nhưng không loại bỏ bản ghi trùng lặp.
SELECT Name FROM Students
UNION ALL
SELECT Name FROM Courses;
EXCEPT
Lấy các bản ghi có trong tập kết quả đầu tiên nhưng không có trong tập kết quả thứ hai.
SELECT Name FROM Students
EXCEPT
SELECT Name FROM Courses;
Lấy danh sách sinh viên không trùng với tên khóa học.
INTERSECT
Lấy các bản ghi xuất hiện trong cả hai tập kết quả.
SELECT Name FROM Students
INTERSECT
SELECT Name FROM Courses;
Câu lệnh trên lấy danh sách tên xuất hiện trong cả Students và
Courses.
Hàm tổng hợp
COUNT
Dùng để đếm số lượng bản ghi khác NULL trong bảng.
Đếm tổng số sinh viên trong bảng Students:
SELECT COUNT(*) AS TotalStudents FROM Students;
SUM
Dùng để tính tổng giá trị của một cột số.
Nếu bảng Enrollments có một cột Fee lưu học phí, tính tổng học phí
của tất cả khóa học như sau:
ALTER TABLE Enrollments
ADD Fee INT;
SELECT SUM(Fee) AS TotalFees FROM Enrollments;
AVG
Dùng để tính giá trị trung bình của một cột số.
Tính học phí trung bình của tất cả khóa học:
SELECT AVG(Fee) AS AverageFee FROM Enrollments;
MIN và MAX
Dùng để tìm giá trị nhỏ nhất và lớn nhất của một cột trong bảng.
Tìm ngày sinh nhỏ nhất và lớn nhất trong bảng Students:
SELECT MIN(Birthdate) AS Oldest, MAX(Birthdate) AS Youngest FROM Students;
GROUP BY
Dùng để nhóm dữ liệu theo một hoặc nhiều cột khi sử dụng các hàm tổng hợp.
Tính tổng học phí của từng khóa học:
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
Câu lệnh trên sẽ nhóm dữ liệu theo CourseID và tính tổng học phí cho từng khóa
học.
HAVING
Dùng để lọc dữ liệu sau khi đã tổng hợp (tương tự WHERE, nhưng dành cho các nhóm
dữ liệu).
Chỉ lấy những khóa học có tổng học phí lớn hơn hoặc bằng 20:
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
Lưu ý: HAVING được dùng để lọc dữ liệu sau khi dùng GROUP BY, trong
khi WHERE dùng trước khi nhóm dữ liệu.
COUNT DISTINCT
Dùng để đếm số lượng giá trị khác nhau (không trùng lặp).
Đếm số lượng sinh viên (không trùng lặp) đã đăng ký vào từng khóa học:
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
Dùng để ghép các giá trị từ nhiều hàng thành một chuỗi, phân tách bằng dấu phân cách.
Lấy danh sách tên sinh viên theo từng lớp học:
SELECT ClassID, STRING_AGG(Name, ', ') AS StudentList
FROM Students
GROUP BY ClassID;
Câu lệnh trên sẽ tạo danh sách tên sinh viên trong từng lớp, ngăn cách bởi dấu phẩy.
STDEV và VAR
Dùng để tính độ lệch chuẩn (STDEV) và phương sai (VAR).
Tính độ lệch chuẩn và phương sai của điểm số trong bảng Scores:
SELECT STDEV(Score) AS StandardDeviation, VAR(Score) AS Variance FROM Scores;
Câu lệnh trên giúp đánh giá mức độ phân tán của điểm số.
Index
Index 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 trong bảng, đặc biệt hữu ích khi truy vấn nhiều dữ liệu.
Chỉ mục CLUSTERED và NONCLUSTERED
- Chỉ mục
CLUSTERED: Dữ liệu trong bảng được sắp xếp theo chỉ mục này. Mỗi bảng chỉ có một chỉ mụcCLUSTERED. - Chỉ mục
NONCLUSTERED: Lưu trữ dữ liệu theo thứ tự riêng biệt và có thể có nhiều chỉ mụcNONCLUSTEREDtrong một bảng.
Ví dụ: Tạo bảng Employees với Index
CREATE TABLE Employees (
EmployeeID INT IDENTITY(1,1) PRIMARY KEY, -- 📌 Clustered Index
Name NVARCHAR(100) NOT NULL,
Email VARCHAR(100) UNIQUE, -- 🔍 Nonclustered Index
DepartmentID INT,
INDEX IX_Employee_Name (Name) -- 🔍 Nonclustered Index
);
Bảng Employees có các chỉ mục sau:
Chỉ mục Clustered trên EmployeeID (do PRIMARY KEY)
✅ Loại: Clustered Index
📝 Giải thích:
PRIMARY KEY sẽ tự động tạo Clustered Index, sắp xếp dữ liệu
theo EmployeeID.
⚡ Tác dụng: Tăng tốc tìm kiếm khi sử dụng
khóa chính (WHERE EmployeeID = ...).
Chỉ mục Nonclustered trên Email (do UNIQUE)
✅ Loại: Nonclustered Index
📝 Giải thích:
UNIQUE tự động tạo Nonclustered Index, đảm bảo dữ liệu không
trùng.
⚡ Tác dụng: Tăng tốc tìm kiếm email
(WHERE Email = ...).
Chỉ mục Nonclustered trên Name (INDEX
IX_Employee_Name)
✅ Loại: Nonclustered Index
📝 Giải thích:
INDEX IX_Employee_Name (Name) tạo Nonclustered Index riêng
trên Name.
⚡ Tác dụng: Tăng tốc tìm kiếm hoặc sắp xếp theo
Name (ORDER BY Name).
So sánh Clustered vs. Nonclustered Index
| Tiêu chí | 📌 Clustered Index | 🔍 Nonclustered Index |
|---|---|---|
| Sắp xếp dữ liệu bảng? | ✅ Có (sắp xếp vật lý) | ❌ Không (dữ liệu bảng không thay đổi) |
| Số lượng trong 1 bảng? | ❗ Chỉ 1 | ✅ Nhiều |
Tốc độ truy vấn (WHERE, ORDER BY,
JOIN) |
⚡ Rất nhanh | 🚀 Nhanh nhưng cần trỏ đến dữ liệu |
Khi dữ liệu thay đổi (INSERT, UPDATE,
DELETE)? |
❌ Có thể chậm (sắp xếp lại bảng) | ✅ Ít ảnh hưởng hơn |
Lưu ý khi sử dụng Index
✅ Tăng tốc truy vấn WHERE, ORDER BY,
JOIN, đặc biệt với bảng lớn.
⚠️ Làm chậm
INSERT, UPDATE, DELETE do phải cập nhật lại chỉ mục.
SQL nâng cao
Subquery (Truy vấn con)
Truy vấn con (subquery) là một truy vấn được lồng trong một truy vấn khác giúp tách biệt logic.
Single-row Subquery (Truy vấn con trả về một hàng)
- Dùng với các toán tử so sánh:
=,>,<,>=,<=
Lấy các sinh viên có ngày sinh sớm nhất:
SELECT * FROM Students
WHERE Birthdate = (SELECT MIN(Birthdate) FROM Students);
Multiple-row Subquery (Truy vấn con trả về nhiều hàng)
- Dùng với toán tử:
IN,ANY,ALL.
Lấy danh sách sinh viên có cùng năm sinh với những sinh viên có tên "Trần Nhật Anh" hoặc "Phạm Thanh Hương":
SELECT * FROM Students
WHERE YEAR(Birthdate) IN (
SELECT YEAR(Birthdate) FROM Students
WHERE Name = N'Trần Nhật Anh' OR Name = N'Phạm Thanh Hương'
);
Lọc ra danh sách các khóa học mà sinh viên có StudentID = 1 chưa đăng ký:
SELECT CourseID, Name
FROM Courses
WHERE CourseID NOT IN (
SELECT CourseID FROM Enrollments WHERE StudentID = 1
);
Correlated Subquery (Truy vấn con tương quan)
- Truy vấn con phụ thuộc vào truy vấn cha và được thực thi nhiều lần (mỗi dòng trong truy vấn cha chạy lại subquery).
Lấy các sinh viên có ngày sinh sớm nhất trong mỗi khóa học:
SELECT C.CourseID, C.Name AS CourseName, S.StudentID, S.Birthdate
FROM Courses C
LEFT JOIN Enrollments E ON C.CourseID = E.CourseID
LEFT JOIN Students S ON E.StudentID = S.StudentID
WHERE S.Birthdate = (
SELECT MIN(S2.Birthdate)
FROM Students S2
JOIN Enrollments E2 ON S2.StudentID = E2.StudentID
WHERE E2.CourseID = C.CourseID
) OR S.StudentID IS NULL;
Derived Table (Bảng dẫn xuất)
- Là truy vấn con được sử dụng trong mệnh đề FROM của một truy vấn chính.
- Derived Table bắt buộc phải có alias (tên bảng dẫn xuất).
Các sinh viên nhỏ hơn 23 tuổi:
SELECT *
FROM (
SELECT *, DATEDIFF(YEAR, Birthdate, GETDATE()) AS AGE
FROM Students
) AS S
WHERE S.AGE < 23;
Câu lệnh trên tránh lặp lại việc tính tuổi trong điều kiện WHERE.
Subquery với EXISTS
Các khóa học có ít nhất một sinh viên đăng ký:
SELECT * FROM Courses
WHERE EXISTS (
SELECT 1 FROM Enrollments WHERE CourseID = Courses.CourseID
);
Câu lệnh trên dùng EXISTS để kiểm tra xem có bản ghi nào trong
Enrollments trùng với CourseID của bảng Courses
không.
Window Function
Window Function là một nhóm các hàm thực hiện tính toán trên một tập hợp các hàng liên quan đến từng hàng trong tập kết quả. Không giống như các Aggregate Function thông thường (SUM, AVG, COUNT…), Window Function không nhóm các hàng lại mà vẫn giữ nguyên từng hàng riêng lẻ.
Window Function thường được sử dụng với mệnh đề OVER(), giúp xác định phạm vi
tính toán cho từng hàng.
Cấu trúc cơ bản của Window Function:
<Window Function> OVER (
PARTITION BY column_name -- Chia nhóm (tùy chọn)
ORDER BY column_name -- Sắp xếp theo cột (bắt buộc với một số hàm)
)
Một số Window Function phổ biến:
- Hàm xếp hạng:
ROW_NUMBER(),RANK(),DENSE_RANK() - Hàm tổng hợp:
SUM(),AVG(),MIN(),MAX() - Hàm phân tích dữ liệu:
LEAD(),LAG(),FIRST_VALUE(),LAST_VALUE()
Chuẩn bị dữ liệu liên quan đến GPA:
CREATE TABLE Students (
StudentID INT IDENTITY(1,1) PRIMARY KEY,
Name NVARCHAR(50) NOT NULL,
Class NVARCHAR(10) NOT NULL,
GPA DECIMAL(3,2) NOT NULL DEFAULT 0.00
);
INSERT INTO Students (Name, Class, GPA)
VALUES
(N'Nguyễn Văn A', N'10A1', 3.8),
(N'Trần Thị B', N'10A1', 3.5),
(N'Lê Văn C', N'10A1', 3.9),
(N'Phạm Minh D', N'10A2', 3.5),
(N'Đỗ Hoàng E', N'10A2', 3.2),
(N'Nguyễn Thị F', N'10A2', 3.7);
Hàm xếp hạng
Các hàm xếp hạng giúp đánh số thứ tự cho từng dòng trong một tập kết quả.
Xếp hạng sinh viên theo GPA:
SELECT
StudentID, Name, GPA,
ROW_NUMBER() OVER (ORDER BY GPA DESC) AS RowNum, -- Số thứ tự duy nhất
RANK() OVER (ORDER BY GPA DESC) AS RankNum, -- Xếp hạng (có thể bỏ số)
DENSE_RANK() OVER (ORDER BY GPA DESC) AS DenseRankNum -- Xếp hạng liên tục
FROM Students;
Sự khác biệt giữa các hàm xếp hạng:
| Trường hợp cần dùng | Hàm phù hợp | Cách hoạt động |
|---|---|---|
| Cần số thứ tự duy nhất, không quan tâm đến giá trị trùng | ROW_NUMBER() |
Luôn tăng đều (1, 2, 3, ...) |
| Cần xếp hạng nhưng cho phép bỏ số khi có giá trị trùng | RANK() |
Nếu có giá trị trùng, thứ hạng tiếp theo sẽ bị bỏ qua (1, 2, 2, 4, ...) |
| Cần xếp hạng liên tục, không bỏ số thứ hạng | DENSE_RANK() |
Nếu có giá trị trùng, thứ hạng tiếp theo vẫn liền kề (1, 2, 2, 3, ...) |
Hàm tổng hợp
Trung bình GPA của từng lớp:
SELECT
StudentID, Name, Class, GPA,
AVG(GPA) OVER (PARTITION BY Class) AS AvgGPA
FROM Students;
Hàm phân tích dữ liệu
Lấy điểm GPA gần nhất trước và sau của từng sinh viên:
SELECT
StudentID, Name, Class, GPA,
LAG(GPA) OVER (ORDER BY GPA) AS PreviousGPA,
LEAD(GPA) OVER (ORDER BY GPA) AS NextGPA
FROM Students;
CTE (Common Table Expression)
CTE (Common Table Expression) là một biểu thức bảng chung được đặt tên trong phạm vi của một truy vấn SQL. Nó giúp cải thiện khả năng đọc và tổ chức truy vấn, đặc biệt hữu ích khi làm việc với các truy vấn đệ quy hoặc các phép tính phức tạp.
Cú pháp của CTE:
WITH CTE1 (Column1, Column2, ...) AS (
-- Câu lệnh truy vấn để tạo CTE
SELECT Column1, Column2, ...
FROM SomeTable
WHERE SomeCondition
),
CTE2 (Column1, Column2, ...) AS (
-- Câu lệnh truy vấn để tạo CTE khác (có thể dùng CTE trên)
SELECT Column1, Column2, ...
FROM AnotherTable
WHERE AnotherCondition
)
-- Sử dụng CTE trong câu truy vấn chính
SELECT * FROM CTE1;
Tính tuổi của tất cả sinh viên, sau đó tính tuổi trung bình, sau đó lọc ra những sinh viên có tuổi lớn hơn tuổi trung bình:
WITH StudentAgeCTE AS (
SELECT StudentID, Name, DATEDIFF(YEAR, Birthdate, GETDATE()) AS Age
FROM Students
),
AvgAgeCTE AS (
-- Câu lệnh truy vấn để tạo CTE khác (có thể dùng CTE trên)
SELECT AVG(Age) AS AvgAge FROM StudentAgeCTE
)
SELECT s.StudentID, s.Name, s.Age
FROM StudentAgeCTE s
WHERE s.Age > (SELECT AvgAge FROM AvgAgeCTE);
CTE đệ quy
CTE hỗ trợ truy vấn đệ quy, giúp xử lý dữ liệu có quan hệ cha –
con hữu ích trong các hệ thống như:
✅ Hệ thống nhân sự
(cấp bậc quản lý)
✅ Cây thư mục (file, folder)
✅ Menu đa
cấp
✅ Mô hình giới thiệu (referral system)
Tạo dữ liệu mẫu (giả sử mỗi sinh viên có thể được một sinh viên khác giới thiệu
(ReferredBy), tạo thành cấu trúc cây):
CREATE TABLE Students (
StudentID INT PRIMARY KEY,
StudentName NVARCHAR(50) NOT NULL,
DateOfBirth DATE,
ReferredBy INT NULL -- ID của sinh viên giới thiệu
);
INSERT INTO Students (StudentID, StudentName, DateOfBirth, ReferredBy)
VALUES
(1, N'An', '2000-05-15', NULL), -- Không ai giới thiệu
(2, N'Bình', '1998-08-22', 1), -- Được An giới thiệu
(3, N'Chi', '2002-11-30', 1), -- Được An giới thiệu
(4, N'Dũng', '1995-04-10', 2), -- Được Bình giới thiệu
(5, N'Em', '2001-07-19', 2), -- Được Bình giới thiệu
(6, N'Phong', '1999-06-08', 3); -- Được Chi giới thiệu
Dùng đệ quy để xây dựng quan hệ sinh viên - người giới thiệu từ bảng
Students:
WITH ReferralCTE (StudentID, StudentName, ReferredBy, Level)
AS (
-- Bước cơ sở: Sinh viên khởi đầu (không có ai giới thiệu)
SELECT StudentID, StudentName, ReferredBy, 1 AS Level
FROM Students
WHERE ReferredBy IS NULL -- Gốc của hệ thống giới thiệu (An)
UNION ALL
-- Bước đệ quy: Tìm các sinh viên được giới thiệu bởi những người trước đó
SELECT s.StudentID, s.StudentName, s.ReferredBy, c.Level + 1
FROM Students s
INNER JOIN ReferralCTE c ON s.ReferredBy = c.StudentID
)
SELECT * FROM ReferralCTE ORDER BY Level, StudentID;
Giải thích
-
Bước cơ sở (
WHERE ReferredBy IS NULL)- Chọn sinh viên khởi đầu (không ai giới thiệu, tức là
An–StudentID = 1). - Thiết lập Level = 1 cho sinh viên này.
- Chọn sinh viên khởi đầu (không ai giới thiệu, tức là
-
Bước đệ quy
- Tìm sinh viên có ReferredBy trùng với
StudentIDtừ bước trước. - Tăng cấp bậc (
Level + 1) để thể hiện mối quan hệ mentor-mentee.
- Tìm sinh viên có ReferredBy trùng với
Temp Table (Bảng tạm)
Temp Table (Bảng tạm) trong SQL Server là một bảng dữ liệu tạm thời được lưu trữ trong tempdb. Nó chỉ tồn tại trong session hoặc scope hiện tại và sẽ bị xóa khi session kết thúc.
Có 2 loại chính:
| Loại | Cú pháp | Tồn tại đến khi nào? | Mô tả |
|---|---|---|---|
| Local Temp Table | #TempTable |
Khi session hiện tại kết thúc | Chỉ sử dụng trong session tạo ra nó |
| Global Temp Table | ##GlobalTempTable |
Khi tất cả session sử dụng nó kết thúc | Có thể dùng chung cho nhiều session |
Tạo và sử dụng Local Temp Table
CREATE TABLE #TempStudents (
StudentID INT PRIMARY KEY,
StudentName NVARCHAR(50),
GPA DECIMAL(3,2)
);
INSERT INTO #TempStudents (StudentID, StudentName, GPA) VALUES
(1, N'Nguyễn Văn A', 3.5),
(2, N'Trần Thị B', 3.8),
(3, N'Lê Văn C', 2.9);
SELECT * FROM #TempStudents;
DROP TABLE #TempStudents; -- Xóa bảng tạm
Bảng trên chỉ tồn tại trong session hiện tại và tự động xóa khi session kết thúc.
Temp Table có thể có Primary Key, Unique, Index, Foreign Key...
CREATE TABLE #TempCourses (
CourseID INT PRIMARY KEY,
CourseName NVARCHAR(100),
Credits INT CHECK (Credits >= 1)
);
Bảng trên có ràng buộc CHECK để đảm bảo Credits không âm.
Temp Table với Stored Procedure
Temp Table rất hữu ích trong Stored Procedure để xử lý dữ liệu tạm thời.
CREATE PROCEDURE GetTopStudents
AS
BEGIN
CREATE TABLE #TopStudents (
StudentID INT,
StudentName NVARCHAR(50),
GPA DECIMAL(3,2)
);
INSERT INTO #TopStudents
SELECT StudentID, StudentName, GPA FROM Students WHERE GPA >= 3.5;
SELECT * FROM #TopStudents;
END;
Temp Table sẽ bị xóa sau khi Stored Procedure chạy xong.
Khi nào nên dùng Temp Table?
✅ Khi cần lưu trữ dữ liệu tạm và sử dụng lại nhiều lần trong một session.
✅ Khi cần tối
ưu truy vấn bằng Index trên dữ liệu tạm thời.
✅ Khi xử lý dữ liệu phức tạp trong Stored
Procedure.
So sánh Temp Table, Table Variable và CTE
| Đặc điểm | Temp Table (#TempTable) |
Table Variable (@TableVariable) |
CTE (WITH CTE) |
|---|---|---|---|
| Tạm thời | ✅ | ✅ | ✅ |
| Có thể tạo Index | ✅ | ❌ | ❌ |
| Tồn tại lâu hơn 1 query | ✅ | ✅ | ❌ |
| Hiệu suất | Tốt với dữ liệu lớn | Tốt với dữ liệu nhỏ | Nhanh với truy vấn đơn giản |
🔹 Dùng Temp Table khi làm việc với nhiều dữ liệu và cần Index.
🔹
Dùng Table Variable khi chỉ cần lưu ít dữ liệu tạm.
🔹 Dùng CTE
khi chỉ cần dùng dữ liệu tạm trong 1 query.
Views (Bảng ảo)
View là một bảng ảo được tạo ra từ một hoặc nhiều bảng trong cơ sở dữ liệu. Nó không lưu trữ dữ liệu thực tế mà chỉ là một tập hợp kết quả từ một truy vấn SQL.
Tạo views truy vấn danh sách sinh viên cùng với khóa học mà họ đăng ký:
CREATE VIEW StudentCourses AS
SELECT e.EnrollmentID, s.Name AS StudentName, c.Name AS CourseName, E.EnrollmentDate
FROM Students s
JOIN Enrollments e ON s.StudentID = e.StudentID
JOIN Courses c ON e.CourseID = c.CourseID;
Gọi Views
SELECT * FROM StudentCourses;
Chỉnh sửa View
ALTER VIEW View_Name AS
SELECT Column1, Column2
FROM Table_Name
WHERE New_Condition;
Xóa View
DROP VIEW View_Name;
Indexed View (Materialized View)
🔹 Indexed View là View có Index giúp truy vấn nhanh hơn.
🔹 SQL Server
hỗ trợ Indexed View, nhưng cần dùng WITH SCHEMABINDING:
CREATE VIEW HighGPAStudentsIndexed
WITH SCHEMABINDING
AS
SELECT StudentID, StudentName, GPA
FROM dbo.Students
WHERE GPA >= 3.5;
🔹 Sau đó, tạo Index:
CREATE UNIQUE CLUSTERED INDEX IX_HighGPA
ON HighGPAStudentsIndexed(StudentID);
View có thể cập nhật được không?
🔹 Có thể cập nhật View nếu:
✅ Nó chỉ dựa trên một
bảng.
✅ Không có hàm tổng hợp (SUM, AVG, COUNT...).
✅
Không có DISTINCT, GROUP BY, HAVING, TOP, UNION.
🔹 Ví dụ cập nhật dữ liệu qua View
UPDATE HighGPAStudents
SET GPA = 3.9
WHERE StudentID = 1;
Khi nào nên dùng Views?
✅ Khi cần bảo vệ dữ liệu gốc, chỉ cho phép xem một phần dữ liệu.
✅ Khi
cần đơn giản hóa truy vấn (truy vấn dài, phức tạp).
✅ Khi cần
tăng hiệu suất truy vấn (Indexed View).
Stored Procedure (Thủ tục lưu trữ)
Stored Procedure (thủ tục lưu trữ) là một tập hợp các câu lệnh SQL được lưu trữ sẵn trong cơ sở dữ liệu và có thể thực thi nhiều lần mà không cần viết lại.
Ví dụ: Lấy danh sách sinh viên theo tuổi và tên:
Chúng ta sẽ tạo một thủ
tục nhận vào hai tham số:
@Age(INT) – Tuổi của sinh viên@Name(NVARCHAR) – Tên sinh viên
Thủ tục sẽ truy vấn danh sách sinh viên có tuổi và tên phù hợp từ bảng Students.
CREATE PROCEDURE GetStudentsByAgeAndName
@Age INT,
@Name NVARCHAR(50)
AS
BEGIN
SELECT *, DATEDIFF(YEAR, Birthdate, GETDATE()) AS Age
FROM Students
WHERE DATEDIFF(YEAR, Birthdate, GETDATE()) = @Age
AND Name LIKE N'%' + @Name + '%';
END;
Gọi Stored Procedure
EXEC GetStudentsByAgeAndName 25, N'Phạm';
EXEC GetStudentsByAgeAndName @age = 25, @name = N'Phạm';
Chỉnh sửa thủ tục lưu trữ
ALTER PROCEDURE GetStudentsByAgeAndName
@Age INT,
@Name VARCHAR(50)
AS
BEGIN
SELECT *, DATEDIFF(YEAR, Birthdate, GETDATE()) AS Age
FROM Students
WHERE DATEDIFF(YEAR, Birthdate, GETDATE()) = @Age
AND Name = @Name;
END;
Xóa thủ tục lưu trữ
DROP PROCEDURE GetStudentsByAgeAndName;
Stored Procedure có Output
Tạo một thủ tục lưu trữ nhận vào năm sinh và trả về số lượng sinh viên:
CREATE PROCEDURE CountStudentsByYear
@BirthYear INT,
@StudentCount INT OUTPUT
AS
BEGIN
-- Đếm số lượng sinh viên có năm sinh được chỉ định
SELECT @StudentCount = COUNT(*)
FROM Students
WHERE YEAR(BirthDate) = @BirthYear;
END;
Gọi Stored Procedure:
DECLARE @Total INT;
EXEC CountStudentsByYear 2000, @Total OUTPUT;
SELECT @Total AS CountStudentsByYear;
Dùng Views hay Stored Procedure
| Trường hợp | Dùng Views | Dùng Stored Procedure |
|---|---|---|
| Cần tái sử dụng một câu lệnh SELECT | ✅ | ❌ |
| Cần thực hiện các thao tác INSERT, UPDATE, DELETE | ❌ | ✅ |
| Cần xử lý logic phức tạp, điều kiện | ❌ | ✅ |
| Cần truyền tham số để lấy dữ liệu động | ❌ | ✅ |
User-Defined Functions (UDFs)
User-Defined Function (UDF) là các hàm do người dùng tự định nghĩa trong SQL Server.
Scalar Function (Hàm vô hướng)
- Trả về một giá trị đơn lẻ (số, chuỗi, ngày tháng...)
- Có thể dùng trong
SELECT,WHERE,ORDER BY
Ví dụ: Tạo hàm tính tuổi sinh viên dựa vào ngày sinh:
CREATE FUNCTION fn_CalculateAge (@Birthdate DATE)
RETURNS INT
AS
BEGIN
RETURN DATEDIFF(YEAR, @Birthdate, GETDATE());
END;
SELECT Name, Birthdate, dbo.fn_CalculateAge(Birthdate) AS Age
FROM Students;
Chú ý:
dbolà viết tắt của Database Owner, làschemamặc định trong SQL Server.- Khi gọi function, bắt buộc phải có
schemađể SQL Server có thể tìm kiếm và thực thi.
Table-Valued Function (Hàm bảng)
- Trả về một bảng dữ liệu
- Có thể dùng trong
SELECT,JOIN...
Ví dụ: Lấy danh sách sinh viên theo năm sinh:
CREATE FUNCTION dbo.fn_GetStudentsByYear (@Year INT)
RETURNS TABLE
AS
RETURN
(
SELECT * FROM Students WHERE YEAR(Birthdate) = @Year
);
SELECT * FROM dbo.fn_GetStudentsByYear(2000);
So sánh UDF và Stored Procedure
| Tiêu chí | User-Defined Function | Stored Procedure |
|---|---|---|
| Trả về | 1 giá trị hoặc bảng | Không bắt buộc có giá trị trả về |
| Dùng trong SQL | Có thể gọi trong SELECT, JOIN, WHERE |
Chỉ có thể gọi bằng EXEC hoặc CALL |
| Chứa câu lệnh | Không thể chứa INSERT, UPDATE, DELETE,
TRY...CATCH |
Có thể thực hiện thao tác với dữ liệu |
Trigger
Trigger là một thủ tục tự động được thực thi khi có một hành động (INSERT, UPDATE, DELETE) xảy ra trên một bảng.
Các loại Trigger
🔹 AFTER Trigger (hoặc FOR Trigger): Kích hoạt sau
khi một thao tác xảy ra.
🔹 INSTEAD OF Trigger: Kích hoạt
thay thế một thao tác (dùng để ngăn chặn hoặc thay đổi hành vi của
INSERT/UPDATE/DELETE).
Ghi log khi thêm sinh viên
Tạo bảng log:
CREATE TABLE StudentLogs (
LogID INT IDENTITY PRIMARY KEY,
StudentID INT,
ActionType NVARCHAR(50),
OldName NVARCHAR(50),
NewName NVARCHAR(50),
OldBirthdate DATE,
NewBirthdate DATE,
LogDate DATETIME DEFAULT GETDATE()
);
Tạo AFTER INSERT Trigger:
CREATE TRIGGER trg_LogStudentInsert
ON Students
AFTER INSERT
AS
BEGIN
INSERT INTO StudentLogs (StudentID, ActionType, NewName, NewBirthdate)
SELECT
i.StudentID,
'INSERT',
i.Name,
i.Birthdate
FROM inserted i;
END;
INSERT INTO Students (Name, BirthDate) VALUES ('Nguyen Van A', '2000-05-15');
SELECT * FROM StudentLogs; -- Kiểm tra log
Ghi log khi cập nhật thông tin sinh viên
Khi thông tin sinh viên thay đổi, ta sẽ lưu thông tin cũ và mới vào bảng
StudentLogs.
Tạo AFTER UPDATE Trigger:
CREATE TRIGGER trg_LogStudentUpdate
ON Students
AFTER UPDATE
AS
BEGIN
INSERT INTO StudentLogs (StudentID, ActionType, OldName, NewName, OldBirthdate, NewBirthdate)
SELECT
i.StudentID,
'UPDATE',
d.Name, i.Name,
d.Birthdate, i.Birthdate
FROM deleted d
INNER JOIN inserted i ON d.StudentID = i.StudentID;
END;
UPDATE Students
SET Name = N'Nguyễn Văn B', Birthdate = '2007-10-19'
WHERE StudentID = 1;
SELECT * FROM StudentLogs; -- Kiểm tra log cập nhật
Ghi log khi xóa sinh viên
Tạo AFTER DELETE Trigger:
CREATE TRIGGER trg_LogStudentDelete
ON Students
AFTER DELETE
AS
BEGIN
INSERT INTO StudentLogs (StudentID, ActionType, OldName, OldBirthdate)
SELECT
d.StudentID,
'DELETE',
d.Name,
d.Birthdate
FROM deleted d;
END;
DELETE FROM Students WHERE StudentID = 5;
SELECT * FROM StudentLogs; -- Kiểm tra log xóa
Ngăn chặn việc thêm sinh viên nếu tuổi nhỏ hơn 18
Tạo AFTER INSERT Trigger (kết hợp ROLLBACK):
CREATE TRIGGER trg_CheckStudentAge
ON Students
AFTER INSERT
AS
BEGIN
-- Nếu có sinh viên dưới 18 tuổi, hủy thao tác INSERT
IF EXISTS (SELECT 1 FROM inserted WHERE DATEDIFF(YEAR, BirthDate, GETDATE()) < 18)
BEGIN
PRINT N'Lỗi: Sinh viên phải từ 18 tuổi trở lên!';
ROLLBACK TRANSACTION;
END
END;
Hoặc tạo INSTEAD OF INSERT Trigger:
CREATE TRIGGER trg_CheckStudentAge
ON Students
INSTEAD OF INSERT
AS
BEGIN
-- Nếu có sinh viên dưới 18 tuổi, từ chối INSERT
IF EXISTS (SELECT 1 FROM inserted WHERE DATEDIFF(YEAR, Birthdate, GETDATE()) < 18)
BEGIN
PRINT N'Lỗi: Sinh viên phải từ 18 tuổi trở lên!';
RETURN; -- Dừng trigger, không chèn dữ liệu
END
-- Nếu tất cả hợp lệ, thực hiện INSERT
INSERT INTO Students (Name, Birthdate)
SELECT Name, Birthdate FROM inserted;
END;
Chèn sinh viên hợp lệ (trên 18 tuổi):
INSERT INTO Students (Name, BirthDate)
VALUES (N'Nguyễn Văn An', '2000-05-15');
Chèn sinh viên không hợp lệ (dưới 18 tuổi):
INSERT INTO Students (Name, BirthDate)
VALUES (N'Hoàng Thị Bình', '2010-07-20');
Transaction
Transaction đảm bảo tính toàn vẹn dữ liệu bằng cách sử dụng COMMIT để lưu thay
đổi và ROLLBACK để hoàn tác khi có lỗi xảy ra.
Transaction với TRY...CATCH để xử lý lỗi
Thêm sinh viên vào bảng Students và đăng ký khóa học, nếu có lỗi (ví dụ: khóa
học không tồn tại) thì rollback để đảm bảo dữ liệu không bị sai lệch.
BEGIN TRANSACTION;
BEGIN TRY
INSERT INTO Students (Name, Birthdate)
VALUES ('Nguyen Van A', '2000-05-10');
-- Giả sử CourseID 999 không tồn tại
INSERT INTO Enrollments (StudentID, CourseID)
VALUES (SCOPE_IDENTITY(), 999);
COMMIT TRANSACTION; -- ✅ Nếu không có lỗi, commit thay đổi
PRINT N'Thêm sinh viên và đăng ký khóa học thành công!';
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION; -- ❌ Nếu có lỗi, hoàn tác
PRINT N'Lỗi! Không thể thêm sinh viên hoặc khóa học không hợp lệ.';
END CATCH
Transaction trong Stored Procedure
Viết một thủ tục lưu trữ (Stored Procedure) để thêm sinh viên vào bảng
Students và đăng ký vào một khóa học với kiểm soát transaction:
CREATE PROCEDURE AddStudentWithCourse
@Name NVARCHAR(50),
@Birthdate DATE,
@CourseID INT
AS
BEGIN
BEGIN TRANSACTION;
BEGIN TRY
INSERT INTO Students (Name, Birthdate) VALUES (@Name, @Birthdate);
DECLARE @StudentID INT = SCOPE_IDENTITY();
INSERT INTO Enrollments (StudentID, CourseID) VALUES (@StudentID, @CourseID);
COMMIT TRANSACTION; -- ✅ Ghi nhận dữ liệu
PRINT N'Thêm sinh viên và đăng ký khóa học thành công!';
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION; -- ❌ Hoàn tác nếu có lỗi
PRINT N'Lỗi! Không thể thêm sinh viên hoặc khóa học không hợp lệ.';
END CATCH
END;
📌 Gọi thủ tục để thêm sinh viên mới và đăng ký khóa học:
EXEC AddStudentWithCourse 'Le Van C', '2002-03-20', 999;
Kiểm tra các transaction đang mở
DBCC OPENTRAN;
Dynamic SQL
Dynamic SQL là câu lệnh SQL được tạo ra và thực thi động
dùng EXEC trong thời gian chạy thay vì viết sẵn cố định.
Ví dụ tìm sinh viên theo nhiều điều kiện linh hoạt:
✔
Tên (nếu có)
✔ Năm sinh (nếu có)
(Có thể
chỉ cần tìm theo 1 tiêu chí, không bắt buộc cả hai)
EXEC sp_executesql (Tránh SQL Injection)
DECLARE @SQL NVARCHAR(MAX);
DECLARE @Name NVARCHAR(50) = N'Hoàng Hiệp';
DECLARE @Year INT = NULL;
SET @SQL = 'SELECT * FROM Students WHERE 1=1';
IF @Name IS NOT NULL
SET @SQL = @SQL + ' AND Name = @Name';
IF @Year IS NOT NULL
SET @SQL = @SQL + ' AND YEAR(Birthdate) = @Year';
EXEC sp_executesql @SQL, N'@Name NVARCHAR(50), @Year INT', @Name, @Year;
Cấu trúc của sp_executesql
EXEC sp_executesql @SQL, @parameter_definition, @param1, @param2, ...
@SQL: Chuỗi SQL động cần thực thi.@parameter_definition: Khai báo danh sách các tham số sẽ được truyền vào.- Các tham số (
@param1, @param2, ...): Giá trị thực tế của tham số được truyền vào chuỗi SQL.
EXEC
DECLARE @SQL NVARCHAR(MAX);
DECLARE @Name NVARCHAR(50) = N'Hoàng Hiệp';
DECLARE @Year INT = NULL;
SET @SQL = 'SELECT * FROM Students WHERE 1=1';
IF @Name IS NOT NULL
SET @SQL = @SQL + ' AND Name = N''' + @Name + '''';
IF @Year IS NOT NULL
SET @SQL = @SQL + ' AND YEAR(Birthdate) = ' + CAST(@Year AS NVARCHAR);
EXEC(@SQL);
SQL Injection nguy hiểm!
DECLARE @Name NVARCHAR(50) = N'Hoàng Hiệp''; EXEC(''DROP TABLE Enrollments''); --';
Khi dùng EXEC sp_executesql, giá trị @Name được truyền vào như một tham số,
không thể bị lợi dụng để chèn mã độc:
DECLARE @SQL NVARCHAR(MAX);
DECLARE @Name NVARCHAR(50) = N'Hoàng Hiệp''; EXEC(''DROP TABLE Enrollments''); --';
DECLARE @Birthdate DATE = '2000-05-10';
SET @SQL = 'INSERT INTO Students (Name, Birthdate) VALUES (@Name, @Birthdate)';
EXEC sp_executesql @SQL,
N'@Name NVARCHAR(50), @Birthdate DATE',
@Name, @Birthdate;
Normalization (Chuẩn hóa dữ liệu)
Chuẩn hóa dữ liệu (Normalization) là quá trình tổ chức dữ liệu trong cơ sở dữ liệu nhằm giảm thiểu dư thừa và cải thiện tính toàn vẹn dữ liệu. Mục tiêu chính của chuẩn hóa là:
- Loại bỏ dữ liệu trùng lặp để tiết kiệm không gian lưu trữ.
- Giảm thiểu sự bất thường khi thêm, sửa, xóa dữ liệu.
- Tăng tính nhất quán và đảm bảo tính toàn vẹn của dữ liệu.
Các mức chuẩn hóa (Normal Forms - NF)
Dữ liệu trong cơ sở dữ liệu có thể được chuẩn hóa theo nhiều mức, từ 1NF đến 5NF (thông thường chỉ đến 3NF là đủ).
-
First Normal Form (1NF) - Dạng chuẩn 1:
- Mỗi cột chỉ chứa một giá trị duy nhất (không có danh sách hoặc mảng trong một cột).
- Các hàng trong bảng là duy nhất (không có bản ghi trùng lặp).
-
Second Normal Form (2NF) - Dạng chuẩn 2:
- Thỏa mãn 1NF.
- Mỗi cột không khóa phải phụ thuộc hoàn toàn vào khóa chính (loại bỏ phụ thuộc một phần).
-
Third Normal Form (3NF) - Dạng chuẩn 3:
- Thỏa mãn 2NF.
- Không có phụ thuộc bắc cầu (một cột không khóa không được phụ thuộc vào một cột không khóa khác).
Ví dụ về chuẩn hóa
0. Dữ liệu chưa chuẩn hóa (Unnormalized Form - UNF)
🛑 Vấn đề:
Bảng sau chứa dữ liệu chưa chuẩn hóa:
| StudentID | Name | Course | Instructor |
|---|---|---|---|
| 1 | Thắng | Lập trình, Tiếng Nhật | Thầy A, Thầy B |
| 2 | Hà | Tiếng Anh | Cô C |
🔍 Lỗi gì đang xảy ra?
- Một ô chứa nhiều giá trị (Ví dụ: "Lập trình, Tiếng Nhật" trong cùng một cột).
- Khó tìm kiếm: Nếu cần tìm sinh viên học "Lập trình", rất khó lọc.
✅ Giải pháp: Chia từng giá trị thành một dòng riêng biệt.
1. Dữ liệu sau khi chuẩn hóa đến 1NF (First Normal Form)
| StudentID | Name | Course | Instructor |
|---|---|---|---|
| 1 | Thắng | Lập trình | Thầy A |
| 1 | Thắng | Tiếng Nhật | Thầy B |
| 2 | Hà | Tiếng Anh | Cô C |
🔍 Đã sửa lỗi gì?
- Mỗi ô chỉ chứa một giá trị duy nhất.
- Dễ dàng tìm kiếm và lọc thông tin.
⛔ Vấn đề mới:
- Lặp lại thông tin: "Thắng" xuất hiện hai lần.
- Nếu đổi tên "Thắng", phải sửa ở tất cả các dòng.
✅ Giải pháp: Tách sinh viên và khóa học thành hai bảng.
2. Dữ liệu sau khi chuẩn hóa đến 2NF (Second Normal Form)
Tách thành hai bảng riêng biệt:
Bảng Students (Thông tin sinh viên)
| StudentID | Name |
|---|---|
| 1 | Thắng |
| 2 | Hà |
Bảng Enrollments (Sinh viên học khóa nào)
| StudentID | Course | Instructor |
|---|---|---|
| 1 | Lập trình | Thầy A |
| 1 | Tiếng Nhật | Thầy B |
| 2 | Tiếng Anh | Cô C |
🔍 Đã sửa lỗi gì?
- Không còn lặp lại thông tin sinh viên.
⛔ Vấn đề mới:
- Giảng viên vẫn bị phụ thuộc vào khóa học.
✅ Giải pháp: Tách giảng viên thành bảng riêng.
3. Dữ liệu sau khi chuẩn hóa đến 3NF (Third Normal Form)
Tách thành bốn bảng:
Bảng Students (Danh sách sinh viên)
| StudentID | Name |
|---|---|
| 1 | Thắng |
| 2 | Hà |
Bảng Courses (Danh sách khóa học)
| CourseID | Course |
|---|---|
| 101 | Lập trình |
| 102 | Tiếng Nhật |
| 103 | Tiếng Anh |
Bảng Instructors (Danh sách giảng viên)
| InstructorID | Name |
|---|---|
| 201 | Thầy A |
| 202 | Thầy B |
| 203 | Cô C |
Bảng Enrollments (Sinh viên học khóa nào, ai dạy)
| StudentID | CourseID | InstructorID |
|---|---|---|
| 1 | 101 | 201 |
| 1 | 102 | 202 |
| 2 | 103 | 203 |
🔍 Đã sửa lỗi gì?
- Không còn phụ thuộc bắc cầu.
Khi nào không nên chuẩn hóa quá mức?
Khi cần tối ưu hiệu suất truy vấn, chuẩn hóa quá mức có thể khiến truy vấn trở nên phức tạp và tốn tài nguyên do phải JOIN nhiều bảng.
Phân quyền
Quản lý user và phân quyền là một phần quan trọng để bảo mật dữ liệu và kiểm soát truy cập. Có hai cách tiếp cận chính:
- Phân quyền bằng nhiều tài khoản SQL Server (mỗi user có tài khoản SQL riêng)
- Phân quyền bằng tài khoản SQL chung + kiểm soát quyền trong ứng dụng
1. Phân quyền bằng nhiều tài khoản SQL Server
🔹 User trong SQL Server
- User là một thực thể trong database, liên kết với một Login trên SQL Server.
- Mỗi user có thể có các quyền khác nhau trên các bảng, stored procedure, schema,…
📌 Ví dụ: Tạo một tài khoản SQL Server cho bác sĩ trong hệ thống bệnh viện.
CREATE LOGIN DoctorStrange WITH PASSWORD = 'SecurePass123'; -- tài khoản đăng nhập (Mức SQL Server)
CREATE USER DoctorStrange FOR LOGIN DoctorStrange; -- User trong database (Mức Database)
🔹 Quyền (Permissions)
- Quyền (Permissions) xác định user có thể làm gì trên database.
- Có thể cấp quyền ở mức database, schema, bảng, hoặc cột.
📌 Ví dụ: Bác sĩ chỉ được xem hồ sơ bệnh nhân của mình.
GRANT SELECT ON Patients TO DoctorStrange WHERE AssignedDoctor = CURRENT_USER;
- Một số quyền phổ biến:
SELECT,INSERT,UPDATE,DELETE– quyền trên dữ liệu.EXECUTE– quyền chạy stored procedure.ALTER,CREATE,DROP– quyền thay đổi cấu trúc database.
🔹 Role trong SQL Server
- Role là nhóm quyền, giúp quản lý nhiều user dễ dàng hơn.
- Có 2 loại role:
- Fixed Database Roles:
db_owner,db_datareader,db_datawriter... - Custom Roles: Có thể tạo role riêng và gán quyền.
- Fixed Database Roles:
| Role | Quyền hạn |
|---|---|
db_owner |
Toàn quyền trên database (tạo, sửa, xóa bảng, thêm user, sao lưu, ...) |
db_datareader |
Chỉ có quyền đọc tất cả dữ liệu trong database |
db_datawriter |
Có quyền ghi (INSERT, UPDATE, DELETE) vào tất cả bảng trong database |
db_ddladmin |
Có quyền thay đổi cấu trúc database (tạo/sửa/xóa bảng, stored procedure, view, ...) |
db_securityadmin |
Quản lý quyền truy cập trong database |
db_accessadmin |
Quản lý quyền kết nối database |
db_backupoperator |
Thực hiện sao lưu database |
db_denydatareader |
Ngăn user đọc dữ liệu trong database |
db_denydatawriter |
Ngăn user ghi dữ liệu vào database |
📌 Gán quyền db_datareader cho user
ALTER ROLE db_datareader ADD MEMBER DoctorStrange;
📌 Xóa quyền db_datareader
ALTER ROLE db_datareader DROP MEMBER DoctorStrange;
📌 Kiểm tra quyền của user trong database
SELECT DP.name AS DatabaseRole, MP.name AS UserName
FROM sys.database_role_members RM
JOIN sys.database_principals DP ON RM.role_principal_id = DP.principal_id
JOIN sys.database_principals MP ON RM.member_principal_id = MP.principal_id
WHERE MP.name = 'DoctorStrange';
📌 Tạo role Doctors và gán quyền xem hồ sơ bệnh nhân
CREATE ROLE Doctors;
GRANT SELECT ON Patients TO Doctors;
ALTER ROLE Doctors ADD MEMBER DoctorStrange;
🔹 Schema trong SQL Server
- Schema là tập hợp các đối tượng database (bảng, view, procedure…).
- Giúp tổ chức dữ liệu tốt hơn và dễ quản lý phân quyền.
📌 Ví dụ: Tạo schema riêng cho khoa Nội và gán quyền.
CREATE SCHEMA InternalMedicine;
-- Tạo bảng
CREATE TABLE InternalMedicine.Patients (
PatientID INT PRIMARY KEY,
FullName NVARCHAR(100),
Diagnosis NVARCHAR(255)
);
SELECT * FROM InternalMedicine.Patients;
-- Cho phép các thành viên trong role Doctors có thể xem (SELECT) và sửa (UPDATE) dữ liệu của tất cả các bảng trong schema InternalMedicine
GRANT SELECT, UPDATE ON SCHEMA::InternalMedicine TO Doctors;
🔥 Ưu điểm của phương pháp này:
- Bảo mật cao, mỗi user chỉ truy cập được dữ liệu của họ.
- Quản lý chi tiết từng user.
⚠️ Nhược điểm:
- Cần tạo nhiều user, khó quản lý khi có hàng nghìn user.
- Nếu user quên mật khẩu, phải reset trên SQL Server.
2. Phân quyền bằng tài khoản SQL chung + kiểm soát quyền trong ứng dụng
🔹 Cách hoạt động
- Ứng dụng sử dụng một tài khoản SQL duy nhất để kết nối với database.
- Phân quyền dựa trên cơ sở dữ liệu ứng dụng thay vì SQL Server.
- Mọi truy vấn đều kiểm tra quyền của user trước khi thực hiện.
📌 Ví dụ: Hệ thống thương mại điện tử
- Chỉ có một tài khoản
AppUserđể kết nối database. - Người dùng đăng nhập vào ứng dụng → Ứng dụng kiểm tra quyền của họ.
- Dữ liệu truy vấn dựa vào ID của người dùng.
🔹 Cấu trúc phân quyền trong ứng dụng
- Tạo tài khoản SQL chung
CREATE LOGIN AppUser WITH PASSWORD = 'StrongPass!';
CREATE USER AppUser FOR LOGIN AppUser;
GRANT SELECT, UPDATE ON Orders TO AppUser;
- Bảng kiểm soát quyền
CREATE TABLE UserRoles (
UserID INT PRIMARY KEY,
Role VARCHAR(50) -- 'Customer', 'Seller', 'Admin'
);
INSERT INTO UserRoles VALUES (1, 'Customer'), (2, 'Seller'), (3, 'Admin');
- Truy vấn có kiểm tra quyền
SELECT * FROM Orders WHERE SellerID = @CurrentUserID;
🔥 Ưu điểm của phương pháp này:
- Chỉ cần một tài khoản SQL duy nhất, dễ quản lý.
- Phù hợp với hệ thống có nhiều user.
⚠️ Nhược điểm:
- Cần viết logic kiểm soát quyền trong ứng dụng.
- Nếu không kiểm tra kỹ, dễ bị lỗ hổng bảo mật.
🔥 Kết Luận
| Phương pháp | Ưu điểm | Nhược điểm |
|---|---|---|
| Dùng nhiều tài khoản SQL Server | Bảo mật cao, kiểm soát chi tiết | Khó quản lý khi có nhiều user |
| Dùng một tài khoản SQL chung | Dễ quản lý, linh hoạt, tốt cho web/app | Cần code kiểm soát quyền trong ứng dụng |
👉 Hầu hết các hệ thống hiện đại sử dụng tài khoản SQL chung + kiểm soát quyền trong ứng dụng.
ERD dạng Chen’s Notation
Chen’s Notation là một phương pháp biểu diễn Mô hình Thực thể - Quan hệ (ERD - Entity Relationship Diagram) do Peter Chen đề xuất vào năm 1976.

Biểu diễn của Chen’s Notation
-
Thực thể (Entity):
- Thực thể mạnh (Strong Entity): Biểu diễn bằng hình chữ nhật đơn.
- Thực thể yếu (Weak Entity): Biểu diễn bằng hình chữ nhật đôi.
-
Thuộc tính (Attribute):
- Thuộc tính biểu diễn bằng hình elip (hình bầu dục).
- Thuộc tính khóa chính: Gạch chân.
- Thuộc tính đa trị: Hình elip đôi.
- Thuộc tính đa trị (Multivalued Attribute): Một thực thể có thể có nhiều giá trị cho một thuộc tính. Ví dụ: Một sinh viên có thể có nhiều số điện thoại (PhoneNumbers).
- Thuộc tính dẫn xuất: Hình elip nét đứt.
- Thuộc tính dẫn xuất (Derived Attribute): Được tính toán từ các thuộc tính khác. Ví dụ: Age có thể được tính từ Birthdate.
-
Mối quan hệ (Relationship):
- Biểu diễn bằng hình thoi (diamond).
- Quan hệ yếu (Weak Relationship): Hình thoi đôi.
-
Tính đa hình (Cardinality):
- Ghi trực tiếp trên đường nối (ví dụ: 1:N, M:N, (0,N), (1,1)).
Thường các giáo trình sẽ dùng loại notation này để dạy về ERD.
Loại này ít được dùng
trong thực tế, vì vẽ khá tốn diện tích.
Ngày nay, Crow’s Foot Notation
thường được sử dụng nhiều hơn vì gọn gàng.
Các loại thực thể chính trong ERD
Thực thể mạnh (Strong Entity)
- Là thực thể có khóa chính (Primary Key) riêng, có thể tồn tại độc lập.
- Không phụ thuộc vào bất kỳ thực thể nào khác.
📌 Ví dụ:
Students(Sinh viên) với khóa chínhStudentID.
💾 SQL ví dụ:
CREATE TABLE Students (
StudentID INT PRIMARY KEY,
Name NVARCHAR(100),
Email NVARCHAR(100) UNIQUE
);
Thực thể yếu (Weak Entity)
- Không có khóa chính riêng, phụ thuộc vào một thực thể mạnh.
- Sử dụng khóa chính của thực thể mạnh làm một phần của khóa chính.
📌 Ví dụ:
Branches(Chi nhánh ngân hàng) phụ thuộc vàoBanks(Ngân hàng).
💾 SQL ví dụ:
CREATE TABLE Banks (
BankID INT PRIMARY KEY,
Name VARCHAR(100) NOT NULL
);
CREATE TABLE Branches (
BankID INT,
BranchNo INT,
Address VARCHAR(255),
PRIMARY KEY (BankID, BranchNo),
FOREIGN KEY (BankID) REFERENCES Banks(BankID)
);
👉 Giải thích:
BranchNolà số thứ tự của chi nhánh, giúp phân biệt các chi nhánh trong cùng một ngân hàng, nhưng có thể trùng số thứ tự với các ngân hàng khác- Bảng không có khóa chính riêng mà dùng
BankID+BranchNolàm khóa chính kép BankIDlà khóa ngoại, tham chiếu đến Banks(BankID)
Thực thể liên kết (Associative Entity)
- Dùng để biểu diễn mối quan hệ N-N giữa hai thực thể.
- Thực thể này có khóa chính tạo từ hai khóa chính của các thực thể khác.
📌 Ví dụ:
Enrollments(Ghi danh) dùng để liên kếtStudents(Học sinh) vàCourses(Khóa học).
💾 SQL ví dụ:
CREATE TABLE Enrollments (
StudentID INT,
CourseID INT,
EnrollDate DATE,
PRIMARY KEY (StudentID, CourseID),
FOREIGN KEY (StudentID) REFERENCES Students(StudentID),
FOREIGN KEY (CourseID) REFERENCES Courses(CourseID)
);
Relational Algebra (Đại số quan hệ)
Đại số quan hệ là một tập hợp các phép toán dùng để truy vấn dữ liệu trong cơ sở dữ liệu quan hệ. Các phép toán này giúp truy xuất, lọc và kết hợp dữ liệu từ các bảng quan hệ một cách có hệ thống và logic.
| Phép Toán | Ký Hiệu | Cách Đọc | Mô Tả | Từ Khóa SQL Server | Phân Loại |
|---|---|---|---|---|---|
| Chiếu (Projection) | π | "Pi" | Chọn các cột cụ thể từ bảng | SELECT column1, column2 |
Đơn nguyên (Unary) |
| Chọn (Selection) | σ | "Sigma" | Chọn các hàng thỏa mãn điều kiện | WHERE |
Đơn nguyên (Unary) |
| Kết (Join) | ⨝ | "Join" | Kết hợp hai bảng dựa trên điều kiện | INNER JOIN, LEFT JOIN, RIGHT JOIN,
FULL JOIN |
Nhị phân (Binary) |
| Tích Descartes (Cartesian Product) | × | "Cross Join" hoặc "Cartesian Product" | Ghép từng dòng của bảng này với từng dòng của bảng khác | CROSS JOIN |
Nhị phân (Binary) |
| Hợp (Union) | ∪ | "Union" | Gộp hai quan hệ (loại bỏ trùng lặp) | UNION |
Nhị phân (Binary) |
| Hiệu (Difference) | ∖ hoặc − | "Minus" | Lấy phần không có trong quan hệ thứ hai | EXCEPT |
Nhị phân (Binary) |
| Giao (Intersection) | ∩ | "Intersection" | Lấy phần chung của hai quan hệ | INTERSECT |
Nhị phân (Binary) |
Giải thích về phân loại:
- Đơn nguyên (Unary): Chỉ tác động lên một quan hệ duy nhất (bảng dữ liệu).
- Nhị phân (Binary): Yêu cầu hai quan hệ (bảng) để thực hiện phép toán.
Query Compiler (Bộ biên dịch truy vấn)
Query Compiler (Bộ biên dịch truy vấn) là một thành phần quan trọng trong Hệ Quản Trị Cơ Sở Dữ Liệu (DBMS), chịu trách nhiệm dịch truy vấn ngôn ngữ bậc cao (như SQL) thành một kế hoạch thực thi tối ưu mà hệ thống có thể hiểu và thực hiện trên dữ liệu.
Query Compiler thường bao gồm các giai đoạn sau
| Giai Đoạn | Mô Tả |
|---|---|
| Phân tích cú pháp (Parsing) | Kiểm tra cú pháp của truy vấn SQL để đảm bảo không có lỗi cú pháp. |
| Phân tích ngữ nghĩa (Semantic Analysis) | Kiểm tra các bảng, cột, ràng buộc có tồn tại hay không. |
| Tối ưu hóa truy vấn (Query Optimization) | Chuyển truy vấn thành một kế hoạch thực thi tối ưu để giảm thời gian xử lý. |
| Sinh mã thực thi (Code Generation) | Chuyển kế hoạch thực thi thành mã cấp thấp mà hệ thống có thể thực hiện. |
Quá trình xử lý truy vấn SQL thường diễn ra theo các bước sau
-
Người dùng nhập truy vấn SQL
SELECT name FROM Students WHERE age > 20; -
Phân tích cú pháp (Parsing)
- Kiểm tra cú pháp của truy vấn.
- Xây dựng cây phân tích cú pháp (Parse Tree).
-
Phân tích ngữ nghĩa (Semantic Analysis)
- Kiểm tra xem bảng
Studentscó tồn tại không. - Xác minh cột
namevàagecó hợp lệ không.
- Kiểm tra xem bảng
-
Tối ưu hóa truy vấn (Query Optimization)
- Tìm cách truy xuất dữ liệu hiệu quả nhất (ví dụ: sử dụng chỉ mục thay vì quét toàn bộ bảng).
- Tối ưu hóa biểu thức điều kiện (
age > 20).
-
Sinh mã thực thi (Code Generation)
- Chuyển đổi thành mã có thể thực thi để truy xuất dữ liệu từ cơ sở dữ liệu.
- Ví dụ, truy vấn có thể được thực thi với chiến lược Index Scan thay vì Full Table Scan.
Transaction Processing (Xử lý giao dịch)
1. Transaction Processing là gì?
Transaction Processing (Xử lý giao dịch) là quá trình thực thi các giao dịch trong Hệ Quản Trị Cơ Sở Dữ Liệu (DBMS), đảm bảo rằng dữ liệu luôn nhất quán, toàn vẹn và đáng tin cậy ngay cả khi có lỗi xảy ra.
2. Giao dịch (Transaction) trong DBMS
Một Transaction là một đơn vị công việc logic bao gồm một hoặc nhiều thao tác truy vấn hoặc cập nhật dữ liệu. Giao dịch đảm bảo rằng dữ liệu được xử lý đúng cách và không bị lỗi.
Ví dụ: Một giao dịch chuyển tiền từ tài khoản A sang tài khoản B bao gồm:
- Kiểm tra số dư tài khoản A.
- Trừ tiền từ tài khoản A.
- Cộng tiền vào tài khoản B.
- Ghi nhận thay đổi vào cơ sở dữ liệu.
Nếu bất kỳ bước nào thất bại, toàn bộ giao dịch sẽ bị hủy để đảm bảo dữ liệu nhất quán.
3. Tính chất ACID của Transaction
ACID là bốn thuộc tính quan trọng đảm bảo tính toàn vẹn của giao dịch:
| Tính Chất | Mô Tả |
|---|---|
| Atomicity (Tính nguyên tử) | Giao dịch phải được thực hiện hoàn toàn hoặc không có gì cả. Nếu một phần thất bại, tất cả sẽ bị hủy. |
| Consistency (Tính nhất quán) | Giao dịch đưa cơ sở dữ liệu từ trạng thái hợp lệ này sang trạng thái hợp lệ khác. |
| Isolation (Tính độc lập) | Các giao dịch không bị ảnh hưởng lẫn nhau nếu chúng được thực hiện đồng thời. |
| Durability (Tính bền vững) | Sau khi giao dịch được xác nhận (commit), dữ liệu sẽ được lưu vĩnh viễn ngay cả khi hệ thống gặp sự cố. |
4. Trạng thái của một Transaction
Một giao dịch có thể trải qua nhiều trạng thái khác nhau:
- Active (Đang hoạt động): Giao dịch đang được thực thi.
- Partially Committed (Đã thực hiện một phần): Đã hoàn thành nhưng chưa xác nhận.
- Committed (Đã xác nhận): Giao dịch hoàn tất và dữ liệu được lưu vĩnh viễn.
- Failed (Thất bại): Một lỗi xảy ra, giao dịch không thể tiếp tục.
- Aborted (Đã hủy): Giao dịch bị hoàn tác và dữ liệu trở về trạng thái ban đầu.
5. Điều khiển giao dịch trong SQL Server
SQL Server hỗ trợ các lệnh để quản lý giao dịch như sau:
| Lệnh | Mô Tả |
|---|---|
BEGIN TRANSACTION |
Bắt đầu một giao dịch. |
COMMIT TRANSACTION |
Xác nhận giao dịch, lưu thay đổi vào cơ sở dữ liệu. |
ROLLBACK TRANSACTION |
Hủy bỏ giao dịch, đưa dữ liệu về trạng thái trước khi giao dịch bắt đầu. |
SAVE TRANSACTION |
Đánh dấu một điểm trong giao dịch để có thể quay lại nếu cần. |
Ví dụ:
BEGIN TRANSACTION;
UPDATE Accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE Accounts SET balance = balance + 100 WHERE account_id = 2;
IF @@TRANCOUNT > 0
COMMIT TRANSACTION;
ELSE
ROLLBACK TRANSACTION;
6. Ví dụ về Transaction Processing trong SQL Server
Trường hợp thành công
Giả sử có một giao dịch chuyển 500,000 VNĐ từ tài khoản A sang tài khoản B:
BEGIN TRANSACTION;
UPDATE Accounts SET balance = balance - 500000 WHERE account_id = 'A';
UPDATE Accounts SET balance = balance + 500000 WHERE account_id = 'B';
COMMIT TRANSACTION;
- Nếu cả hai cập nhật (
UPDATE) đều thành công →COMMIT TRANSACTION. - Số dư của hai tài khoản được cập nhật vĩnh viễn.
Trường hợp thất bại
Nếu lỗi xảy ra sau khi trừ tiền từ tài khoản A nhưng chưa cộng vào tài khoản B:
BEGIN TRANSACTION;
UPDATE Accounts SET balance = balance - 500000 WHERE account_id = 'A';
-- Giả sử lỗi xảy ra trước khi cập nhật tài khoản B
ROLLBACK TRANSACTION;
ROLLBACK TRANSACTIONkhôi phục số dư tài khoản A về trạng thái ban đầu.- Không có tiền bị mất hoặc sai lệch.
7. Các phương pháp xử lý giao dịch đồng thời trong SQL Server
Trong hệ thống đa người dùng, các giao dịch có thể xảy ra đồng thời, gây ra các vấn đề như lost update (cập nhật bị mất), dirty read (đọc dữ liệu bẩn), phantom read (đọc dữ liệu ảo).
Một số kỹ thuật điều khiển đồng thời:
| Phương Pháp | Mô Tả |
|---|---|
| Khóa (Locking) | Ngăn các giao dịch khác truy cập vào dữ liệu đang được xử lý. |
| Giao thức TimeStamp (Timestamp Ordering) | Sắp xếp giao dịch theo thời gian để tránh xung đột. |
| Snapshot Isolation | Cho phép mỗi giao dịch làm việc với một phiên bản dữ liệu không bị ảnh hưởng bởi giao dịch khác. |
Ví dụ về Snapshot Isolation trong SQL Server:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
BEGIN TRANSACTION;
SELECT balance FROM Accounts WHERE account_id = 'A';
-- Giả sử giao dịch khác cũng đang cập nhật cùng tài khoản
UPDATE Accounts SET balance = balance - 500000 WHERE account_id = 'A';
COMMIT TRANSACTION;
SQL Server sử dụng phiên bản dữ liệu trước đó (row versioning) để đảm bảo rằng một giao dịch có thể đọc dữ liệu như tại thời điểm giao dịch bắt đầu, ngay cả khi dữ liệu đó bị thay đổi bởi giao dịch khác.
Functional Dependency (Phụ thuộc hàm)
1. Functional Dependency là gì?
Functional Dependency (FD - Phụ thuộc hàm) là một ràng buộc giữa các thuộc tính trong một quan hệ (table) của cơ sở dữ liệu. Nó xác định rằng giá trị của một thuộc tính (hoặc một tập thuộc tính) có thể xác định duy nhất giá trị của một thuộc tính khác.
Ví dụ: Trong bảng SinhVien(MaSV, HoTen, Lop)
MaSV → HoTen, Lop(Mã sinh viên xác định duy nhất họ tên và lớp)
2. Ký hiệu của Functional Dependency
Một phụ thuộc hàm được biểu diễn dưới dạng:
X→Y
Trong đó:
X: Tập thuộc tính xác định (determinant)Y: Tập thuộc tính phụ thuộc (dependent)
3. Các loại Functional Dependency
3.1. Full Functional Dependency (Phụ thuộc hàm toàn phần)
- Một thuộc tính
Yphụ thuộc hoàn toàn vàoXnếu bỏ bất kỳ phần nào củaXthìYkhông còn phụ thuộc vàoX. - Ví dụ:
(MaSV, MaMon) → Diem(Điểm số phụ thuộc vào cả Mã SV và Mã Môn) - Nếu bỏ
MaMon, ta không thể biết đượcDiem, nên đây là phụ thuộc toàn phần.
3.2. Partial Dependency (Phụ thuộc hàm một phần)
- Xảy ra khi một phần của khóa chính cũng có thể xác định một thuộc tính nào đó.
- Ví dụ:
(MaSV, MaMon) → DiemnhưngMaSV → HoTen HoTenchỉ phụ thuộc vàoMaSV, không cầnMaMon→ Phụ thuộc một phần.
3.3. Transitive Dependency (Phụ thuộc hàm bắc cầu)
- Xảy ra khi
X → YvàY → Z, dẫn đếnX → Z. - Ví dụ:
MaNV → MaPB(Nhân viên thuộc phòng ban nào)MaPB → TenPB(Mã phòng ban xác định tên phòng ban)- Suy ra:
MaNV → TenPB→ Phụ thuộc bắc cầu
3.4. Trivial Dependency (Phụ thuộc hàm tầm thường)
- Nếu
Ylà tập con củaX, thìX → Ylà phụ thuộc tầm thường. - Ví dụ:
MaSV, HoTen → MaSV(vô nghĩa vìMaSVđã nằm trong vế trái).
3.5. Multivalued Dependency (Phụ thuộc đa trị)
- Xảy ra khi một thuộc tính có nhiều giá trị độc lập với nhau.
- Ví dụ: Một nhân viên có thể có nhiều kỹ năng và nhiều dự án.
4. Functional Dependency trong Chuẩn Hóa (Normalization)
Functional Dependency giúp xác định các dạng chuẩn (Normal Forms - NF) của cơ sở dữ liệu:
| Dạng Chuẩn | Điều Kiện |
|---|---|
| 1NF (First Normal Form) | Không có bảng lồng nhau, mỗi cột chỉ chứa một giá trị duy nhất. |
| 2NF (Second Normal Form) | 1NF + Không có phụ thuộc một phần vào khóa chính. |
| 3NF (Third Normal Form) | 2NF + Không có phụ thuộc bắc cầu. |
| BCNF (Boyce-Codd Normal Form) | 3NF + Mỗi thuộc tính không khóa phải phụ thuộc vào siêu khóa (superkey). |
| 4NF (Fourth Normal Form) | BCNF + Không có phụ thuộc đa trị. |
| 5NF (Fifth Normal Form) | 4NF + Không có phụ thuộc hàm tầm thường. |
The Closure of Attributes (Bao đóng của thuộc tính)
1. Bao đóng của thuộc tính là gì?
Bao đóng của một tập thuộc tính (X⁺) là tập tất cả các thuộc tính có thể
suy ra từ X dựa trên một tập hợp các phụ thuộc hàm.
2. Ý nghĩa của Bao đóng thuộc tính
- Xác định siêu khóa: Nếu
X⁺chứa tất cả các thuộc tính của quan hệ, thìXlà một siêu khóa. - Kiểm tra phụ thuộc hàm: Để kiểm tra xem
X → Acó đúng không, chỉ cần kiểm tra xemAcó thuộcX⁺không. - Tìm khóa chính: Dùng bao đóng để xác định tập tối thiểu các thuộc tính có thể xác định toàn bộ quan hệ.
3. Thuật toán tìm Bao đóng của thuộc tính
Đầu vào
X: Tập thuộc tính cần tìm bao đóng.FDs: Tập phụ thuộc hàm.
Bước thực hiện
- Bắt đầu: Đặt
X⁺ = X. - Lặp lại:
- Với mỗi phụ thuộc
Y → ZtrongFDs:- Nếu
Y ⊆ X⁺, thì thêmZvàoX⁺.
- Nếu
- Lặp lại cho đến khi không thể thêm được thuộc tính mới vào
X⁺.
- Với mỗi phụ thuộc
- Kết thúc:
X⁺là tập bao đóng củaX.
4. Ví dụ minh họa
Cho quan hệ R(A, B, C, D, E) với tập phụ thuộc hàm:
A → BB → CA C → DD → E
Tìm bao đóng {A}+:
| Lần | X⁺ (tập bao đóng) |
Giải thích |
|---|---|---|
| 0 | {A} |
Khởi tạo với A. |
| 1 | {A, B} |
Vì A → B. |
| 2 | {A, B, C} |
Vì B → C. |
| 3 | {A, B, C, D} |
Vì A C → D (A và C đều có trong A⁺). |
| 4 | {A, B, C, D, E} |
Vì D → E. |
Vậy:
{A}+ = {A,B,C,D,E}
Vì A⁺ chứa tất cả các thuộc tính của quan hệ R, nên
A là siêu khóa.
5. Ứng dụng của Bao đóng thuộc tính
✅ Xác định khóa chính: Nếu X⁺ = R, thì X là
khóa chính hoặc siêu khóa.
✅ Kiểm tra phụ
thuộc hàm: Kiểm tra xem X → Y có hợp lệ không bằng cách kiểm tra
Y ⊆ X⁺.
✅ Giảm thiểu tập phụ thuộc hàm: Dùng bao đóng để
tìm tập phụ thuộc tối thiểu.
Cursor
1. Cursor là gì?
Cursor là một công cụ cho phép xử lý từng dòng dữ liệu của một tập kết quả truy vấn một cách tuần tự. Cursor thường được sử dụng khi cần thực hiện thao tác trên từng hàng dữ liệu mà không thể dùng các truy vấn SQL thông thường.
2. Khi nào nên dùng Cursor?
Cursor hữu ích trong các tình huống sau:
✅ Khi cần duyệt từng hàng dữ liệu và thực hiện
các thao tác phức tạp.
✅ Khi cần xử lý dữ liệu theo từng bước mà câu lệnh SQL thông
thường không thể thực hiện hiệu quả.
✅ Khi cần thực hiện các tính toán hoặc logic phụ
thuộc vào từng hàng dữ liệu.
🚫 Lưu ý: Cursor có thể làm chậm hiệu suất vì nó xử lý từng dòng một thay vì làm việc trên tập dữ liệu toàn bộ.
3. Các loại Cursor trong SQL Server
| Loại Cursor | Mô Tả |
|---|---|
| Forward-Only Cursor | Chỉ di chuyển theo hướng về phía trước. Không thể quay lại dòng trước đó. |
| Static Cursor | Sao chép dữ liệu vào bộ nhớ tạm, giúp di chuyển qua lại nhưng không phản ánh thay đổi trong bảng. |
| Dynamic Cursor | Cho phép di chuyển tới lui và phản ánh thay đổi dữ liệu trong bảng khi Cursor đang mở. |
| Keyset-Driven Cursor | Giống Dynamic nhưng chỉ phản ánh thay đổi dữ liệu, không phản ánh dòng mới được thêm vào. |
4. Cách sử dụng Cursor trong SQL Server
4.1. Cú pháp chung
DECLARE cursor_name CURSOR FOR
SELECT column_name FROM table_name;
OPEN cursor_name;
FETCH NEXT FROM cursor_name INTO @variable;
WHILE @@FETCH_STATUS = 0
BEGIN
-- Xử lý dữ liệu
FETCH NEXT FROM cursor_name INTO @variable;
END
CLOSE cursor_name;
DEALLOCATE cursor_name;
4.2. Ví dụ cụ thể
Giả sử chúng ta có bảng Employees với các cột EmployeeID và
FullName. Chúng ta muốn duyệt qua từng nhân viên và hiển thị thông tin của họ.
DECLARE @EmpID INT, @EmpName NVARCHAR(100);
DECLARE emp_cursor CURSOR FOR
SELECT EmployeeID, FullName FROM Employees;
OPEN emp_cursor;
FETCH NEXT FROM emp_cursor INTO @EmpID, @EmpName;
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT 'Employee ID: ' + CAST(@EmpID AS NVARCHAR) + ', Name: ' + @EmpName;
FETCH NEXT FROM emp_cursor INTO @EmpID, @EmpName;
END
CLOSE emp_cursor;
DEALLOCATE emp_cursor;
📌 Giải thích:
- Cursor
emp_cursorchọn dữ liệu từ bảngEmployees. FETCH NEXTlấy dòng dữ liệu đầu tiên và lặp qua từng dòng.WHILE @@FETCH_STATUS = 0đảm bảo tiếp tục duyệt đến khi không còn dữ liệu.- Cuối cùng,
CLOSEđóng cursor vàDEALLOCATEgiải phóng bộ nhớ.
5. Nhược điểm của Cursor
🚫 Hiệu suất kém: Cursor thực hiện duyệt từng dòng thay vì xử lý theo tập dữ
liệu, làm giảm hiệu suất.
🚫 Tiêu tốn tài nguyên: Cursor giữ bộ nhớ
trong quá trình xử lý, đặc biệt nếu không đóng đúng cách.
🚫 Khó bảo
trì: Code với Cursor thường dài dòng và khó đọc hơn so với các câu lệnh SQL
thông thường.
6. Giải pháp thay thế Cursor
✅ Dùng câu lệnh SQL thay vì duyệt từng dòng:
Ví dụ: Nếu cần cập nhật lương của tất cả nhân viên thay vì dùng Cursor, ta có thể viết:
UPDATE Employees SET Salary = Salary * 1.1 WHERE Department = 'IT';
✅ Dùng Common Table Expression (CTE) hoặc WHILE LOOP nếu cần lặp:
Ví dụ
thay vì Cursor, dùng WHILE để lặp dữ liệu theo điều kiện:
DECLARE @Counter INT = 1;
WHILE @Counter <= 10
BEGIN
PRINT 'Giá trị: ' + CAST(@Counter AS NVARCHAR);
SET @Counter = @Counter + 1;
END
Tip
Chú thích trong SQL Server
Chú thích một dòng
-- Đây là một chú thích
SELECT * FROM Students;
Chú thích nhiều dòng
/*
Đây là chú thích nhiều dòng
*/
SELECT * FROM Students;
ALIASES (Bí danh)
Dùng để đặt tên thay thế cho bảng hoặc cột.
SELECT Name AS StudentName, Birthdate AS DOB FROM Students;
Câu lệnh trên sẽ đổi tên cột Name thành StudentName và
Birthdate thành DOB.
Gợi ý:
- Có thể sử dụng bí danh chứa dấu cách (ví dụ:
"Total Students"), nhưng phải đặt trong dấu nháy kép""hoặc dấu ngoặc vuông[]. - Có thể bỏ từ khóa
ASkhi đặt bí danh.
Hiển thị gợi ý từ IntelliSense sau khi tạo bảng
1. Kiểm tra xem IntelliSense có bị tắt không
- Đảm bảo rằng IntelliSense đang được bật:
- Vào Tools → Options → Text Editor → Transact-SQL → IntelliSense, sau đó kiểm tra xem tùy chọn này đã được bật chưa.
2. Làm mới bộ nhớ cache của IntelliSense
- Nhấn tổ hợp phím Ctrl + Shift + R để làm mới IntelliSense.
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
USE [master]
GO
/*******************************************************************************
Drop database if it exists
********************************************************************************/
IF EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name = N'EZSEStudentManagementSystem')
BEGIN
ALTER DATABASE EZSEStudentManagementSystem SET OFFLINE WITH ROLLBACK IMMEDIATE;
ALTER DATABASE EZSEStudentManagementSystem SET ONLINE;
DROP DATABASE EZSEStudentManagementSystem;
END
GO
CREATE DATABASE EZSEStudentManagementSystem
GO
USE EZSEStudentManagementSystem
GO
/*******************************************************************************
Drop tables if exists
*******************************************************************************/
DECLARE @sql nvarchar(MAX)
SET @sql = N''
SELECT @sql = @sql + N'ALTER TABLE ' + QUOTENAME(KCU1.TABLE_SCHEMA)
+ N'.' + QUOTENAME(KCU1.TABLE_NAME)
+ N' DROP CONSTRAINT ' -- + QUOTENAME(rc.CONSTRAINT_SCHEMA) + N'.' -- not in MS-SQL
+ QUOTENAME(rc.CONSTRAINT_NAME) + N'; ' + CHAR(13) + CHAR(10)
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS RC
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU1
ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG
AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA
AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME
EXECUTE(@sql)
GO
DECLARE @sql2 NVARCHAR(max)=''
SELECT @sql2 += ' Drop table ' + QUOTENAME(TABLE_SCHEMA) + '.'+ QUOTENAME(TABLE_NAME) + '; '
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
Exec Sp_executesql @sql2
GO
---------------------------- Create table ----------------------------------
-- Bảng Ngành học (Major)
CREATE TABLE Majors (
MajorID INT IDENTITY(1,1) PRIMARY KEY,
MajorName NVARCHAR(100) NOT NULL
);
-- Bảng Sinh viên (Student)
CREATE TABLE Students (
StudentID INT IDENTITY(1,1) PRIMARY KEY,
FirstName NVARCHAR(50) NOT NULL,
LastName NVARCHAR(50) NOT NULL,
DateOfBirth DATE NOT NULL,
Gender NVARCHAR(15) CHECK (Gender IN ('Male', 'Female', 'Other')),
Email NVARCHAR(100) UNIQUE NOT NULL,
Phone NVARCHAR(15) UNIQUE,
Address NVARCHAR(255),
EnrollmentDate DATE NOT NULL,
MajorID INT FOREIGN KEY REFERENCES Majors(MajorID)
);
-- Bảng Giảng viên (Teacher)
CREATE TABLE Teachers (
TeacherID INT IDENTITY(1,1) PRIMARY KEY,
FirstName NVARCHAR(50) NOT NULL,
LastName NVARCHAR(50) NOT NULL,
Email NVARCHAR(100) UNIQUE NOT NULL,
Phone NVARCHAR(15) UNIQUE,
Address NVARCHAR(255)
);
-- Bảng Môn học (Course)
CREATE TABLE Courses (
CourseID INT IDENTITY(1,1) PRIMARY KEY,
CourseName NVARCHAR(100) NOT NULL,
Credits INT CHECK (Credits > 0)
);
-- Bảng Môn học theo ngành (MajorCourses)
CREATE TABLE MajorCourses (
MajorID INT FOREIGN KEY REFERENCES Majors(MajorID),
CourseID INT FOREIGN KEY REFERENCES Courses(CourseID),
PRIMARY KEY (MajorID, CourseID)
);
-- Bảng Lớp học (Class)
CREATE TABLE Classes (
ClassID INT IDENTITY(1,1) PRIMARY KEY,
CourseID INT FOREIGN KEY REFERENCES Courses(CourseID),
TeacherID INT FOREIGN KEY REFERENCES Teachers(TeacherID),
Semester NVARCHAR(20) NOT NULL,
Year INT NOT NULL
);
-- Bảng Đăng ký môn học (Enrollment)
CREATE TABLE Enrollments (
StudentID INT FOREIGN KEY REFERENCES Students(StudentID),
ClassID INT FOREIGN KEY REFERENCES Classes(ClassID),
Attempt INT NOT NULL DEFAULT 1,
Grade DECIMAL(4,2) CHECK (Grade >= 0 AND Grade <= 10),
PRIMARY KEY (StudentID, ClassID, Attempt)
);
INSERT INTO Majors (MajorName) VALUES
(N'Computer Science'), -- Khoa học máy tính
(N'Software Engineering'), -- Kỹ thuật phần mềm
(N'Information Security'); -- An toàn thông tin
INSERT INTO Courses (CourseName, Credits) VALUES
(N'Data Structures & Algorithms', 3),
(N'Object-Oriented Programming', 3),
(N'Database Systems', 3),
(N'Computer Networks', 3),
(N'Web Development', 4),
(N'Agile Software Development', 3),
(N'Cyber Security', 3),
(N'Mobile Application Development', 3);
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID) VALUES
(N'Phạm', N'Gia Bảo', '2002-03-10', 'Male', '[email protected]', '0912345678', N'12 Trần Phú, Hà Nội', '2021-09-01', 1),
(N'Nguyễn', N'Thảo Linh', '2003-07-22', 'Female', '[email protected]', '0987654321', N'34 Lê Lợi, TP.HCM', '2021-09-01', 2),
(N'Trần', N'Quang Vinh', '2001-05-15', 'Male', '[email protected]', '0933123456', N'56 Nguyễn Trãi, Đà Nẵng', '2020-09-01', 3),
(N'Lê', N'Minh Nhật', '2002-09-25', 'Male', '[email protected]', '0977890123', N'78 Hai Bà Trưng, Hải Phòng', '2021-09-01', 1),
(N'Vũ', N'Hồng Ngọc', '2003-01-30', 'Female', '[email protected]', '0966543210', N'90 Quang Trung, Cần Thơ', '2022-09-01', 2),
(N'Đỗ', N'Thanh Huyền', '2004-02-14', 'Female', '[email protected]', '0913456789', N'15 Hùng Vương, Huế', '2022-09-01', 3),
(N'Hoàng', N'Đức Anh', '2003-11-05', 'Male', '[email protected]', '0934567890', N'27 Nguyễn Huệ, Quy Nhơn', '2021-09-01', 1),
(N'Ngô', N'Khánh Duy', '2002-08-19', 'Male', '[email protected]', '0945678901', N'39 Lê Đại Hành, Vinh', '2020-09-01', 2),
(N'Bùi', N'Thục Đoan', '2004-06-25', 'Female', '[email protected]', '0956789012', N'51 Phạm Ngũ Lão, Nha Trang', '2023-09-01', 3),
(N'Phan', N'Tiến Đạt', '2003-04-12', 'Male', '[email protected]', '0967890123', N'63 Nguyễn Văn Cừ, Đà Lạt', '2022-09-01', 1),
(N'Đặng', N'Mỹ Linh', '2004-09-08', 'Female', '[email protected]', '0978901234', N'75 Trần Hưng Đạo, Hà Nội', '2023-09-01', 2),
(N'Võ', N'Hoàng Phúc', '2002-12-30', 'Male', '[email protected]', '0989012345', N'87 Bạch Đằng, TP.HCM', '2020-09-01', 3),
(N'Trương', N'Ngọc Ánh', '2003-03-17', 'Female', '[email protected]', '0990123456', N'99 Nguyễn Thị Minh Khai, Đà Nẵng', '2021-09-01', 1),
(N'Lý', N'Quốc Hưng', '2004-07-21', 'Male', '[email protected]', '0901234567', N'111 Lê Lai, Hải Phòng', '2023-09-01', 2),
(N'Nguyễn', N'Văn Hùng', '2003-05-10', 'Male', '[email protected]', '0912345689', N'123 Nguyễn Huệ, Hà Nội', '2022-09-01', 1),
(N'Trần', N'Thị Mai', '2004-08-15', 'Female', '[email protected]', '0987654332', N'45 Lê Lợi, TP.HCM', '2023-09-01', 1),
(N'Lê', N'Quang Minh', '2002-11-20', 'Male', '[email protected]', '0933123467', N'67 Trần Phú, Đà Nẵng', '2021-09-01', 1),
(N'Phạm', N'Hồng Phúc', '2003-02-25', 'Male', '[email protected]', '0977890134', N'89 Hai Bà Trưng, Hải Phòng', '2022-09-01', 1),
(N'Vũ', N'Thị Lan', '2004-04-12', 'Female', '[email protected]', '0966543221', N'101 Quang Trung, Cần Thơ', '2023-09-01', 2),
(N'Đỗ', N'Minh Tuấn', '2003-09-30', 'Male', '[email protected]', '0913456790', N'23 Hùng Vương, Huế', '2022-09-01', 2),
(N'Hoàng', N'Ngọc Ánh', '2002-07-18', 'Female', '[email protected]', '0934567901', N'34 Nguyễn Huệ, Quy Nhơn', '2021-09-01', 2),
(N'Ngô', N'Văn Long', '2003-12-05', 'Male', '[email protected]', '0945678912', N'56 Lê Đại Hành, Vinh', '2022-09-01', 3),
(N'Bùi', N'Thị Hương', '2004-03-22', 'Female', '[email protected]', '0956789023', N'78 Phạm Ngũ Lão, Nha Trang', '2023-09-01', 3),
(N'Phan', N'Quốc Anh', '2002-06-15', 'Male', '[email protected]', '0967890134', N'90 Nguyễn Văn Cừ, Đà Lạt', '2021-09-01', 3);
INSERT INTO Teachers (FirstName, LastName, Email, Phone, Address) VALUES
(N'Hoàng', N'Văn Hiệp', '[email protected]', '0944123456', N'101 Lý Thường Kiệt, Hà Nội'),
(N'Lê', N'Thu Hà', '[email protected]', '0933221122', N'202 Phạm Văn Đồng, TP.HCM'),
(N'Hoàng', N'Việt Thắng', '[email protected]', '0922334455', N'303 Trường Chinh, Đà Nẵng'),
(N'Nguyễn', N'Minh Tuấn', '[email protected]', '0911445566', N'404 Nguyễn Văn Linh, Hải Phòng'),
(N'Phạm', N'Quốc Khánh', '[email protected]', '0902345678', N'505 Điện Biên Phủ, Huế'),
(N'Vũ', N'Thị Duyên', '[email protected]', '0913567890', N'606 Nguyễn Đình Chiểu, TP.HCM'),
(N'Trần', N'Đình Phong', '[email protected]', '0924678901', N'707 Trần Phú, Hà Nội'),
(N'Đỗ', N'Ngọc Mai', '[email protected]', '0935789012', N'808 Lê Hồng Phong, Đà Nẵng'),
(N'Bùi', N'Gia Bảo', '[email protected]', '0946890123', N'909 Nguyễn Trãi, Hải Phòng'),
(N'Ngô', N'Thị Lan Anh', '[email protected]', '0957901234', N'1010 Phạm Hùng, Cần Thơ');
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);
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);
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 INT IDENTITY(1,1) PRIMARY KEY,
MajorName NVARCHAR(100) NOT NULL
);
- MajorID: Mã ngành học, là khóa chính.
- 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 IDENTITY(1,1) PRIMARY KEY,
FirstName NVARCHAR(50) NOT NULL,
LastName NVARCHAR(50) NOT NULL,
DateOfBirth DATE NOT NULL,
Gender NVARCHAR(15) CHECK (Gender IN ('Male', 'Female', 'Other')),
Email NVARCHAR(100) UNIQUE NOT NULL,
Phone NVARCHAR(15) UNIQUE,
Address NVARCHAR(255),
EnrollmentDate DATE NOT NULL,
MajorID INT FOREIGN KEY REFERENCES Majors(MajorID)
);
- StudentID: Mã sinh viên, là khóa chính.
- 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 INT IDENTITY(1,1) PRIMARY KEY,
FirstName NVARCHAR(50) NOT NULL,
LastName NVARCHAR(50) NOT NULL,
Email NVARCHAR(100) UNIQUE NOT NULL,
Phone NVARCHAR(15) UNIQUE,
Address NVARCHAR(255)
);
- TeacherID: Mã giảng viên, là khóa chính.
- 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 INT IDENTITY(1,1) PRIMARY KEY,
CourseName NVARCHAR(100) NOT NULL,
Credits INT CHECK (Credits > 0)
);
- CourseID: Mã môn học, là khóa chính.
- 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 FOREIGN KEY REFERENCES Majors(MajorID),
CourseID INT FOREIGN KEY 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 INT IDENTITY(1,1) PRIMARY KEY,
CourseID INT FOREIGN KEY REFERENCES Courses(CourseID),
TeacherID INT FOREIGN KEY REFERENCES Teachers(TeacherID),
Semester NVARCHAR(20) NOT NULL,
Year INT NOT NULL
);
- ClassID: Mã lớp học, là khóa chính.
- 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 FOREIGN KEY REFERENCES Students(StudentID),
ClassID INT FOREIGN KEY REFERENCES Classes(ClassID),
Attempt INT 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 khoá 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, dbo.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 (N'Nguyễn', N'Văn A', '2002-05-10', 'Male', 'nguyenvana', '0734567890', N'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 (N'Trần', N'Thị B', '2001-08-20', 'Female', '[email protected]', '0678123456', N'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
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 = N'Hoàng' AND T.LastName = N'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 khoá 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
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 TeacherClassesCTE AS (
SELECT C.Year, T.TeacherID, CONCAT(T.FirstName, ' ', T.LastName) AS TeacherFullName,
COUNT(C.ClassID) AS NumberOfClasses
FROM Teachers T
JOIN Classes C ON T.TeacherID = C.TeacherID
GROUP BY C.Year, T.TeacherID, CONCAT(T.FirstName, ' ', T.LastName)
),
MaxClassesCTE AS (
SELECT Year, MAX(NumberOfClasses) AS MaxClasses FROM TeacherClassesCTE
GROUP BY Year
)
SELECT * FROM TeacherClassesCTE TC
WHERE TC.NumberOfClasses = (SELECT MaxClasses FROM MaxClassesCTE WHERE TC.Year = Year)
ORDER BY Year, 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 TotalStudentsCTE AS (
SELECT C.Year, M.MajorID, M.MajorName, COUNT(DISTINCT S.StudentID) AS TotalStudents
FROM Classes C
JOIN Enrollments E ON C.ClassID = E.ClassID
JOIN Students S ON E.StudentID = S.StudentID
JOIN Majors M ON S.MajorID = M.MajorID
AND M.MajorName IN ('Computer Science', 'Software Engineering')
GROUP BY C.Year, M.MajorID, M.MajorName
),
MajorsCTE AS (
SELECT MajorID, MajorName FROM Majors WHERE MajorName IN ('Computer Science', 'Software Engineering')
),
YearsCTE AS (
SELECT DISTINCT Year FROM Classes
),
MajorsYearsCTE AS (
SELECT Y.Year, M.MajorID, M.MajorName FROM MajorsCTE M
CROSS JOIN YearsCTE Y
)
SELECT M.*, ISNULL(T.TotalStudents, 0) AS TotalStudents FROM MajorsYearsCTE M
LEFT JOIN TotalStudentsCTE T ON M.Year = T.Year AND M.MajorID = T.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, dbo.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 (@CourseID INT)
RETURNS INT
AS
BEGIN
DECLARE @NumberOfStudents INT;
SELECT @NumberOfStudents = COUNT(DISTINCT E.StudentID) FROM Classes C
JOIN Enrollments E ON C.ClassID = E.ClassID
WHERE C.CourseID = @CourseID;
RETURN @NumberOfStudents;
END
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 (N'Nguyễn', N'Văn A', '2002-05-10', 'Male', 'nguyenvana', '0734567890', N'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 (N'Trần', N'Thị B', '2001-08-20', 'Female', '[email protected]', '0678123456', N'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 TRIGGER trg_InsteadOfInsertStudent
ON Students
INSTEAD OF INSERT
AS
BEGIN
DECLARE @SoftwareEngineeringID INT;
SELECT @SoftwareEngineeringID = MajorID FROM Majors WHERE MajorName = 'Software Engineering';
INSERT INTO Students
(FirstName
,LastName
,DateOfBirth
,Gender
,Email
,Phone
,Address
,EnrollmentDate
,MajorID)
SELECT FirstName
,LastName
,DateOfBirth
,Gender
,IIF(Email NOT LIKE '%@%', CONCAT(Email, '@ezse.net'), Email)
,Phone
,Address
,EnrollmentDate
,ISNULL(MajorID, @SoftwareEngineeringID)
FROM inserted
END;
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:
DELETE FROM Enrollments WHERE StudentID IN (3, 5);
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 trong
Enrollmentstrướ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:
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.
