Summary: SQL Server project for a multi-hotel reservation system, login users, price calendars, available rooms, calendar blocks, and support messages.
- Overview
- Project Files
- Prerequisites
- Data Model - Core Tables
- Recommended Indexes
- User-Defined Functions (UDFs)
- Stored Procedures & Utilities
- Triggers
- Usage Examples (queries)
- Validation & Recommended Tests
- Deployment / Execution Order
- Security & Permissions
- Error Messages & Handling
This project implements a hotel booking system in T-SQL with the following features:
- Independent reservations, prices, support messages, and calendar for each hotel.
- User Registration and Login "Password / Salt"
- Creation of reservations by managing the number of rooms available in relation to reservation overlapping dates and/or blocked dates.
- The same applies to customized daily prices.
- Blocking date ranges via
CalendarBlocksper Hotel - Custom daily prices via
CustomPricesper Hotel - Automatic final price calculation (combining hotel base price and custom daily prices) based on their respective intercessions and unions.
- Reusable functions for overlap checks, user and hotel reports
- Validation of incorrect entry and exit dates.
The goal is to enforce business integrity at the database level and provide reusable query objects for the application layer.
Project Folder/
└── SQL/
├── Hotel Network Project.sln
├── SQL_DOC.md
└── Schemas/
├── DataBase.sql
├── Indexes.sql
├── Tables.sql
├── Functions/
│ ├── fn_ListBookingsByHotel.sql
│ ├── fn_ListBookingsByUser.sql
│ ├── fn_ListRequestedSupportByHotel.sql
│ ├── fn_Login.sql
│ └── fn_PriceSetter.sql
├── Procedures/
│ ├── usp_DateTimeRangeChecker.sql
│ ├── usp_DateRangeChecker.sql
│ └── usp_CheckBookingsBlocksOverlaps.sql
├── Queries/
│ └── SQLQueryTests.sql
└── Triggers/
├── trg_Check_Booking_Overlaps.sql
├── trg_Check_Booking_Overlaps_On_Update.sql
├── trg_Check_CalendarBlocks_Overlaps.sql
└── trg_Check_CustomPrices_Overlaps.sql
| File | Description |
|---|---|
| DataBase.sql | Initial configuration (schemas, server settings). |
| Tables.sql | Table creation scripts (Bookings, Hotels, Users, CustomPrices, CalendarBlocks, and SupportMessages). |
| Indexes.sql | Index creation scripts for the required triggers, functions, and procedures. |
| fn_ListBookingsByHotel.sql | Function to list bookings by hotel. |
| fn_ListBookingsByUser.sql | Function to list bookings by user. |
| fn_ListRequestedSupportByHotel.sql | Function that aggregates support counts by hotel. |
| fn_Login.sql | Tests login with the PasswordHash and Salt columns using indexes. |
| usp_DateTimeRangeChecker.sql | Utility stored procedure for date range checks on triggers. |
| trg_Check_Booking_Overlaps.sql | INSTEAD OF INSERT trigger to check for overlapping reservation dates, available rooms, blocked dates, and custom daily rate overlaps/unions. |
| trg_Check_CalendarBlocks_Overlaps.sql | INSTEAD OF INSERT trigger for CalendarBlocks overlap validation per hotel. |
| trg_Check_CustomPrices_Overlaps.sql | INSTEAD OF INSERT trigger for CustomPrices overlap validation per hotel. |
- Microsoft SQL Server 2018+ (SQL Server 2019/2022 recommended).
CREATE OR ALTERrequires a compatible version. - DDL/DML permissions to create objects in the target schema (typically
dbo). - Optional:
tSQLtfor unit testing if you want automated tests.
This section describes the primary tables used by the project. Refer to Tables.sql for the exact column definitions and constraints.
ID INT PKName NVARCHAR(200)Location NVARCHAR(255)Price DECIMAL(10,2)- base daily price.Rooms INT- Total rooms available on overlapping dates.
ID INT PK(IDENTITY)UserID INT(FK → Users.ID)HotelID INT(FK → Hotels.ID)CheckIn DATETIME2CheckOut DATETIME2Price DECIMAL(18,2)- Final booking price.CreatedAt DATETIME2(DEFAULTSYSUTCDATETIME())
ID INT PKHotelID INT(FK → Hotels.ID)DateIn DATETIME2DateOut DATETIME2Price DECIMAL(10,2)- Daily price.
ID INT PKHotelID INT(FK → Hotels.ID)DateIn DATETIME2DateOut DATETIME2
Users(ID, Username, Email, Name, ...)SupportMessages(ID, UserID, HotelID, Message, CreatedDate)
All indexes for triggers, functions, and procedures are in Indexes.sql
Eg:.
CREATE INDEX IX_Bookings_Hotel_DateRange
ON Bookings (HotelID, CheckIn, CheckOut)
INCLUDE (ID);Why:
HotelIDis equality-filtered - make it the first key column.CheckIn/CheckOutare range filters and help reduce the scanned rows.INCLUDE (ID)avoids lookups forCOUNT(ID)queries.
Other useful indexes:
IX_CustomPrices_Hotel_DateRange (HotelID, DateIn, DateOut)IX_CalendarBlocks_Hotel_DateRange (HotelID, DateIn, DateOut)IX_Users_EmailIX_Users_Username
Always keep statistics up-to-date (UPDATE STATISTICS) after bulk loads.
Files provided and their purpose:
| File | Description |
|---|---|
| fn_ListBookingsByHotel.sql | Returns bookings for a given hotel. |
| fn_ListBookingsByUser.sql | Returns bookings for a specific user. |
| fn_ListRequestedSupportByHotel.sql | Aggregates support requests per user with last message. |
| fn_Login | Tests the login by combining PasswordHash and Salt to return user ID. If it does not exist, it returns -1. |
usp_DateTimeRangeChecker.sql |
Utility procedure for checking date-time intersections (useful for tests and callers outside triggers). |
Main trigger: trg_Check_Booking_Overlaps - INSTEAD OF INSERT on Bookings that enforces:
CheckIn < CheckOutvalidation per row.CalendarBlocksoverlap check (abort if any inserted row intersects a block).- Capacity check (
Rooms) combining existing overlaps and other rows in the same batch. - Final price computation per inserted row (custom price days + remaining days × hotel base price).
Final price calculation (per row):
- For each
CustomPricesentry that intersects the booking, computeoverlapDays * cp.Priceand sum. - If booking has an explicit
Priceprovided on insert, use it. - Otherwise:
final = sumCustomPrice + (bookingDays - overlapDays) * hotelBasePrice.
Count overlaps for hotel in date range:
DECLARE @Start DATETIME2 = '2025-03-01 00:00:00', @End DATETIME2 = '2025-03-09 23:59:59';
SELECT COUNT(ID) AS OverlapCount
FROM Bookings
WHERE
HotelID = 1
AND CheckIn < @End
AND CheckOut > @Start;Insert booking example (trigger performs validation & price):
INSERT INTO Bookings (CheckIn, CheckOut, UserID, HotelID)
VALUES ('2025-07-01', '2025-07-05', 10, 1);- Single-row insert: valid booking inserts successfully.
- Batch insert exceeding
Roomscapacity: whole batch is rejected. - Insert where
CheckIn >= CheckOut: rejected. - Insert intersecting
CalendarBlocks: rejected. - Booking crossing
CustomPricesperiods: price is calculated correctly per day. - Concurrency test: two concurrent sessions inserting for the same hotel - verify no race conditions.
Test tools and hints:
- Use
BEGIN TRAN / ROLLBACKto run non-destructive tests. - Use
SET STATISTICS IO, TIME ONto measure IO and CPU cost. - Use
SQL Server Profiler/Extended Eventsto detect blocking.
DataBase.sql(schemas, server-level setup)Tables.sql(create tables & constraints)Indexes.sql(create indexes - create non-clustered indexes after heavy seed load if needed)fn_*.sql(functions)usp_*.sql(procedures)trg_*.sql(triggers) - create triggers last or disable them during seed load- Seed data / migrations
- Create roles like
booking_app_readandbooking_app_writerwith minimal privileges. - Grant
EXECUTEon procedures/functions where appropriate. - Be cautious with ownership chaining and cross-database access in triggers.
Standard trigger error pattern used in this project:
THROW 51000, 'Friendly error message here', 1;Common messages:
The entry date must be earlier than the exit date.Maximum number of rooms sold out for this hotel on the specified dates.The reservation dates overlap with blocking dates.
- Index:
Bookings(HotelID, CheckIn, CheckOut) - Index:
CustomPrices(HotelID, DateIn, DateOut) - Consider
sp_getapplockbyHotelIDorSERIALIZABLEisolation for critical sections - Monitor
PAGE LATCHand blocking at peak times - Use filtered indexes for active rows if you have a
CancelledorIsActiveflag