Thi thử thực hành Cơ sở dữ liệu
Đề thi thử bám sát cấu trúc thi thực hành chính thức - Tạo CSDL và khóa (3 điểm), ràng buộc toàn vẹn và Trigger (2 điểm), truy vấn dữ liệu (5 điểm). Bài thi tổ chức nội bộ tại lớp thực hành, sinh viên không được sử dụng tài liệu khi làm bài. Đề bài và đáp án đều cần mã truy cập riêng do giảng viên cung cấp tại buổi thi thử.
ĐỀ BÀI · CẦN MÃ TRUY CẬP
Xem đề thi
Nội dung được bảo vệ
Nhập mã truy cập để xem đề thi thử.
Mã đề thi được giảng viên phát tại buổi thi thử - Khác với mã xem đáp án.
Lược đồ CSDL "Quản lý cho thuê khu nghỉ dưỡng"
Cho lược đồ CSDL gồm 6 quan hệ sau:
PHONG (MaPH, LoaiPhong, GiaPhong, TinhTrangPhong, DienTich)
Tân từ: Lưu trữ thông tin phòng - Mã phòng, loại phòng, giá phòng theo ngày, tình trạng phòng và diện tích.
| Thuộc tính | Kiểu dữ liệu | Diễn giải |
|---|---|---|
MaPH | char(5) | Mã phòng - Khóa chính |
LoaiPhong | varchar(50) | Loại phòng (Deluxe, Suite, Standard, Superior,...) |
GiaPhong | money | Giá phòng theo ngày (VNĐ) |
TinhTrangPhong | varchar(20) | Tình trạng phòng (Trống, Đã đặt, Đang ở, Đang bảo trì,...) |
DienTich | float | Diện tích phòng (m²) |
DICHVU (MaDV, TenDV, DonGia)
Tân từ: Lưu trữ thông tin dịch vụ - Mã dịch vụ, tên dịch vụ và đơn giá.
| Thuộc tính | Kiểu dữ liệu | Diễn giải |
|---|---|---|
MaDV | char(5) | Mã dịch vụ - Khóa chính |
TenDV | varchar(50) | Tên dịch vụ (Spa, Massage, Ăn uống, Khu vui chơi,...) |
DonGia | money | Đơn giá dịch vụ (VNĐ) |
KHACHHANG (MaKH, HoTen, CCCD, NgaySinh, GioiTinh, SoDT, Email, DiaChi)
Tân từ: Lưu trữ thông tin khách hàng - Mã khách hàng, họ tên, số căn cước công dân, ngày sinh, giới tính, số điện thoại, email và địa chỉ.
| Thuộc tính | Kiểu dữ liệu | Diễn giải |
|---|---|---|
MaKH | char(5) | Mã khách hàng - Khóa chính |
HoTen | varchar(50) | Họ tên khách hàng |
CCCD | varchar(12) | Số căn cước công dân |
NgaySinh | smalldatetime | Ngày sinh khách hàng |
GioiTinh | varchar(10) | Giới tính khách hàng |
SoDT | varchar(15) | Số điện thoại |
Email | varchar(50) | Email liên lạc |
DiaChi | varchar(150) | Địa chỉ khách hàng |
DATPHONG (MaDatPhong, MaKH, MaPH, NgayDat, NgayCheckIn, NgayCheckOut, TrangThai)
Tân từ: Lưu trữ thông tin đặt phòng - Mã đặt phòng, khách hàng đặt, phòng được đặt, ngày đặt, ngày nhận phòng, ngày trả phòng và trạng thái đơn.
| Thuộc tính | Kiểu dữ liệu | Diễn giải |
|---|---|---|
MaDatPhong | char(5) | Mã đặt phòng - Khóa chính |
MaKH | char(5) | Mã khách hàng đặt phòng - Khóa ngoại đến KHACHHANG |
MaPH | char(5) | Mã phòng được đặt - Khóa ngoại đến PHONG |
NgayDat | smalldatetime | Ngày đặt phòng |
NgayCheckIn | smalldatetime | Ngày nhận phòng |
NgayCheckOut | smalldatetime | Ngày trả phòng |
TrangThai | varchar(20) | Trạng thái đơn đặt phòng (Đã xác nhận, Đã hủy, Đang ở, Chờ xác nhận,...) |
THANHTOAN (MaThanhToan, MaDatPhong, SoTien, PTThanhToan, TTThanhToan, NgayThanhToan)
Tân từ: Lưu trữ thông tin thanh toán - Mã thanh toán, đơn đặt phòng được thanh toán, số tiền, phương thức, tình trạng và ngày thanh toán.
| Thuộc tính | Kiểu dữ liệu | Diễn giải |
|---|---|---|
MaThanhToan | char(5) | Mã thanh toán - Khóa chính |
MaDatPhong | char(5) | Mã đặt phòng được thanh toán - Khóa ngoại đến DATPHONG |
SoTien | money | Số tiền thanh toán (VNĐ) |
PTThanhToan | varchar(20) | Phương thức thanh toán (Tiền mặt, Chuyển khoản, Voucher,...) |
TTThanhToan | varchar(20) | Tình trạng thanh toán (Đã thanh toán, Chưa thanh toán, Đã thanh toán một phần,...) |
NgayThanhToan | smalldatetime | Ngày thanh toán |
CTDICHVU (MaDatPhong, MaDV, NgaySuDung, SoLuong, ThanhTien)
Tân từ: Lưu trữ chi tiết dịch vụ - Đơn đặt phòng nào đã sử dụng dịch vụ gì, vào ngày nào, số lượng và thành tiền bao nhiêu. Khóa chính là bộ ba (MaDatPhong, MaDV, NgaySuDung).
| Thuộc tính | Kiểu dữ liệu | Diễn giải |
|---|---|---|
MaDatPhong | char(5) | Mã đặt phòng - Khóa chính, khóa ngoại đến DATPHONG |
MaDV | char(5) | Mã dịch vụ được sử dụng - Khóa chính, khóa ngoại đến DICHVU |
NgaySuDung | smalldatetime | Ngày sử dụng dịch vụ - Khóa chính |
SoLuong | int | Số lượng dịch vụ đã sử dụng |
ThanhTien | money | Tổng số tiền dịch vụ đã sử dụng |
Sơ đồ quan hệ
KHACHHANG (1) ➜ DATPHONG (N): Một khách hàng có thể đặt nhiều đơn đặt phòng.
PHONG (1) ➜ DATPHONG (N): Một phòng có thể xuất hiện trong nhiều đơn đặt phòng khác nhau (ở các khoảng thời gian khác nhau).
DATPHONG (1) ➜ THANHTOAN (N): Một đơn đặt phòng có thể có nhiều lượt thanh toán (ví dụ thanh toán từng phần).
DATPHONG (1) ➜ CTDICHVU (N): Một đơn đặt phòng có thể sử dụng nhiều dịch vụ, ở nhiều ngày khác nhau.
DICHVU (1) ➜ CTDICHVU (N): Một dịch vụ có thể được nhiều đơn đặt phòng sử dụng.
Xem trước dữ liệu mẫu (PHONG, DICHVU)
PHONG (12 dòng)
| MaPH | LoaiPhong | GiaPhong | TinhTrangPhong | DienTich |
|---|---|---|---|---|
| PH001 | Deluxe | 5.200.000 | Da dat | 55 |
| PH002 | Deluxe | 5.000.000 | Trong | 45 |
| PH003 | Suite | 7.000.000 | Da dat | 35 |
| PH004 | Suite | 7.000.000 | Trong | 35 |
| PH005 | Standard | 1.500.000 | Dang o | 25 |
| PH006 | Standard | 1.200.000 | Dang o | 22 |
| PH007 | Deluxe | 5.200.000 | Trong | 58 |
| PH008 | Deluxe | 8.000.000 | Dang o | 59 |
| PH009 | Suite | 6.500.000 | Trong | 40 |
| PH010 | Standard | 1.800.000 | Dang bao tri | 26 |
| PH011 | Superior | 2.500.000 | Dang o | 30 |
| PH012 | Superior | 3.000.000 | Da dat | 32 |
DICHVU (10 dòng)
| MaDV | TenDV | DonGia |
|---|---|---|
| DV001 | Spa | 800.000 |
| DV002 | Massage | 500.000 |
| DV003 | An uong | 300.000 |
| DV004 | Khu vui choi | 400.000 |
| DV005 | Dua don san bay | 700.000 |
| DV006 | Giat ui | 200.000 |
| DV007 | Thue xe | 900.000 |
| DV008 | Yoga buoi sang | 600.000 |
| DV009 | Thue huong dan vien | 1.200.000 |
| DV010 | Tham quan bien bang cano | 1.500.000 |
KHACHHANG (15 dòng), DATPHONG (20 dòng), THANHTOAN (20 dòng) và CTDICHVU (37 dòng) chỉ có trong file du-lieu-mau-thi-thu.sql ở trên - Quá dài để xem trước tại đây, hãy tải file để chạy trực tiếp trong SSMS.
Yêu cầu đề bài
Sinh viên thực hiện các yêu cầu sau bằng ngôn ngữ SQL:
Phần 1 - Tạo CSDL (3 điểm)
- Tạo cơ sở dữ liệu tên "QLRESORT" bao gồm các quan hệ như trong bảng thuộc tính trên. Khai báo khóa chính, khóa ngoại. (3 điểm)
Phần 2 - Ràng buộc toàn vẹn (2 điểm)
- Đối với các phòng thuộc loại phòng "Deluxe", giá thuê theo ngày phải lớn hơn 500.000 đồng. (0.5 điểm)
- Số điện thoại của khách hàng phải bắt đầu bằng chữ số "0". (0.5 điểm)
- Viết TRIGGER để kiểm tra khi thêm hoặc cập nhật dữ liệu vào bảng CTDICHVU. Nếu ngày sử dụng dịch vụ (NgaySuDung) không nằm trong khoảng thời gian lưu trú từ NgayCheckIn đến NgayCheckOut của đơn đặt phòng tương ứng, thì trigger phải báo lỗi và hủy giao dịch. (1 điểm)
Phần 3 - Truy vấn dữ liệu (5 điểm)
- Liệt kê các dịch vụ (MaDV, TenDV) đã được sử dụng trong các đơn đặt phòng trong sáu tháng đầu năm 2025 có trạng thái "Đã xác nhận" và đã đặt phòng có giá phòng theo ngày lớn hơn 2.000.000 đồng. (1 điểm)
- Cho biết số lần đặt phòng có trạng thái "Đã xác nhận" của mỗi phòng trong khoảng thời gian từ năm 2024 đến năm 2025. Thông tin hiển thị: MaPH, LoaiPhong, SoLanDat. Sắp xếp thứ tự theo SoLanDat giảm dần. (1 điểm)
- Trong năm 2025, xác định các đơn đặt phòng (MaDatPhong) đã đặt phòng thuộc loại phòng là "Deluxe" có sử dụng cả hai dịch vụ có tên là "Spa" và "Massage" trong ngày nhận phòng (NgayCheckIn). (1 điểm)
- Trong các dịch vụ có tổng số lượng được sử dụng nhiều nhất trong quý III năm 2025, tìm dịch vụ được sử dụng trong các đơn đặt phòng có tổng tiền dịch vụ đã sử dụng thấp nhất. Thông tin hiển thị: MaDV, TenDV, MaDatPhong, TongTienDichVu. (1 điểm)
- Liệt kê các dịch vụ (MaDV, TenDV) không bao gồm dịch vụ có tên là "Massage", được sử dụng bởi cả 3 phòng có tổng số tiền dịch vụ đã sử dụng cao nhất trong các đơn đặt phòng có trạng thái "Đã xác nhận". (1 điểm)
ĐÁP ÁN · CẦN MÃ TRUY CẬP RIÊNG
Xem đáp án
Nội dung được bảo vệ
Nhập mã truy cập để xem đáp án.
Mã đáp án chỉ được phát sau khi kết thúc giờ làm bài - Khác với mã xem đề thi.
Đáp án đầy đủ cho cả 9 câu (10 điểm). Gõ lại từng câu vào SSMS để nhớ lâu hơn nhé - Nút Copy ở đây chỉ mang tính hình thức thôi.
CÂU 1 · 3 ĐIỂM · CREATE TABLE
Đề bài: Tạo cơ sở dữ liệu tên "QLRESORT" bao gồm các quan hệ như trong bảng thuộc tính trên. Khai báo khóa chính, khóa ngoại.
CREATE DATABASE QLRESORT;
GO
USE QLRESORT;
GO
CREATE TABLE PHONG
(
MaPH CHAR(5) PRIMARY KEY,
LoaiPhong VARCHAR(50),
GiaPhong MONEY,
TinhTrangPhong VARCHAR(20),
DienTich FLOAT
);
CREATE TABLE DICHVU
(
MaDV CHAR(5) PRIMARY KEY,
TenDV VARCHAR(50),
DonGia MONEY
);
CREATE TABLE KHACHHANG
(
MaKH CHAR(5) PRIMARY KEY,
HoTen VARCHAR(50),
CCCD VARCHAR(12),
NgaySinh SMALLDATETIME,
GioiTinh VARCHAR(10),
SoDT VARCHAR(15),
Email VARCHAR(50),
DiaChi VARCHAR(150)
);
CREATE TABLE DATPHONG
(
MaDatPhong CHAR(5) PRIMARY KEY,
MaKH CHAR(5),
MaPH CHAR(5),
NgayDat SMALLDATETIME,
NgayCheckIn SMALLDATETIME,
NgayCheckOut SMALLDATETIME,
TrangThai VARCHAR(20),
FOREIGN KEY (MaKH) REFERENCES KHACHHANG(MaKH),
FOREIGN KEY (MaPH) REFERENCES PHONG(MaPH)
);
CREATE TABLE THANHTOAN
(
MaThanhToan CHAR(5) PRIMARY KEY,
MaDatPhong CHAR(5),
SoTien MONEY,
PTThanhToan VARCHAR(20),
TTThanhToan VARCHAR(20),
NgayThanhToan SMALLDATETIME,
FOREIGN KEY (MaDatPhong) REFERENCES DATPHONG(MaDatPhong)
);
CREATE TABLE CTDICHVU
(
MaDatPhong CHAR(5),
MaDV CHAR(5),
NgaySuDung SMALLDATETIME,
SoLuong INT,
ThanhTien MONEY,
PRIMARY KEY (MaDatPhong, MaDV, NgaySuDung),
FOREIGN KEY (MaDatPhong) REFERENCES DATPHONG(MaDatPhong),
FOREIGN KEY (MaDV) REFERENCES DICHVU(MaDV)
);Tạo lần lượt 6 bảng theo đúng thứ tự phụ thuộc khóa ngoại: PHONG, DICHVU, KHACHHANG không phụ thuộc bảng nào nên tạo trước; DATPHONG tham chiếu KHACHHANG và PHONG nên tạo sau; THANHTOAN và CTDICHVU tham chiếu DATPHONG nên tạo cuối cùng.
Không có cặp khóa ngoại tham chiếu vòng lẫn nhau như một số lược đồ khác (kiểu KHOA↔GIAOVIEN) nên không cần ALTER TABLE bổ sung sau - Chỉ cần đúng thứ tự tạo bảng theo chiều phụ thuộc là khai báo được hết khóa ngoại ngay trong CREATE TABLE. Khóa chính của CTDICHVU là bộ ba (MaDatPhong, MaDV, NgaySuDung) vì đề cho phép 1 đơn đặt phòng dùng cùng 1 dịch vụ vào nhiều ngày khác nhau (mỗi ngày là 1 dòng riêng).
CÂU 2.1 · 0.5 ĐIỂM · CHECK
Đề bài: Đối với các phòng thuộc loại phòng "Deluxe", giá thuê theo ngày phải lớn hơn 500.000 đồng.
ALTER TABLE PHONG ADD CONSTRAINT CK_PHONG_Deluxe
CHECK (LoaiPhong <> 'Deluxe' OR GiaPhong > 500000);LoaiPhong <> 'Deluxe' OR GiaPhong > 500000 là dạng CHECK "nếu...thì..." kinh điển - Không phải Deluxe thì không ràng buộc gì cả (vế đầu đúng), là Deluxe thì bắt buộc vế sau phải đúng.
Ràng buộc chỉ áp dụng cho 1 loại phòng cụ thể, không phải toàn bảng - Nếu viết thẳng CHECK (GiaPhong > 500000) thì mọi phòng (kể cả Standard, Superior) đều bị ép giá tối thiểu 500.000, sai với ý đề chỉ giới hạn riêng Deluxe. Đây là kỹ thuật dùng phép kéo theo (P ⟹ Q ⟺ ¬P ∨ Q) để diễn đạt điều kiện có điều kiện bằng CHECK.
CÂU 2.2 · 0.5 ĐIỂM · CHECK ... LIKE
Đề bài: Số điện thoại của khách hàng phải bắt đầu bằng chữ số "0".
ALTER TABLE KHACHHANG ADD CONSTRAINT CK_KHACHHANG_SoDT
CHECK (SoDT LIKE '0%');LIKE '0%' khớp mọi chuỗi bắt đầu bằng ký tự "0", dấu % đại diện cho phần còn lại độ dài bất kỳ.
Ràng buộc "bắt đầu bằng" là ràng buộc định dạng chuỗi, không phải giá trị cụ thể hay khoảng số - CHECK ... LIKE là công cụ đúng cho trường hợp này, tương tự Câu 8 (YC2 - Phần I) của BTTH1 Tuần 1.
CÂU 2.3 · 1 ĐIỂM · TRIGGER AFTER INSERT, UPDATE
Đề bài: Viết TRIGGER để kiểm tra khi thêm hoặc cập nhật dữ liệu vào bảng CTDICHVU. Nếu ngày sử dụng dịch vụ (NgaySuDung) không nằm trong khoảng thời gian lưu trú từ NgayCheckIn đến NgayCheckOut của đơn đặt phòng tương ứng, thì trigger phải báo lỗi và hủy giao dịch.
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể vi phạm |
|---|---|---|
CTDICHVU | INSERT, UPDATE | Thêm/sửa 1 dòng sử dụng dịch vụ với NgaySuDung nằm ngoài thời gian lưu trú - Đây cũng chính xác là những gì đề bài yêu cầu bắt. |
CREATE TRIGGER TRG_INSERTUPDATE_CTDICHVU_NgaySuDung
ON CTDICHVU
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS (
SELECT *
FROM inserted i
JOIN DATPHONG dp ON i.MaDatPhong = dp.MaDatPhong
WHERE i.NgaySuDung < dp.NgayCheckIn OR i.NgaySuDung > dp.NgayCheckOut
)
BEGIN
RAISERROR (N'Ngay su dung dich vu khong nam trong thoi gian luu tru!', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;
GOJOIN inserted với DATPHONG qua MaDatPhong để lấy đúng khoảng lưu trú của đơn đặt phòng tương ứng; IF EXISTS kiểm tra toàn bộ inserted cùng lúc nên đúng dù INSERT/UPDATE tác động 1 hay nhiều dòng.
Đây là dạng "nằm ngoài khoảng" nên phải phủ định bằng OR: Sớm hơn NgayCheckIn hoặc muộn hơn NgayCheckOut đều là vi phạm - Không thể viết gộp thành 1 điều kiện BETWEEN vì đây là Trigger báo lỗi khi không thỏa, không phải lọc khi thỏa. Mở rộng: Đáp án chính thức chỉ cần Trigger trên CTDICHVU (đã đủ 1 điểm) - Trên thực tế, ràng buộc này còn có thể bị phá vỡ nếu UPDATE đổi NgayCheckIn/NgayCheckOut của DATPHONG khiến nó không còn bao trọn các NgaySuDung đã ghi nhận trước đó - Muốn bảo vệ tuyệt đối thì cần thêm 1 Trigger tương tự trên DATPHONG AFTER UPDATE, nhưng đề bài không yêu cầu phần này.
CÂU 3.1 · 1 ĐIỂM · JOIN
Đề bài: Liệt kê các dịch vụ (MaDV, TenDV) đã được sử dụng trong các đơn đặt phòng trong sáu tháng đầu năm 2025 có trạng thái "Đã xác nhận" và đã đặt phòng có giá phòng theo ngày lớn hơn 2.000.000 đồng.
SELECT DISTINCT DV.MaDV, TenDV
FROM CTDICHVU CT
JOIN DATPHONG DP ON CT.MaDatPhong = DP.MaDatPhong
JOIN PHONG PH ON DP.MaPH = PH.MaPH
JOIN DICHVU DV ON CT.MaDV = DV.MaDV
WHERE YEAR(NgaySuDung) = 2025 AND MONTH(NgaySuDung) BETWEEN 1 AND 6
AND DP.TrangThai = 'Da xac nhan' AND GiaPhong > 2000000;JOIN liên tiếp 4 bảng để đủ điều kiện lọc trên cả 3 thực thể khác nhau: Ngày dùng dịch vụ (CTDICHVU), trạng thái đơn (DATPHONG), giá phòng (PHONG); DISTINCT loại trùng vì 1 dịch vụ có thể được nhiều đơn khác nhau sử dụng.
"Sáu tháng đầu năm 2025" lọc trên NgaySuDung (ngày dùng dịch vụ), không phải NgayDat hay NgayCheckIn - Đọc kỹ đề để biết chính xác cột nào bị ràng buộc điều kiện thời gian, tránh lọc nhầm cột.
CÂU 3.2 · 1 ĐIỂM · GROUP BY ... ORDER BY
Đề bài: Cho biết số lần đặt phòng có trạng thái "Đã xác nhận" của mỗi phòng trong khoảng thời gian từ năm 2024 đến năm 2025. Thông tin hiển thị: MaPH, LoaiPhong, SoLanDat. Sắp xếp thứ tự theo SoLanDat giảm dần.
SELECT PH.MaPH, LoaiPhong, COUNT(DP.MaDatPhong) AS SoLanDat
FROM PHONG PH
JOIN DATPHONG DP ON PH.MaPH = DP.MaPH
WHERE DP.TrangThai = 'Da xac nhan' AND YEAR(DP.NgayDat) BETWEEN 2024 AND 2025
GROUP BY PH.MaPH, LoaiPhong
ORDER BY SoLanDat DESC;COUNT(DP.MaDatPhong) đếm số đơn đặt phòng đã lọc theo từng phòng; GROUP BY phải liệt kê cả MaPH và LoaiPhong vì cả 2 đều xuất hiện trong SELECT mà không nằm trong hàm tổng hợp.
JOIN bắt đầu từ PHONG (không phải DATPHONG) chỉ mang tính quy ước đọc - Bản chất INNER JOIN không quan tâm thứ tự bảng, nhưng ở đây phù hợp vì đề hỏi "mỗi phòng", nên đặt PHONG làm bảng chính giúp câu lệnh dễ đọc hơn.
CÂU 3.3 · 1 ĐIỂM · INTERSECT
Đề bài: Trong năm 2025, xác định các đơn đặt phòng (MaDatPhong) đã đặt phòng thuộc loại phòng là "Deluxe" có sử dụng cả hai dịch vụ có tên là "Spa" và "Massage" trong ngày nhận phòng (NgayCheckIn).
(SELECT DP.MaDatPhong
FROM DATPHONG DP
JOIN PHONG PH ON DP.MaPH = PH.MaPH
JOIN CTDICHVU CT ON DP.MaDatPhong = CT.MaDatPhong
JOIN DICHVU DV ON CT.MaDV = DV.MaDV
WHERE PH.LoaiPhong = 'Deluxe' AND YEAR(DP.NgayCheckIn) = 2025
AND CT.NgaySuDung = DP.NgayCheckIn AND DV.TenDV = 'Spa')
INTERSECT
(SELECT DP.MaDatPhong
FROM DATPHONG DP
JOIN PHONG PH ON DP.MaPH = PH.MaPH
JOIN CTDICHVU CT ON DP.MaDatPhong = CT.MaDatPhong
JOIN DICHVU DV ON CT.MaDV = DV.MaDV
WHERE PH.LoaiPhong = 'Deluxe' AND YEAR(DP.NgayCheckIn) = 2025
AND CT.NgaySuDung = DP.NgayCheckIn AND DV.TenDV = 'Massage');2 truy vấn giống hệt nhau về cấu trúc, chỉ khác điều kiện tên dịch vụ (Spa/Massage); INTERSECT lấy giao 2 tập MaDatPhong, chỉ giữ lại đơn thỏa cả 2 vế.
CT.NgaySuDung = DP.NgayCheckIn là điều kiện bắt buộc - Đề yêu cầu dịch vụ dùng đúng "trong ngày nhận phòng", không phải bất kỳ lúc nào trong thời gian lưu trú. Với dữ liệu mẫu, kết quả gồm DP008 (Spa và Massage cùng dùng ngày 18/1/2025, đúng ngày check-in) và DP015 (Spa và Massage cùng dùng ngày 8/7/2025, đúng ngày check-in) - DP011 tuy có cả Spa lẫn Massage nhưng Massage lại dùng lệch 1 ngày so với NgayCheckIn nên bị loại.
CÂU 3.4 · 1 ĐIỂM · TOP WITH TIES
Đề bài: Trong các dịch vụ có tổng số lượng được sử dụng nhiều nhất trong quý III năm 2025, tìm dịch vụ được sử dụng trong các đơn đặt phòng có tổng tiền dịch vụ đã sử dụng thấp nhất. Thông tin hiển thị: MaDV, TenDV, MaDatPhong, TongTienDichVu.
SELECT DV.MaDV, TenDV, DP.MaDatPhong, SUM(ThanhTien) AS TongTienDichVu
FROM CTDICHVU CT
JOIN DATPHONG DP ON CT.MaDatPhong = DP.MaDatPhong
JOIN DICHVU DV ON DV.MaDV = CT.MaDV
WHERE DV.MaDV IN (
SELECT TOP 1 WITH TIES MaDV
FROM CTDICHVU
WHERE YEAR(NgaySuDung) = 2025 AND MONTH(NgaySuDung) BETWEEN 7 AND 9
GROUP BY MaDV
ORDER BY SUM(SoLuong) DESC
)
AND DP.MaDatPhong IN (
SELECT TOP 1 WITH TIES MaDatPhong
FROM CTDICHVU
WHERE YEAR(NgaySuDung) = 2025 AND MONTH(NgaySuDung) BETWEEN 7 AND 9
GROUP BY MaDatPhong
ORDER BY SUM(ThanhTien) ASC
)
GROUP BY DV.MaDV, TenDV, DP.MaDatPhong;2 subquery độc lập, mỗi subquery giải quyết đúng 1 vế của đề: Vế 1 tìm (các) dịch vụ có tổng SoLuong cao nhất trong quý III/2025; vế 2 tìm (các) đơn đặt phòng có tổng ThanhTien thấp nhất cùng quý - TOP 1 WITH TIES đảm bảo không bỏ sót nếu có đồng hạng.
Đây là câu khó nhất đề vì có 2 tiêu chí "nhất" độc lập cần thỏa đồng thời - Dịch vụ phải nằm trong nhóm dùng nhiều nhất, đồng thời đơn đặt phòng chứa nó phải nằm trong nhóm chi ít nhất cho dịch vụ. Không thể gộp thành 1 subquery duy nhất vì 2 tiêu chí tính trên 2 cách GROUP BY khác nhau (theo MaDV và theo MaDatPhong), nên phải tách 2 subquery rồi nối bằng AND.
CÂU 3.5 · 1 ĐIỂM · NOT EXISTS / TOP WITH TIES
Đề bài: Liệt kê các dịch vụ (MaDV, TenDV) không bao gồm dịch vụ có tên là "Massage", được sử dụng bởi cả 3 phòng có tổng số tiền dịch vụ đã sử dụng cao nhất trong các đơn đặt phòng có trạng thái "Đã xác nhận".
SELECT DV.MaDV, DV.TenDV
FROM CTDICHVU CT
JOIN DICHVU DV ON CT.MaDV = DV.MaDV
JOIN DATPHONG DP ON DP.MaDatPhong = CT.MaDatPhong
WHERE DV.TenDV <> 'Massage'
AND DP.MaPH IN (
SELECT TOP 3 WITH TIES DP2.MaPH
FROM DATPHONG DP2
JOIN CTDICHVU CT2 ON DP2.MaDatPhong = CT2.MaDatPhong
WHERE DP2.TrangThai = 'Da xac nhan'
GROUP BY DP2.MaPH
ORDER BY SUM(CT2.ThanhTien) DESC
)
GROUP BY DV.MaDV, DV.TenDV
HAVING COUNT(DISTINCT DP.MaPH) = 3;Cách khác (NOT EXISTS)
SELECT DISTINCT DV.MaDV, DV.TenDV
FROM DICHVU DV
WHERE DV.TenDV <> 'Massage'
AND NOT EXISTS (
SELECT *
FROM DATPHONG DP
WHERE DP.MaPH IN (
SELECT TOP 3 MaPH
FROM DATPHONG DP1
JOIN CTDICHVU CT1 ON DP1.MaDatPhong = CT1.MaDatPhong
WHERE DP1.TrangThai = 'Da xac nhan'
GROUP BY DP1.MaPH
ORDER BY SUM(CT1.ThanhTien) DESC
)
AND NOT EXISTS (
SELECT *
FROM DATPHONG DP2
JOIN CTDICHVU CT2 ON DP2.MaDatPhong = CT2.MaDatPhong
WHERE DP2.MaPH = DP.MaPH AND CT2.MaDV = DV.MaDV
)
);Đây là dạng phép chia (division) - "Không tồn tại 1 phòng nằm trong Top 3 chi nhiều nhất mà dịch vụ này lại chưa từng được dùng ở đó" tương đương "dịch vụ này được dùng ở cả 3 phòng Top 3". Cách này không cần HAVING COUNT() = 3 vì bản thân cấu trúc 2 lớp NOT EXISTS đã tự đảm bảo phủ đủ cả 3 phòng.
Subquery TOP 3 WITH TIES tìm 3 phòng (có thể nhiều hơn nếu đồng hạng) chi nhiều nhất cho dịch vụ trong các đơn "Đã xác nhận"; HAVING COUNT(DISTINCT DP.MaPH) = 3 ở truy vấn ngoài đảm bảo dịch vụ đó phải xuất hiện ở đủ cả 3 phòng đó, không phải chỉ 1-2 phòng.
"Được sử dụng bởi cả 3 phòng" là yêu cầu kiểu "với mọi" (for all) trên 1 tập con cụ thể (3 phòng Top), khác với "được sử dụng bởi ít nhất 1 phòng" (chỉ cần IN là đủ) - Phải đếm số phòng phân biệt mà dịch vụ đó khớp được và so với đúng 3 mới chắc chắn phủ hết. Đây cũng là lý do GROUP BY + HAVING COUNT DISTINCT là cách trực quan hơn để diễn đạt "với mọi trên 1 tập hữu hạn đã biết trước", còn phép chia 2 lớp NOT EXISTS (Cách khác) tổng quát hơn khi tập đối chiếu không biết trước số lượng chính xác.
