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

  1. Mở SSMS.
  2. 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 UsernamePassword).
  3. 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 NULLUNIQUE, 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ảng Students bị xóa, tất cả các bản ghi liên quan trong Enrollments cũng bị xóa.
  • ON UPDATE CASCADE: Nếu StudentID bị cập nhật trong Students, nó sẽ tự động cập nhật trong Enrollments.
  • ON DELETE SET NULL: Nếu một khóa chính trong bảng Courses bị xóa, các khóa ngoại tham chiếu đến nó trong Enrollments sẽ được đặt thành NULL.
  • ON UPDATE NO ACTION: Nếu CourseID bị cập nhật trong Courses, 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ăng 1.
  • Có thể tùy chỉnh bước nhảy, ví dụ IDENTITY(100,10) sẽ bắt đầu từ 100 và tăng 10 mỗ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 ý

  • IDENTITY chỉ áp dụng cho cột số nguyên (INT, BIGINT, SMALLINT, TINYINT).
  • NOT NULL thường nên đặt trước UNIQUE hoặc CHECK để tránh lỗi logic.
  • DEFAULT có thể khai báo trước hoặc sau CHECK, nhưng SQL Server sẽ luôn áp dụng DEFAULT trước nếu không có giá trị nhập vào.
  • FOREIGN KEY thườ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 ý:

  • DELETE có thể kết hợp với WHERE để 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.
  • DELETE có 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 StudentID chư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
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.

  • ANY trả về TRUE nếu có ít nhất một giá trị thỏa mãn điều kiện.
  • ALL chỉ trả về TRUE nế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

JOIN

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 StudentsCourses 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ả StudentsCourses.

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ục CLUSTERED.
  • 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ục NONCLUSTERED trong 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

  1. 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à AnStudentID = 1).
    • Thiết lập Level = 1 cho sinh viên này.
  2. Bước đệ quy

    • Tìm sinh viên có ReferredBy trùng với StudentID từ bước trước.
    • Tăng cấp bậc (Level + 1) để thể hiện mối quan hệ mentor-mentee.

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ú ý:

  • dbo là viết tắt của Database Owner, là schema mặ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à đủ).

  1. 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).
  2. 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).
  3. 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 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 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

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

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:

  1. 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)
  2. 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.
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
  1. 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;
  1. 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');
  1. 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.

Chen’s Notation

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ính StudentID.

💾 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ào Banks (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:

  • BranchNosố 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 + BranchNo làm khóa chính kép
  • BankIDkhó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ết Students (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

  1. Người dùng nhập truy vấn SQL

    SELECT name FROM Students WHERE age > 20;
    
  2. 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).
  3. Phân tích ngữ nghĩa (Semantic Analysis)

    • Kiểm tra xem bảng Students có tồn tại không.
    • Xác minh cột nameage có hợp lệ không.
  4. 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).
  5. 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:

  1. Kiểm tra số dư tài khoản A.
  2. Trừ tiền từ tài khoản A.
  3. Cộng tiền vào tài khoản B.
  4. 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:

  1. Active (Đang hoạt động): Giao dịch đang được thực thi.
  2. Partially Committed (Đã thực hiện một phần): Đã hoàn thành nhưng chưa xác nhận.
  3. Committed (Đã xác nhận): Giao dịch hoàn tất và dữ liệu được lưu vĩnh viễn.
  4. Failed (Thất bại): Một lỗi xảy ra, giao dịch không thể tiếp tục.
  5. 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 TRANSACTION khô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 Y phụ thuộc hoàn toàn vào X nếu bỏ bất kỳ phần nào của X thì Y không còn phụ thuộc vào X.
  • 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 được Diem, 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) → Diem nhưng MaSV → HoTen
  • HoTen chỉ phụ thuộc vào MaSV, không cần MaMonPhụ thuộc một phần.
3.3. Transitive Dependency (Phụ thuộc hàm bắc cầu)
  • Xảy ra khi X → YY → Z, dẫn đến X → 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 → TenPBPhụ thuộc bắc cầu
3.4. Trivial Dependency (Phụ thuộc hàm tầm thường)
  • Nếu Y là tập con của X, thì X → Y là 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ăngnhiề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ì X là một siêu khóa.
  • Kiểm tra phụ thuộc hàm: Để kiểm tra xem X → A có đúng không, chỉ cần kiểm tra xem A có thuộc X⁺ 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
  1. Bắt đầu: Đặt X⁺ = X.
  2. Lặp lại:
    • Với mỗi phụ thuộc Y → Z trong FDs:
      • Nếu Y ⊆ X⁺, thì thêm Z vào X⁺.
    • Lặp lại cho đến khi không thể thêm được thuộc tính mới vào X⁺.
  3. Kết thúc: X⁺ là tập bao đóng của X.

4. Ví dụ minh họa

Cho quan hệ R(A, B, C, D, E) với tập phụ thuộc hàm:

  1. A → B
  2. B → C
  3. A C → D
  4. D → 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} A → B.
2 {A, B, C} B → C.
3 {A, B, C, D} A C → D (A và C đều có trong A⁺).
4 {A, B, C, D, E} D → E.

Vậy:

{A}+ = {A,B,C,D,E}

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ì Xkhó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 EmployeeIDFullName. 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_cursor chọn dữ liệu từ bảng Employees.
  • FETCH NEXT lấ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à DEALLOCATE giả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 StudentNameBirthdate 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 AS khi đặ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 ToolsOptionsText EditorTransact-SQLIntelliSense, 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.

Question 1


Câu hỏi 2

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

Question 2


Câu hỏi 3

Viết truy vấn để lấy thông tin các lớp học mà giảng viên có họ là "Hoàng" và tên là "Văn Hiệp" phụ trách.

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

Question 3


Câu hỏi 4

Viết truy vấn để lấy danh sách sinh viên đã đăng ký ít nhất một trong các lớp thuộc các môn học "Data Structures & Algorithms", "Database Systems", hoặc "Web Development".

Hiển thị các cột như phía dưới, không hiển thị kết quả trùng nhau.
Kết quả sắp xếp theo CourseID tăng dần, sau đó theo StudentID tăng dần.

Question 4


Câu hỏi 5

Viết truy vấn để hiển thị số lượng sinh viên đã đăng ký vào các 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.

Question 5


Câu hỏi 6

Viết truy vấn để tìm các giảng viên có số lượng lớp dạy nhiều nhất trong từng năm.

Hiển thị các cột như phía dưới.
Kết quả sắp xếp theo Year tăng dần, sau đó theo TeacherID tăng dần.

Question 6


Câu hỏi 7

Viết truy vấn để đếm số sinh viên duy nhất đã đăng ký môn học theo từng năm của ngành "Computer Science""Software Engineering".

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

Question 7


Câu hỏi 8

Tạo function GetNumberOfStudentsByCourseID để tính số lượng sinh viên duy nhất đã đăng ký một môn học cụ thể theo CourseID.

Sau khi viết xong, hãy kiểm tra lại function bằng câu lệnh SQL dưới.

SELECT CourseID, CourseName, 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ể

Question 8


Câu hỏi 9

Tạo trigger để xử lý dữ liệu khi thêm sinh viên mới vào bảng Students có tên trg_InsteadOfInsertStudent đảm bảo hai điều kiện:

  1. Nếu Email không chứa ký tự "@", tự động thêm @ezse.net vào cuối.
  2. Nếu MajorID bị NULL, tự động đặt MajorID thành ID của ngành "Software Engineering".

Sau khi viết xong, hãy kiểm tra lại trigger bằng câu SQL lệnh dưới.

-- Trường hợp thiếu @ -> Tự động thêm @ezse.net, MajorID có giá trị nên giữ nguyên
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID)
VALUES (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.

Question 9


Câu hỏi 10

Xóa sinh viên có StudentID = 3StudentID = 5 khỏi bảng Students, đồng thời xóa các hàng liên quan trong bảng Enrollments.

Đáp án đề thực hành

Câu hỏi 1

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

Question 1

Đáp án:

SELECT * FROM Teachers;

Câu hỏi 2

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

Question 2

Đáp án:

SELECT * FROM Students WHERE MajorID IN (2, 3);

Câu hỏi 3

Viết truy vấn để lấy thông tin các lớp học mà giảng viên có họ là "Hoàng" và tên là "Văn Hiệp" phụ trách.

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

Question 3

Đáp án:

SELECT C.ClassID, C.CourseID, CO.CourseName, C.Semester, C.Year, T.TeacherID, T.FirstName, T.LastName FROM Classes C
JOIN Teachers T ON C.TeacherID = T.TeacherID
JOIN Courses CO ON C.CourseID = CO.CourseID
WHERE T.FirstName = 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.

Question 4

Đáp án:

SELECT DISTINCT CO.CourseID, CO.CourseName, S.StudentID,
CONCAT(S.FirstName, ' ', S.LastName) AS StudentFullName, S.DateOfBirth
FROM Students S
JOIN Enrollments E ON S.StudentID = E.StudentID
JOIN Classes C ON E.ClassID = C.ClassID
JOIN Courses CO ON C.CourseID = CO.CourseID
WHERE CO.CourseName IN ('Data Structures & Algorithms' , 'Database Systems', 'Web Development')
ORDER BY CO.CourseID, S.StudentID

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.

Question 5

Đáp án:

SELECT C.CourseID, C.CourseName, COUNT(E.StudentID) AS NumberOfEnrollments FROM Courses C
LEFT JOIN Classes CL ON C.CourseID = CL.CourseID 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.

Question 6

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

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

Question 7

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

Question 8

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

  1. Nếu Email không chứa ký tự "@", tự động thêm @ezse.net vào cuối.
  2. Nếu MajorID bị NULL, tự động đặt MajorID thành ID của ngành "Software Engineering".

Sau khi viết xong, hãy kiểm tra lại trigger bằng câu SQL lệnh dưới.

-- Trường hợp thiếu @ -> Tự động thêm @ezse.net, MajorID có giá trị nên giữ nguyên
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID)
VALUES (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.

Question 9

Đá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 = 3StudentID = 5 khỏi bảng Students, đồng thời xóa các hàng liên quan trong bảng Enrollments.

Đáp án:

DELETE FROM Enrollments WHERE StudentID IN (3, 5);
DELETE FROM Students WHERE StudentID IN (3, 5);

Giải thích:

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

    • Bảng Enrollmentskhóa ngoại StudentID tham chiếu đến Students.
    • Nếu không có ON DELETE CASCADE, ta phải xóa các bản ghi liên quan trong Enrollments trước.
  2. Xóa dữ liệu chính trong Students

    • Sau khi xóa các bản ghi trong Enrollments, ta có thể xóa sinh viên có StudentID = 3StudentID = 5 mà không vi phạm ràng buộc.

Trường hợp có ON DELETE CASCADE trên Enrollments.StudentID
Nếu khi tạo bảng Enrollments, ta đã thiết lập:

FOREIGN KEY (StudentID) REFERENCES Students(StudentID) ON DELETE CASCADE

thì chỉ cần chạy:

DELETE FROM Students WHERE StudentID IN (3, 5);

Các bản ghi trong Enrollments sẽ tự động bị xóa.