Database Practice Mock Exam
This mock exam follows the exact structure of the official practice exam - Creating the database and keys (3 points), integrity constraints and Trigger (2 points), data queries (5 points). The exam is held in-class at the practice session; students may not use any reference material. Both the exam paper and the answer key require their own separate access code, provided by the instructor at the mock-exam session.
EXAM PAPER · ACCESS CODE REQUIRED
View the exam paper
Protected content
Enter the access code to view the exam paper.
The exam code is given by the instructor at the mock-exam session - Different from the answer-key code.
Database schema "Resort Booking Management"
Given a database schema with the following 6 relations:
PHONG (MaPH, LoaiPhong, GiaPhong, TinhTrangPhong, DienTich)
Description: Stores room information - Room code, room type, daily rate, room status, and area.
| Attribute | Data type | Description |
|---|---|---|
MaPH | char(5) | Room code - Primary key |
LoaiPhong | varchar(50) | Room type (Deluxe, Suite, Standard, Superior,...) |
GiaPhong | money | Daily room rate (VND) |
TinhTrangPhong | varchar(20) | Room status (Vacant, Booked, Occupied, Under maintenance,...) |
DienTich | float | Room area (m²) |
DICHVU (MaDV, TenDV, DonGia)
Description: Stores service information - Service code, service name, and unit price.
| Attribute | Data type | Description |
|---|---|---|
MaDV | char(5) | Service code - Primary key |
TenDV | varchar(50) | Service name (Spa, Massage, Dining, Playground,...) |
DonGia | money | Service unit price (VND) |
KHACHHANG (MaKH, HoTen, CCCD, NgaySinh, GioiTinh, SoDT, Email, DiaChi)
Description: Stores customer information - Customer code, full name, national ID number, date of birth, gender, phone number, email, and address.
| Attribute | Data type | Description |
|---|---|---|
MaKH | char(5) | Customer code - Primary key |
HoTen | varchar(50) | Customer full name |
CCCD | varchar(12) | National ID number |
NgaySinh | smalldatetime | Customer date of birth |
GioiTinh | varchar(10) | Customer gender |
SoDT | varchar(15) | Phone number |
Email | varchar(50) | Contact email |
DiaChi | varchar(150) | Customer address |
DATPHONG (MaDatPhong, MaKH, MaPH, NgayDat, NgayCheckIn, NgayCheckOut, TrangThai)
Description: Stores booking information - Booking code, the customer who booked, the room booked, booking date, check-in date, check-out date, and booking status.
| Attribute | Data type | Description |
|---|---|---|
MaDatPhong | char(5) | Booking code - Primary key |
MaKH | char(5) | Customer code who booked - Foreign key to KHACHHANG |
MaPH | char(5) | Room code booked - Foreign key to PHONG |
NgayDat | smalldatetime | Booking date |
NgayCheckIn | smalldatetime | Check-in date |
NgayCheckOut | smalldatetime | Check-out date |
TrangThai | varchar(20) | Booking status (Confirmed, Cancelled, Occupied, Pending confirmation,...) |
THANHTOAN (MaThanhToan, MaDatPhong, SoTien, PTThanhToan, TTThanhToan, NgayThanhToan)
Description: Stores payment information - Payment code, the booking being paid, amount, payment method, status, and payment date.
| Attribute | Data type | Description |
|---|---|---|
MaThanhToan | char(5) | Payment code - Primary key |
MaDatPhong | char(5) | Booking code being paid - Foreign key to DATPHONG |
SoTien | money | Payment amount (VND) |
PTThanhToan | varchar(20) | Payment method (Cash, Bank transfer, Voucher,...) |
TTThanhToan | varchar(20) | Payment status (Paid, Unpaid, Partially paid,...) |
NgayThanhToan | smalldatetime | Payment date |
CTDICHVU (MaDatPhong, MaDV, NgaySuDung, SoLuong, ThanhTien)
Description: Stores service usage details - Which booking used which service, on what date, quantity, and the amount charged. The primary key is the triple (MaDatPhong, MaDV, NgaySuDung).
| Attribute | Data type | Description |
|---|---|---|
MaDatPhong | char(5) | Booking code - Primary key, foreign key to DATPHONG |
MaDV | char(5) | Service code used - Primary key, foreign key to DICHVU |
NgaySuDung | smalldatetime | Date the service was used - Primary key |
SoLuong | int | Quantity of the service used |
ThanhTien | money | Total amount for the service used |
Relationship diagram
KHACHHANG (1) ➜ DATPHONG (N): One customer can make many bookings.
PHONG (1) ➜ DATPHONG (N): One room can appear in many different bookings (at different time periods).
DATPHONG (1) ➜ THANHTOAN (N): One booking can have multiple payments (e.g. partial payments).
DATPHONG (1) ➜ CTDICHVU (N): One booking can use multiple services, on multiple different dates.
DICHVU (1) ➜ CTDICHVU (N): One service can be used by many different bookings.
Preview sample data (PHONG, DICHVU)
PHONG (12 rows)
| 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 rows)
| 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 rows), DATPHONG (20 rows), THANHTOAN (20 rows), and CTDICHVU (37 rows) are only in the du-lieu-mau-thi-thu.sql file above - Too long to preview here, download the file to run it directly in SSMS.
Exam requirements
Students must complete the following requirements using SQL:
Part 1 - Database creation (3 points)
- Create a database named "QLRESORT" containing the relations shown in the attribute table above. Declare primary keys and foreign keys. (3 points)
Part 2 - Integrity constraints (2 points)
- For rooms of type "Deluxe", the daily rate must be greater than 500,000 VND. (0.5 points)
- A customer's phone number must start with the digit "0". (0.5 points)
- Write a TRIGGER to check when data is inserted or updated into the CTDICHVU table. If the service usage date (NgaySuDung) does not fall within the stay period from NgayCheckIn to NgayCheckOut of the corresponding booking, the trigger must raise an error and cancel the transaction. (1 point)
Part 3 - Data queries (5 points)
- List the services (MaDV, TenDV) used in bookings during the first six months of 2025 with status "Confirmed" and booked for a room with a daily rate greater than 2,000,000 VND. (1 point)
- Show the number of "Confirmed" bookings for each room during the period from 2024 to 2025. Display: MaPH, LoaiPhong, SoLanDat. Sort by SoLanDat in descending order. (1 point)
- In 2025, identify the bookings (MaDatPhong) for "Deluxe" rooms that used both the "Spa" and "Massage" services on the check-in date (NgayCheckIn). (1 point)
- Among the services with the highest total quantity used in Q3 2025, find the service used in the bookings with the lowest total service amount. Display: MaDV, TenDV, MaDatPhong, TongTienDichVu. (1 point)
- List the services (MaDV, TenDV), excluding the service named "Massage", that are used by all 3 rooms with the highest total service amount among bookings with status "Confirmed". (1 point)
ANSWER KEY · SEPARATE ACCESS CODE REQUIRED
View the answer key
Protected content
Enter the access code to view the answer key.
The answer-key code is only given out after the exam period ends - Different from the exam-paper code.
Full answers for all 9 questions (10 points). Retype each one into SSMS yourself for better retention - The Copy button here is just for show.
QUESTION 1 · 3 POINTS · CREATE TABLE
Question: Create a database named "QLRESORT" containing the relations shown in the attribute table above. Declare primary keys and foreign keys.
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)
);Create the 6 tables in strict foreign-key dependency order: PHONG, DICHVU, KHACHHANG depend on no other table so they are created first; DATPHONG references KHACHHANG and PHONG so it comes next; THANHTOAN and CTDICHVU reference DATPHONG so they come last.
There is no pair of foreign keys referencing each other in a cycle (unlike some other schemas, e.g. KHOA↔GIAOVIEN), so no extra ALTER TABLE is needed afterward - Simply creating the tables in dependency order lets every foreign key be declared directly inside CREATE TABLE. The primary key of CTDICHVU is the triple (MaDatPhong, MaDV, NgaySuDung) because the same booking is allowed to use the same service on multiple different dates (each date is its own row).
QUESTION 2.1 · 0.5 POINTS · CHECK
Question: For rooms of type "Deluxe", the daily rate must be greater than 500,000 VND.
ALTER TABLE PHONG ADD CONSTRAINT CK_PHONG_Deluxe
CHECK (LoaiPhong <> 'Deluxe' OR GiaPhong > 500000);LoaiPhong <> 'Deluxe' OR GiaPhong > 500000 is the classic "if...then..." CHECK pattern - If it isn't Deluxe, there's no constraint at all (the first clause is true); if it is Deluxe, the second clause must be true.
The constraint only applies to one specific room type, not the whole table - Writing CHECK (GiaPhong > 500000) directly would force every room (including Standard and Superior) to have a minimum rate of 500,000, which is wrong since the requirement only limits Deluxe rooms. This is the technique of using implication (P ⟹ Q ⟺ ¬P ∨ Q) to express a conditional rule with CHECK.
QUESTION 2.2 · 0.5 POINTS · CHECK ... LIKE
Question: A customer's phone number must start with the digit "0".
ALTER TABLE KHACHHANG ADD CONSTRAINT CK_KHACHHANG_SoDT
CHECK (SoDT LIKE '0%');LIKE '0%' matches any string starting with the character "0"; the % wildcard represents any remaining characters of any length.
A "starts with" constraint is a string-format constraint, not a specific value or numeric range - CHECK ... LIKE is the right tool for this case, similar to Question 8 (Requirement 2 - Part I) of Week 1's Practice Exercise 1.
QUESTION 2.3 · 1 POINT · TRIGGER AFTER INSERT, UPDATE
Question: Write a TRIGGER to check when data is inserted or updated into the CTDICHVU table. If the service usage date (NgaySuDung) does not fall within the stay period from NgayCheckIn to NgayCheckOut of the corresponding booking, the trigger must raise an error and cancel the transaction.
Impact-scope table
| Table | Operations to catch | Why it can be violated |
|---|---|---|
CTDICHVU | INSERT, UPDATE | Adding/editing a service-usage row whose NgaySuDung falls outside the stay period - This is exactly what the requirement asks to catch. |
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 with DATPHONG on MaDatPhong to get the correct stay period for the corresponding booking; IF EXISTS checks the entire inserted table at once, so it stays correct whether INSERT/UPDATE affects 1 row or many.
This is an "outside the range" case, so it must be negated with OR: Earlier than NgayCheckIn or later than NgayCheckOut both count as a violation - It cannot be written as a single BETWEEN condition, because this Trigger raises an error when the condition is not satisfied, not when it is. Extension: The official answer only requires a Trigger on CTDICHVU (already worth the full point) - In practice, this constraint can still be broken if an UPDATE changes a DATPHONG row's NgayCheckIn/NgayCheckOut so that it no longer covers NgaySuDung values already recorded - Full protection would require an additional Trigger on DATPHONG AFTER UPDATE, but this is not required by the question.
QUESTION 3.1 · 1 POINT · JOIN
Question: List the services (MaDV, TenDV) used in bookings during the first six months of 2025 with status "Confirmed" and booked for a room with a daily rate greater than 2,000,000 VND.
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;4 consecutive JOINs are needed to filter on 3 different entities: Service usage date (CTDICHVU), booking status (DATPHONG), room rate (PHONG); DISTINCT removes duplicates since the same service can be used by multiple different bookings.
"The first six months of 2025" filters on NgaySuDung (the service usage date), not NgayDat or NgayCheckIn - Read the question carefully to know exactly which column carries the time constraint, to avoid filtering on the wrong one.
QUESTION 3.2 · 1 POINT · GROUP BY ... ORDER BY
Question: Show the number of "Confirmed" bookings for each room during the period from 2024 to 2025. Display: MaPH, LoaiPhong, SoLanDat. Sort by SoLanDat in descending order.
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) counts the filtered bookings per room; GROUP BY must list both MaPH and LoaiPhong since both appear in SELECT outside an aggregate function.
Starting the JOIN from PHONG (rather than DATPHONG) is purely a readability convention - An INNER JOIN doesn't care about table order, but here it makes sense because the question asks "for each room," so putting PHONG as the leading table makes the statement easier to read.
QUESTION 3.3 · 1 POINT · INTERSECT
Question: In 2025, identify the bookings (MaDatPhong) for "Deluxe" rooms that used both the "Spa" and "Massage" services on the check-in date (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 structurally identical queries, differing only in the service name condition (Spa/Massage); INTERSECT takes the intersection of the 2 MaDatPhong sets, keeping only bookings that satisfy both sides.
CT.NgaySuDung = DP.NgayCheckIn is a required condition - The question asks for the service used exactly "on the check-in date," not at any point during the stay. With the sample data, the result set is DP008 (Spa and Massage both used on 2025-01-18, exactly the check-in date) and DP015 (Spa and Massage both used on 2025-07-08, exactly the check-in date) - DP011 has both Spa and Massage but Massage was used 1 day off from NgayCheckIn, so it is excluded.
QUESTION 3.4 · 1 POINT · TOP WITH TIES
Question: Among the services with the highest total quantity used in Q3 2025, find the service used in the bookings with the lowest total service amount. Display: 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 independent subqueries, each handling exactly one side of the requirement: The first finds the service(s) with the highest total SoLuong in Q3 2025; the second finds the booking(s) with the lowest total ThanhTien in the same quarter - TOP 1 WITH TIES ensures ties are never missed.
This is the hardest question on the exam because it has 2 independent "highest/lowest" criteria that must hold simultaneously - The service must be in the most-used group, and the booking that contains it must be in the lowest-spending group for that service. These cannot be combined into a single subquery because the 2 criteria are computed with 2 different GROUP BY keys (by MaDV and by MaDatPhong), so they must be split into 2 subqueries joined by AND.
QUESTION 3.5 · 1 POINT · NOT EXISTS / TOP WITH TIES
Question: List the services (MaDV, TenDV), excluding the service named "Massage", that are used by all 3 rooms with the highest total service amount among bookings with status "Confirmed".
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;Alternative approach (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
)
);This is a division-style query - "There is no room in the Top-3-spending set where this service was never used" is equivalent to "this service was used at all 3 Top-3 rooms." This approach does not need HAVING COUNT() = 3 because the double NOT EXISTS structure itself already guarantees full coverage of all 3 rooms.
The TOP 3 WITH TIES subquery finds the 3 rooms (possibly more if tied) with the highest service spending among "Confirmed" bookings; HAVING COUNT(DISTINCT DP.MaPH) = 3 in the outer query ensures the service appears at all 3 of those rooms, not just 1-2 of them.
"Used by all 3 rooms" is a "for all" requirement over a specific, known subset (the 3 top rooms), unlike "used by at least 1 room" (which only needs IN) - Counting the distinct rooms the service matches and comparing to exactly 3 is what guarantees full coverage. This is also why GROUP BY + HAVING COUNT DISTINCT is the more intuitive way to express "for all over a known finite set," while the double NOT EXISTS division pattern (Alternative approach) is more general when the comparison set's exact size isn't known in advance.
