Compare commits
5 Commits
415f417182
...
e64288694b
| Author | SHA1 | Date | |
|---|---|---|---|
| e64288694b | |||
| ee95be1031 | |||
| 5f6251114f | |||
| a49013fc0c | |||
| 3cd7be1e7b |
@@ -1,9 +1,9 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$CONTAINER = "ewc2025-mysql"
|
||||
$CONTAINER = "hotel-mysql"
|
||||
$IMAGE = "mysql:8.4"
|
||||
$ROOT_PASSWORD = "ewc2025root"
|
||||
$DATABASE = "ewc2025"
|
||||
$ROOT_PASSWORD = "hotel2025root"
|
||||
$DATABASE = "hotel_reservations"
|
||||
$PORT = "13306"
|
||||
|
||||
$SQL_DIR = Resolve-Path "$PSScriptRoot\..\sql"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CONTAINER="ewc2025-mysql"
|
||||
CONTAINER="hotel-mysql"
|
||||
IMAGE="mysql:8.4"
|
||||
ROOT_PASSWORD="ewc2025root"
|
||||
DATABASE="ewc2025"
|
||||
ROOT_PASSWORD="hotel2025root"
|
||||
DATABASE="hotel_reservations"
|
||||
PORT="13306"
|
||||
|
||||
RUNTIME="docker"
|
||||
@@ -26,7 +26,7 @@ else
|
||||
-e MYSQL_DATABASE="${DATABASE}" \
|
||||
-p "${PORT}:3306" \
|
||||
-v "${CONTAINER}-data:/var/lib/mysql" \
|
||||
-v "${SQL_DIR}/schema.sql:/docker-entrypoint-initdb.d/01_schema.sql:ro" \
|
||||
-v "${SQL_DIR}/schema.sql:/docker-entrypoint-initdb.d/01_schema.sql:ro,z" \
|
||||
"${IMAGE}"
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$CONTAINER = "ewc2025-mysql"
|
||||
$CONTAINER = "hotel-mysql"
|
||||
|
||||
$exists = docker ps -a --format '{{.Names}}' | Where-Object { $_ -eq $CONTAINER }
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CONTAINER="ewc2025-mysql"
|
||||
CONTAINER="hotel-mysql"
|
||||
|
||||
RUNTIME="docker"
|
||||
if [[ "${1:-}" == "--podman" ]]; then
|
||||
|
||||
539
generator/generate.cs
Normal file
539
generator/generate.cs
Normal file
@@ -0,0 +1,539 @@
|
||||
#:package MySqlConnector@2.3.7
|
||||
|
||||
using System.Text;
|
||||
using MySqlConnector;
|
||||
|
||||
// ── Config ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const string DSN = "Server=127.0.0.1;Port=13306;Database=hotel_reservations;Uid=root;Pwd=hotel2025root;AllowLoadLocalInfile=true;";
|
||||
const int HOTEL_COUNT = 200;
|
||||
const int GUEST_COUNT = 100_000;
|
||||
const int BOOKING_COUNT = 500_000;
|
||||
const int BATCH = 500;
|
||||
const int SEED = 42;
|
||||
|
||||
var rng = new Random(SEED);
|
||||
|
||||
// ── DB helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
await using var conn = new MySqlConnection(DSN);
|
||||
await conn.OpenAsync();
|
||||
Console.WriteLine("Connected.");
|
||||
|
||||
async Task Exec(string sql)
|
||||
{
|
||||
await using var cmd = new MySqlCommand(sql, conn);
|
||||
cmd.CommandTimeout = 300;
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
async Task<long> ExecScalar(string sql)
|
||||
{
|
||||
await using var cmd = new MySqlCommand(sql, conn);
|
||||
return Convert.ToInt64(await cmd.ExecuteScalarAsync());
|
||||
}
|
||||
|
||||
// Bulk insert: builds a single INSERT ... VALUES (...),(...),...
|
||||
async Task BulkInsert(string table, string columns, List<string> valueTuples)
|
||||
{
|
||||
for (int i = 0; i < valueTuples.Count; i += BATCH)
|
||||
{
|
||||
var batch = valueTuples.Skip(i).Take(BATCH);
|
||||
await Exec($"INSERT INTO {table} ({columns}) VALUES {string.Join(',', batch)}");
|
||||
}
|
||||
}
|
||||
|
||||
string S(string? s) => s == null ? "NULL" : $"'{s.Replace("'", "''")}'";
|
||||
string N(object? n) => n == null ? "NULL" : n.ToString()!;
|
||||
string D(DateTime d) => $"'{d:yyyy-MM-dd}'";
|
||||
string DT(DateTime d) => $"'{d:yyyy-MM-dd HH:mm:ss}'";
|
||||
|
||||
// ── Reference data ────────────────────────────────────────────────────────────
|
||||
|
||||
// ── 1. hotel_chain ────────────────────────────────────────────────────────────
|
||||
Console.WriteLine("[1/8] hotel_chain");
|
||||
|
||||
var chains = new (string Code, string Name)[]
|
||||
{
|
||||
("HLT", "Hilton Worldwide"),
|
||||
("MRT", "Marriott International"),
|
||||
("HYT", "Hyatt Hotels Corporation"),
|
||||
("IHG", "InterContinental Hotels Group"),
|
||||
("WYN", "Wyndham Hotels & Resorts"),
|
||||
("ACC", "Accor"),
|
||||
("BW", "Best Western Hotels"),
|
||||
("RAD", "Radisson Hotels"),
|
||||
("MEL", "Meliá Hotels International"),
|
||||
("NH", "NH Hotel Group"),
|
||||
};
|
||||
|
||||
await BulkInsert("hotel_chain", "code, name",
|
||||
chains.Select(c => $"({S(c.Code)},{S(c.Name)})").ToList());
|
||||
|
||||
var chainIds = new Dictionary<string, int>();
|
||||
{
|
||||
await using var cmd = new MySqlCommand("SELECT hotel_chain_id, code FROM hotel_chain", conn);
|
||||
await using var r = await cmd.ExecuteReaderAsync();
|
||||
while (await r.ReadAsync()) chainIds[r.GetString(1)] = r.GetInt32(0);
|
||||
}
|
||||
|
||||
// ── 2. country ────────────────────────────────────────────────────────────────
|
||||
Console.WriteLine("[2/8] country");
|
||||
|
||||
var countries = new (string Code, string Name, string Currency)[]
|
||||
{
|
||||
("GB","United Kingdom","GBP"), ("FR","France","EUR"), ("DE","Germany","EUR"),
|
||||
("ES","Spain","EUR"), ("IT","Italy","EUR"), ("PT","Portugal","EUR"),
|
||||
("NL","Netherlands","EUR"), ("BE","Belgium","EUR"), ("AT","Austria","EUR"),
|
||||
("CH","Switzerland","CHF"), ("SE","Sweden","SEK"), ("NO","Norway","NOK"),
|
||||
("DK","Denmark","DKK"), ("PL","Poland","PLN"), ("CZ","Czech Republic","CZK"),
|
||||
("HU","Hungary","HUF"), ("HR","Croatia","EUR"), ("GR","Greece","EUR"),
|
||||
("TR","Turkey","TRY"), ("US","United States","USD"), ("CA","Canada","CAD"),
|
||||
("MX","Mexico","MXN"), ("BR","Brazil","BRL"), ("AR","Argentina","ARS"),
|
||||
("AU","Australia","AUD"), ("NZ","New Zealand","NZD"), ("JP","Japan","JPY"),
|
||||
("CN","China","CNY"), ("KR","South Korea","KRW"), ("SG","Singapore","SGD"),
|
||||
("TH","Thailand","THB"), ("AE","United Arab Emirates","AED"),
|
||||
("SA","Saudi Arabia","SAR"), ("EG","Egypt","EGP"), ("ZA","South Africa","ZAR"),
|
||||
("IN","India","INR"), ("MA","Morocco","MAD"), ("TN","Tunisia","TND"),
|
||||
("ID","Indonesia","IDR"), ("MY","Malaysia","MYR"),
|
||||
};
|
||||
|
||||
await BulkInsert("country", "code, name, currency",
|
||||
countries.Select(c => $"({S(c.Code)},{S(c.Name)},{S(c.Currency)})").ToList());
|
||||
|
||||
var countryIds = new Dictionary<string, int>();
|
||||
{
|
||||
await using var cmd = new MySqlCommand("SELECT country_id, code FROM country", conn);
|
||||
await using var r = await cmd.ExecuteReaderAsync();
|
||||
while (await r.ReadAsync()) countryIds[r.GetString(1)] = r.GetInt32(0);
|
||||
}
|
||||
|
||||
// ── 3. star_rating ────────────────────────────────────────────────────────────
|
||||
Console.WriteLine("[3/8] star_rating");
|
||||
|
||||
await BulkInsert("star_rating", "code, description",
|
||||
Enumerable.Range(1, 5).Select(i => $"({i},{S(i + " Star")})").ToList());
|
||||
|
||||
var starIds = new Dictionary<int, int>();
|
||||
{
|
||||
await using var cmd = new MySqlCommand("SELECT star_rating_id, code FROM star_rating", conn);
|
||||
await using var r = await cmd.ExecuteReaderAsync();
|
||||
while (await r.ReadAsync()) starIds[r.GetInt32(1)] = r.GetInt32(0);
|
||||
}
|
||||
|
||||
// ── 4. hotel_characteristic ───────────────────────────────────────────────────
|
||||
Console.WriteLine("[4/8] hotel_characteristic");
|
||||
|
||||
var characteristics = new (string Code, string Desc)[]
|
||||
{
|
||||
("WIFI", "Free WiFi"), ("POOL", "Swimming Pool"),
|
||||
("GYM", "Fitness Center"), ("SPA", "Spa & Wellness"),
|
||||
("RESTAURANT", "On-site Restaurant"), ("BAR", "Hotel Bar"),
|
||||
("PARKING", "Free Parking"), ("VALET", "Valet Parking"),
|
||||
("CONFERENCE", "Conference Rooms"), ("SHUTTLE", "Airport Shuttle"),
|
||||
("ROOM_SVC", "Room Service"), ("PETS", "Pet Friendly"),
|
||||
};
|
||||
|
||||
await BulkInsert("hotel_characteristic", "code, description",
|
||||
characteristics.Select(c => $"({S(c.Code)},{S(c.Desc)})").ToList());
|
||||
|
||||
var charIds = new Dictionary<string, int>();
|
||||
{
|
||||
await using var cmd = new MySqlCommand("SELECT characteristic_id, code FROM hotel_characteristic", conn);
|
||||
await using var r = await cmd.ExecuteReaderAsync();
|
||||
while (await r.ReadAsync()) charIds[r.GetString(1)] = r.GetInt32(0);
|
||||
}
|
||||
|
||||
// ── 5. room_type + rate_period + period_room_rate ─────────────────────────────
|
||||
Console.WriteLine("[5/8] room_type / rate_period / period_room_rate");
|
||||
|
||||
var roomTypes = new (string Code, string Desc, decimal BaseRate, bool Smoking)[]
|
||||
{
|
||||
("SINGLE", "Single Room", 80m, false),
|
||||
("DOUBLE", "Double Room", 120m, false),
|
||||
("TWIN", "Twin Room", 115m, false),
|
||||
("DELUXE", "Deluxe Double", 180m, false),
|
||||
("SUITE", "Junior Suite", 280m, false),
|
||||
("EXEC", "Executive Suite", 450m, false),
|
||||
("FAMILY", "Family Room", 200m, false),
|
||||
};
|
||||
|
||||
await BulkInsert("room_type", "code, description, standard_rate, smoking_yn",
|
||||
roomTypes.Select(rt => $"({S(rt.Code)},{S(rt.Desc)},{rt.BaseRate},0)").ToList());
|
||||
|
||||
var roomTypeIds = new Dictionary<string, int>();
|
||||
{
|
||||
await using var cmd = new MySqlCommand("SELECT room_type_id, code FROM room_type", conn);
|
||||
await using var r = await cmd.ExecuteReaderAsync();
|
||||
while (await r.ReadAsync()) roomTypeIds[r.GetString(1)] = r.GetInt32(0);
|
||||
}
|
||||
|
||||
// Seasons: month → multiplier
|
||||
var ratePeriods = new (string Code, string Desc, int MonthFrom, int MonthTo, decimal Multiplier)[]
|
||||
{
|
||||
("PEAK", "Peak Season (Jun-Aug)", 6, 8, 1.5m),
|
||||
("HIGH", "High Season (Mar-May)", 3, 5, 1.2m),
|
||||
("AUTUMN", "Autumn Season (Sep-Nov)", 9, 11, 1.1m),
|
||||
("WINTER", "Winter Season (Dec-Feb)", 12, 2, 0.9m),
|
||||
};
|
||||
|
||||
await BulkInsert("rate_period", "code, description, month_from, month_to",
|
||||
ratePeriods.Select(rp => $"({S(rp.Code)},{S(rp.Desc)},{rp.MonthFrom},{rp.MonthTo})").ToList());
|
||||
|
||||
var ratePeriodIds = new Dictionary<string, int>();
|
||||
{
|
||||
await using var cmd = new MySqlCommand("SELECT rate_period_id, code FROM rate_period", conn);
|
||||
await using var r = await cmd.ExecuteReaderAsync();
|
||||
while (await r.ReadAsync()) ratePeriodIds[r.GetString(1)] = r.GetInt32(0);
|
||||
}
|
||||
|
||||
// period_room_rate: rate = base_rate * season_multiplier
|
||||
var prrRows = new List<string>();
|
||||
foreach (var rt in roomTypes)
|
||||
foreach (var rp in ratePeriods)
|
||||
{
|
||||
var rate = Math.Round(rt.BaseRate * rp.Multiplier, 2);
|
||||
prrRows.Add($"({roomTypeIds[rt.Code]},{ratePeriodIds[rp.Code]},{rate})");
|
||||
}
|
||||
await BulkInsert("period_room_rate", "room_type_id, rate_period_id, rate", prrRows);
|
||||
|
||||
// Build month → rate lookup in memory
|
||||
var monthToRatePeriodId = new Dictionary<int, int>();
|
||||
foreach (var rp in ratePeriods)
|
||||
{
|
||||
if (rp.MonthFrom <= rp.MonthTo)
|
||||
for (int m = rp.MonthFrom; m <= rp.MonthTo; m++)
|
||||
monthToRatePeriodId[m] = ratePeriodIds[rp.Code];
|
||||
else // wraps year (Dec-Feb)
|
||||
{
|
||||
for (int m = rp.MonthFrom; m <= 12; m++) monthToRatePeriodId[m] = ratePeriodIds[rp.Code];
|
||||
for (int m = 1; m <= rp.MonthTo; m++) monthToRatePeriodId[m] = ratePeriodIds[rp.Code];
|
||||
}
|
||||
}
|
||||
|
||||
// Build (room_type_id, rate_period_id) → rate lookup
|
||||
var rateMap = new Dictionary<(int, int), decimal>();
|
||||
foreach (var rt in roomTypes)
|
||||
foreach (var rp in ratePeriods)
|
||||
rateMap[(roomTypeIds[rt.Code], ratePeriodIds[rp.Code])] =
|
||||
Math.Round(rt.BaseRate * rp.Multiplier, 2);
|
||||
|
||||
// ── 6. hotel + hotel_room + hotel_hotel_characteristic ────────────────────────
|
||||
Console.WriteLine("[6/8] hotel / hotel_room / hotel_hotel_characteristic");
|
||||
|
||||
var hotelCities = new (string City, string Country)[]
|
||||
{
|
||||
("London","GB"), ("Manchester","GB"), ("Edinburgh","GB"),
|
||||
("Paris","FR"), ("Lyon","FR"), ("Nice","FR"),
|
||||
("Berlin","DE"), ("Munich","DE"), ("Hamburg","DE"),
|
||||
("Madrid","ES"), ("Barcelona","ES"), ("Seville","ES"),
|
||||
("Rome","IT"), ("Milan","IT"), ("Florence","IT"),
|
||||
("Lisbon","PT"), ("Porto","PT"), ("Amsterdam","NL"),
|
||||
("Vienna","AT"), ("Zurich","CH"), ("Geneva","CH"),
|
||||
("Stockholm","SE"), ("Oslo","NO"), ("Copenhagen","DK"),
|
||||
("Warsaw","PL"), ("Prague","CZ"), ("Budapest","HU"),
|
||||
("Athens","GR"), ("Istanbul","TR"), ("New York","US"),
|
||||
("Los Angeles","US"), ("Miami","US"), ("Chicago","US"),
|
||||
("Toronto","CA"), ("Vancouver","CA"), ("Sydney","AU"),
|
||||
("Melbourne","AU"), ("Tokyo","JP"), ("Osaka","JP"),
|
||||
("Singapore","SG"), ("Bangkok","TH"), ("Dubai","AE"),
|
||||
("Mumbai","IN"), ("Cape Town","ZA"), ("Marrakech","MA"),
|
||||
("Cairo","EG"), ("Cancun","MX"), ("Rio de Janeiro","BR"),
|
||||
("Seoul","KR"), ("Kuala Lumpur","MY"),
|
||||
};
|
||||
|
||||
// Star rating distribution: 3★ most common, 5★ rarest
|
||||
int[] starWeights = [0, 5, 10, 40, 30, 15]; // index = star, value = weight
|
||||
int PickStar()
|
||||
{
|
||||
int roll = rng.Next(100);
|
||||
int cum = 0;
|
||||
for (int s = 1; s <= 5; s++) { cum += starWeights[s]; if (roll < cum) return s; }
|
||||
return 3;
|
||||
}
|
||||
|
||||
var hotelRows = new List<string>();
|
||||
var roomRows = new List<string>();
|
||||
var hotelCharRows = new List<string>();
|
||||
var charCodes = characteristics.Select(c => c.Code).ToArray();
|
||||
|
||||
// Track room_type per hotel_room for later rate lookups (in-memory)
|
||||
// hotel_room gets an auto-increment ID; we'll load them after insert
|
||||
// So store: hotel index → list of (room_number, room_type_code)
|
||||
var hotelRoomTypes = new List<(int hotelIndex, string roomNumber, string roomTypeCode)>();
|
||||
|
||||
string[] streetNames = ["Main St","Park Ave","King Rd","Grand Blvd","Lake Dr",
|
||||
"Ocean Blvd","Hill Rd","Market St","Central Ave","Palace Rd"];
|
||||
|
||||
for (int h = 0; h < HOTEL_COUNT; h++)
|
||||
{
|
||||
var (city, ctryCode) = hotelCities[h % hotelCities.Length];
|
||||
int chainIndex = rng.Next(chains.Length);
|
||||
// 20% of hotels are independent (no chain)
|
||||
int? chainId = rng.Next(100) < 20 ? null : chainIds[chains[chainIndex].Code];
|
||||
int star = PickStar();
|
||||
int starId = starIds[star];
|
||||
int ctryId = countryIds[ctryCode];
|
||||
|
||||
string code = $"HTL{h+1:D4}";
|
||||
string name = chainId == null
|
||||
? $"The {city} Hotel"
|
||||
: $"{chains[chainIndex].Name.Split(' ')[0]} {city}";
|
||||
string addr = $"{rng.Next(1, 200)} {streetNames[rng.Next(streetNames.Length)]}";
|
||||
string url = $"https://www.{code.ToLower()}.example.com";
|
||||
|
||||
hotelRows.Add($"({N(chainId)},{ctryId},{starId},{S(code)},{S(name)},{S(addr)},{S("00000")},{S(city)},{S(url)})");
|
||||
|
||||
// Characteristics: 5★ gets all, lower stars get fewer
|
||||
int charCount = star switch { 5 => 11, 4 => 8, 3 => 6, 2 => 4, _ => 3 };
|
||||
var shuffled = charCodes.OrderBy(_ => rng.Next()).Take(charCount).ToArray();
|
||||
// Store char codes for later — we need hotel_id from DB first
|
||||
// Mark with h as placeholder; we'll match after insert
|
||||
foreach (var cc in shuffled)
|
||||
hotelCharRows.Add($"__HOTEL_{h}__,{charIds[cc]}");
|
||||
|
||||
// Rooms: more rooms for higher star hotels
|
||||
int roomCount = star switch { 5 => rng.Next(40, 60), 4 => rng.Next(25, 40),
|
||||
3 => rng.Next(15, 25), 2 => rng.Next(8, 15), _ => rng.Next(5, 10) };
|
||||
|
||||
// Room type distribution per star rating
|
||||
string[] typePool = star switch
|
||||
{
|
||||
5 => ["DOUBLE","DOUBLE","DELUXE","DELUXE","SUITE","SUITE","EXEC","FAMILY"],
|
||||
4 => ["SINGLE","DOUBLE","DOUBLE","DELUXE","SUITE","FAMILY"],
|
||||
3 => ["SINGLE","SINGLE","DOUBLE","DOUBLE","TWIN","FAMILY"],
|
||||
2 => ["SINGLE","SINGLE","DOUBLE","TWIN"],
|
||||
_ => ["SINGLE","SINGLE","DOUBLE"],
|
||||
};
|
||||
|
||||
for (int r = 0; r < roomCount; r++)
|
||||
{
|
||||
int floor = r / 10 + 1;
|
||||
string rnum = $"{floor}{(r % 10 + 1):D2}";
|
||||
string rtype = typePool[rng.Next(typePool.Length)];
|
||||
// Store for later (after we get real hotel IDs from DB)
|
||||
hotelRoomTypes.Add((h, rnum, rtype));
|
||||
}
|
||||
}
|
||||
|
||||
await BulkInsert("hotel",
|
||||
"hotel_chain_id, country_id, star_rating_id, code, name, address, postcode, city, url",
|
||||
hotelRows);
|
||||
|
||||
// Load hotel IDs in order
|
||||
var hotelIds = new List<int>();
|
||||
{
|
||||
await using var cmd = new MySqlCommand("SELECT hotel_id FROM hotel ORDER BY hotel_id", conn);
|
||||
await using var r = await cmd.ExecuteReaderAsync();
|
||||
while (await r.ReadAsync()) hotelIds.Add(r.GetInt32(0));
|
||||
}
|
||||
|
||||
// Now build hotel_room rows with real hotel IDs
|
||||
foreach (var (hIdx, rnum, rtype) in hotelRoomTypes)
|
||||
roomRows.Add($"({hotelIds[hIdx]},{roomTypeIds[rtype]},{S(rnum)},{rnum[0] - '0'})");
|
||||
|
||||
await BulkInsert("hotel_room", "hotel_id, room_type_id, room_number, floor", roomRows);
|
||||
|
||||
// hotel_hotel_characteristic — replace placeholder with real hotel_id
|
||||
var hhcRows = hotelCharRows
|
||||
.Select(row => {
|
||||
var parts = row.Split(',');
|
||||
var hIdx = int.Parse(parts[0].Replace("__HOTEL_", "").Replace("__", ""));
|
||||
var charId = parts[1];
|
||||
return $"({hotelIds[hIdx]},{charId})";
|
||||
})
|
||||
.Distinct()
|
||||
.ToList();
|
||||
await BulkInsert("hotel_hotel_characteristic", "hotel_id, characteristic_id", hhcRows);
|
||||
|
||||
// Load rooms into memory: hotel_id → list of (room_id, room_type_id)
|
||||
var hotelRooms = new Dictionary<int, List<(int RoomId, int RoomTypeId)>>();
|
||||
{
|
||||
await using var cmd = new MySqlCommand("SELECT room_id, hotel_id, room_type_id FROM hotel_room", conn);
|
||||
await using var r = await cmd.ExecuteReaderAsync();
|
||||
while (await r.ReadAsync())
|
||||
{
|
||||
int rid = r.GetInt32(0), hid = r.GetInt32(1), rtid = r.GetInt32(2);
|
||||
if (!hotelRooms.ContainsKey(hid)) hotelRooms[hid] = [];
|
||||
hotelRooms[hid].Add((rid, rtid));
|
||||
}
|
||||
}
|
||||
|
||||
// ── 7. guest ──────────────────────────────────────────────────────────────────
|
||||
Console.WriteLine("[7/8] guest");
|
||||
|
||||
string[] firstNames =
|
||||
[
|
||||
"James","Mary","John","Patricia","Robert","Jennifer","Michael","Linda","William","Barbara",
|
||||
"David","Elizabeth","Richard","Susan","Joseph","Jessica","Thomas","Sarah","Charles","Karen",
|
||||
"Luca","Sofia","Marco","Giulia","Hans","Anna","Klaus","Maria","Pierre","Marie","Jean","Claire",
|
||||
"Miguel","Ana","Carlos","Carmen","Andrei","Ioana","Mihai","Elena","Tomasz","Agnieszka",
|
||||
"Dimitri","Eleni","Mehmet","Fatima","Yuki","Kenji","Haruto","Yuna","Wei","Fang","Li","Mei",
|
||||
"Ahmed","Layla","Omar","Nour","Raj","Priya","Arjun","Ananya","Lucas","Emma","Noah","Olivia",
|
||||
"Ethan","Ava","Mason","Isabella","Liam","Sophia","Oliver","Charlotte","Elijah","Amelia",
|
||||
];
|
||||
|
||||
string[] lastNames =
|
||||
[
|
||||
"Smith","Johnson","Williams","Brown","Jones","Garcia","Miller","Davis","Wilson","Moore",
|
||||
"Taylor","Anderson","Thomas","Jackson","White","Harris","Martin","Thompson","Young","Lee",
|
||||
"Rossi","Ferrari","Esposito","Romano","Müller","Schmidt","Fischer","Weber","Meyer","Wagner",
|
||||
"Dupont","Martin","Bernard","Petit","Dubois","Moreau","Laurent","Simon","Michel","Garcia",
|
||||
"Kowalski","Nowak","Wiśniewski","Wójcik","Kowalczyk","Kamiński","Lewandowski","Zieliński",
|
||||
"Papadopoulos","Georgiou","Yilmaz","Kaya","Tanaka","Sato","Suzuki","Watanabe","Ito","Yamamoto",
|
||||
"Wang","Li","Zhang","Liu","Chen","Yang","Huang","Zhao","Kim","Park","Lee","Choi","Patel",
|
||||
"Singh","Kumar","Sharma","Gupta","Ali","Hassan","Ahmed","Mohamed","Silva","Santos","Oliveira",
|
||||
];
|
||||
|
||||
string[] guestCities =
|
||||
[
|
||||
"London","Paris","Berlin","Madrid","Rome","Amsterdam","Vienna","Zurich","Brussels","Stockholm",
|
||||
"New York","Los Angeles","Chicago","Houston","Phoenix","Toronto","Vancouver","Sydney","Melbourne",
|
||||
"Tokyo","Seoul","Beijing","Shanghai","Singapore","Bangkok","Dubai","Mumbai","Cape Town",
|
||||
"Warsaw","Prague","Budapest","Athens","Istanbul","Lisbon","Oslo","Copenhagen","Helsinki",
|
||||
];
|
||||
|
||||
var countryList = countries.Select(c => c.Code).ToArray();
|
||||
|
||||
var guestRows = new List<string>();
|
||||
for (int g = 0; g < GUEST_COUNT; g++)
|
||||
{
|
||||
string fn = firstNames[rng.Next(firstNames.Length)];
|
||||
string ln = lastNames[rng.Next(lastNames.Length)];
|
||||
string name = $"{fn} {ln}";
|
||||
string email = $"{fn.ToLower()}.{ln.ToLower()}{rng.Next(100, 999)}@example.com";
|
||||
string city = guestCities[rng.Next(guestCities.Length)];
|
||||
int ctryId = countryIds[countryList[rng.Next(countryList.Length)]];
|
||||
guestRows.Add($"({ctryId},{S(name)},{S(email)},{S(city)})");
|
||||
}
|
||||
await BulkInsert("guest", "country_id, name, email, city", guestRows);
|
||||
|
||||
var guestIdMin = (int)await ExecScalar("SELECT MIN(guest_id) FROM guest");
|
||||
var guestIdMax = (int)await ExecScalar("SELECT MAX(guest_id) FROM guest");
|
||||
|
||||
// ── 8. booking + room_booking ─────────────────────────────────────────────────
|
||||
Console.WriteLine("[8/8] booking + room_booking");
|
||||
|
||||
var dateStart = new DateTime(2022, 1, 1);
|
||||
var dateEnd = new DateTime(2025, 12, 31);
|
||||
int dateRange = (dateEnd - dateStart).Days;
|
||||
|
||||
// Seasonal weight: month → weight (higher = more bookings)
|
||||
int[] monthWeight = [0, 6, 5, 7, 9, 10, 14, 16, 15, 11, 9, 7, 11]; // Jan-Dec
|
||||
|
||||
DateTime RandomCheckin()
|
||||
{
|
||||
// Rejection sampling to simulate seasonal distribution
|
||||
while (true)
|
||||
{
|
||||
var d = dateStart.AddDays(rng.Next(dateRange));
|
||||
if (rng.Next(16) < monthWeight[d.Month]) return d;
|
||||
}
|
||||
}
|
||||
|
||||
int RandomNights() => rng.Next(100) switch
|
||||
{
|
||||
< 30 => 1,
|
||||
< 55 => 2,
|
||||
< 75 => 3,
|
||||
< 85 => 4,
|
||||
< 92 => 5,
|
||||
< 96 => rng.Next(6, 8),
|
||||
_ => rng.Next(8, 15),
|
||||
};
|
||||
|
||||
string RandomStatus() => rng.Next(100) switch
|
||||
{
|
||||
< 80 => "completed",
|
||||
< 90 => "confirmed",
|
||||
< 97 => "cancelled",
|
||||
_ => "no_show",
|
||||
};
|
||||
|
||||
// 90% single room, 8% two rooms, 2% three rooms
|
||||
int RandomRoomCount() => rng.Next(100) switch { < 90 => 1, < 98 => 2, _ => 3 };
|
||||
|
||||
var bookingRows = new List<string>();
|
||||
var roomBookingRows = new List<string>();
|
||||
|
||||
// We need booking_id for room_booking FK.
|
||||
// Strategy: flush bookings in batches, then read back the auto-increment IDs,
|
||||
// then insert room_bookings for that batch.
|
||||
|
||||
int bookingsDone = 0;
|
||||
while (bookingsDone < BOOKING_COUNT)
|
||||
{
|
||||
int batchSize = Math.Min(BATCH, BOOKING_COUNT - bookingsDone);
|
||||
bookingRows.Clear();
|
||||
roomBookingRows.Clear();
|
||||
|
||||
for (int b = 0; b < batchSize; b++)
|
||||
{
|
||||
int guestId = guestIdMin + rng.Next(guestIdMax - guestIdMin + 1);
|
||||
int hotelId = hotelIds[rng.Next(hotelIds.Count)];
|
||||
DateTime checkin = RandomCheckin();
|
||||
int nights = RandomNights();
|
||||
DateTime checkout = checkin.AddDays(nights);
|
||||
string status = RandomStatus();
|
||||
DateTime created = checkin.AddDays(-rng.Next(1, 180));
|
||||
|
||||
bookingRows.Add($"({guestId},{hotelId},{D(checkin)},{D(checkout)},{S(status)},{DT(created)})");
|
||||
}
|
||||
|
||||
// Insert bookings and get the first inserted ID
|
||||
long firstId = await ExecScalar("SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema='hotel_reservations' AND table_name='booking'");
|
||||
await Exec($"INSERT INTO booking (guest_id, hotel_id, date_from, date_to, status, created_at) VALUES {string.Join(',', bookingRows)}");
|
||||
|
||||
// Re-derive checkin/nights from the same rng sequence is impossible after the fact,
|
||||
// so re-parse from inserted rows to build room_bookings
|
||||
// Simpler: re-read the batch back
|
||||
await using (var cmd = new MySqlCommand(
|
||||
$"SELECT booking_id, hotel_id, date_from, date_to, status FROM booking WHERE booking_id >= {firstId} ORDER BY booking_id", conn))
|
||||
await using (var reader = await cmd.ExecuteReaderAsync())
|
||||
{
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
long bookingId = reader.GetInt64(0);
|
||||
int hid = reader.GetInt32(1);
|
||||
DateTime dfrom = reader.GetDateTime(2);
|
||||
DateTime dto = reader.GetDateTime(3);
|
||||
string status = reader.GetString(4);
|
||||
int nights = (dto - dfrom).Days;
|
||||
|
||||
if (!hotelRooms.ContainsKey(hid) || hotelRooms[hid].Count == 0) continue;
|
||||
|
||||
// Skip room_bookings for cancelled/no_show sometimes
|
||||
if (status == "cancelled" && rng.Next(100) < 60) continue;
|
||||
if (status == "no_show" && rng.Next(100) < 30) continue;
|
||||
|
||||
int roomCount = RandomRoomCount();
|
||||
var available = hotelRooms[hid].OrderBy(_ => rng.Next()).Take(roomCount).ToList();
|
||||
|
||||
foreach (var (roomId, roomTypeId) in available)
|
||||
{
|
||||
int ratePeriodId = monthToRatePeriodId[dfrom.Month];
|
||||
decimal nightly = rateMap[(roomTypeId, ratePeriodId)];
|
||||
decimal total = Math.Round(nightly * nights, 2);
|
||||
roomBookingRows.Add($"({bookingId},{roomId},{D(dfrom)},{D(dto)},{nightly},{total})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (roomBookingRows.Count > 0)
|
||||
await Exec($"INSERT INTO room_booking (booking_id, room_id, date_from, date_to, nightly_rate, total_amount) VALUES {string.Join(',', roomBookingRows)}");
|
||||
|
||||
bookingsDone += batchSize;
|
||||
if (bookingsDone % 10_000 == 0)
|
||||
Console.WriteLine($" bookings: {bookingsDone:N0} / {BOOKING_COUNT:N0}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("── Row counts ───────────────────────────────");
|
||||
foreach (var t in new[]{"hotel_chain","country","star_rating","hotel_characteristic",
|
||||
"room_type","rate_period","period_room_rate","hotel",
|
||||
"hotel_room","hotel_hotel_characteristic","guest","booking","room_booking"})
|
||||
{
|
||||
long cnt = await ExecScalar($"SELECT COUNT(*) FROM {t}");
|
||||
Console.WriteLine($" {t,-35} {cnt,10:N0}");
|
||||
}
|
||||
Console.WriteLine("Done.");
|
||||
133
sql/datamart_schema.sql
Normal file
133
sql/datamart_schema.sql
Normal file
@@ -0,0 +1,133 @@
|
||||
-- =============================================================================
|
||||
-- HOTEL RESERVATIONS — DATA MART (STAR SCHEMA)
|
||||
-- Target: Oracle (university lab schema)
|
||||
-- Based on A.24 Revenue Data Mart — Dimensional Modelling by Example
|
||||
-- =============================================================================
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- DIMENSION TABLES
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
-- YYYYMMDD integer key — cheap date range predicates, no JOIN to calendar needed
|
||||
CREATE TABLE DIM_DATE (
|
||||
date_key NUMBER(8,0) NOT NULL,
|
||||
full_date DATE NOT NULL,
|
||||
year NUMBER(4,0) NOT NULL,
|
||||
quarter NUMBER(1,0) NOT NULL,
|
||||
month NUMBER(2,0) NOT NULL,
|
||||
month_name VARCHAR2(10) NOT NULL,
|
||||
week_number NUMBER(2,0) NOT NULL,
|
||||
day_of_month NUMBER(2,0) NOT NULL,
|
||||
day_name VARCHAR2(10) NOT NULL,
|
||||
is_weekend NUMBER(1,0) NOT NULL,
|
||||
is_business_day NUMBER(1,0) NOT NULL,
|
||||
season VARCHAR2(10) NOT NULL, -- Peak / High / Low / Off
|
||||
CONSTRAINT pk_dim_date PRIMARY KEY (date_key),
|
||||
CONSTRAINT ck_dim_date_wknd CHECK (is_weekend IN (0,1)),
|
||||
CONSTRAINT ck_dim_date_bday CHECK (is_business_day IN (0,1))
|
||||
);
|
||||
|
||||
CREATE TABLE DIM_COUNTRY (
|
||||
country_key NUMBER(10,0) GENERATED ALWAYS AS IDENTITY,
|
||||
country_id NUMBER(10,0) NOT NULL,
|
||||
code CHAR(2) NOT NULL,
|
||||
name VARCHAR2(100) NOT NULL,
|
||||
currency VARCHAR2(10) NOT NULL,
|
||||
CONSTRAINT pk_dim_country PRIMARY KEY (country_key),
|
||||
CONSTRAINT uq_dim_cntry_id UNIQUE (country_id)
|
||||
);
|
||||
|
||||
CREATE TABLE DIM_STAR_RATING (
|
||||
star_rating_key NUMBER(10,0) GENERATED ALWAYS AS IDENTITY,
|
||||
star_rating_id NUMBER(10,0) NOT NULL,
|
||||
code NUMBER(1,0) NOT NULL,
|
||||
description VARCHAR2(20) NOT NULL,
|
||||
CONSTRAINT pk_dim_star PRIMARY KEY (star_rating_key),
|
||||
CONSTRAINT uq_dim_star_id UNIQUE (star_rating_id)
|
||||
);
|
||||
|
||||
CREATE TABLE DIM_HOTEL_CHAIN (
|
||||
hotel_chain_key NUMBER(10,0) GENERATED ALWAYS AS IDENTITY,
|
||||
hotel_chain_id NUMBER(10,0) NOT NULL,
|
||||
code VARCHAR2(10) NOT NULL,
|
||||
name VARCHAR2(100) NOT NULL,
|
||||
CONSTRAINT pk_dim_chain PRIMARY KEY (hotel_chain_key),
|
||||
CONSTRAINT uq_dim_chain_id UNIQUE (hotel_chain_id)
|
||||
);
|
||||
|
||||
CREATE TABLE DIM_HOTEL (
|
||||
hotel_key NUMBER(10,0) GENERATED ALWAYS AS IDENTITY,
|
||||
hotel_id NUMBER(10,0) NOT NULL,
|
||||
hotel_chain_key NUMBER(10,0),
|
||||
country_key NUMBER(10,0) NOT NULL,
|
||||
star_rating_key NUMBER(10,0) NOT NULL,
|
||||
code VARCHAR2(20) NOT NULL,
|
||||
name VARCHAR2(150) NOT NULL,
|
||||
city VARCHAR2(100) NOT NULL,
|
||||
CONSTRAINT pk_dim_hotel PRIMARY KEY (hotel_key),
|
||||
CONSTRAINT uq_dim_hotel_id UNIQUE (hotel_id),
|
||||
CONSTRAINT fk_dh_chain FOREIGN KEY (hotel_chain_key) REFERENCES DIM_HOTEL_CHAIN (hotel_chain_key),
|
||||
CONSTRAINT fk_dh_country FOREIGN KEY (country_key) REFERENCES DIM_COUNTRY (country_key),
|
||||
CONSTRAINT fk_dh_star FOREIGN KEY (star_rating_key) REFERENCES DIM_STAR_RATING (star_rating_key)
|
||||
);
|
||||
|
||||
CREATE TABLE DIM_ROOM (
|
||||
room_key NUMBER(10,0) GENERATED ALWAYS AS IDENTITY,
|
||||
room_id NUMBER(10,0) NOT NULL,
|
||||
hotel_key NUMBER(10,0) NOT NULL,
|
||||
room_number VARCHAR2(10) NOT NULL,
|
||||
floor NUMBER(3,0) NOT NULL,
|
||||
room_type_code VARCHAR2(20) NOT NULL,
|
||||
room_type_desc VARCHAR2(100) NOT NULL,
|
||||
smoking_yn NUMBER(1,0) NOT NULL,
|
||||
standard_rate NUMBER(10,2) NOT NULL,
|
||||
CONSTRAINT pk_dim_room PRIMARY KEY (room_key),
|
||||
CONSTRAINT uq_dim_room_id UNIQUE (room_id),
|
||||
CONSTRAINT fk_dr_hotel FOREIGN KEY (hotel_key) REFERENCES DIM_HOTEL (hotel_key),
|
||||
CONSTRAINT ck_dim_room_smk CHECK (smoking_yn IN (0,1))
|
||||
);
|
||||
|
||||
CREATE TABLE DIM_GUEST (
|
||||
guest_key NUMBER(10,0) GENERATED ALWAYS AS IDENTITY,
|
||||
guest_id NUMBER(10,0) NOT NULL,
|
||||
country_key NUMBER(10,0),
|
||||
name VARCHAR2(150) NOT NULL,
|
||||
city VARCHAR2(100),
|
||||
CONSTRAINT pk_dim_guest PRIMARY KEY (guest_key),
|
||||
CONSTRAINT uq_dim_guest_id UNIQUE (guest_id),
|
||||
CONSTRAINT fk_dg_country FOREIGN KEY (country_key) REFERENCES DIM_COUNTRY (country_key)
|
||||
);
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- FACT TABLE
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
-- Grain: one row per room_booking
|
||||
-- Revenue measures: nightly_rate, total_amount, nights_stayed
|
||||
CREATE TABLE FACT_ROOM_BOOKING (
|
||||
fact_id NUMBER(10,0) GENERATED ALWAYS AS IDENTITY,
|
||||
-- foreign keys
|
||||
hotel_key NUMBER(10,0) NOT NULL,
|
||||
hotel_chain_key NUMBER(10,0),
|
||||
room_key NUMBER(10,0) NOT NULL,
|
||||
guest_key NUMBER(10,0) NOT NULL,
|
||||
country_key NUMBER(10,0),
|
||||
star_rating_key NUMBER(10,0) NOT NULL,
|
||||
checkin_date_key NUMBER(8,0) NOT NULL,
|
||||
checkout_date_key NUMBER(8,0) NOT NULL,
|
||||
-- degenerate dimensions
|
||||
booking_status VARCHAR2(20) NOT NULL,
|
||||
-- measures
|
||||
nights_stayed NUMBER(4,0) NOT NULL,
|
||||
nightly_rate NUMBER(10,2) NOT NULL,
|
||||
total_amount NUMBER(12,2) NOT NULL,
|
||||
CONSTRAINT pk_fact_rb PRIMARY KEY (fact_id),
|
||||
CONSTRAINT fk_frb_hotel FOREIGN KEY (hotel_key) REFERENCES DIM_HOTEL (hotel_key),
|
||||
CONSTRAINT fk_frb_chain FOREIGN KEY (hotel_chain_key) REFERENCES DIM_HOTEL_CHAIN (hotel_chain_key),
|
||||
CONSTRAINT fk_frb_room FOREIGN KEY (room_key) REFERENCES DIM_ROOM (room_key),
|
||||
CONSTRAINT fk_frb_guest FOREIGN KEY (guest_key) REFERENCES DIM_GUEST (guest_key),
|
||||
CONSTRAINT fk_frb_country FOREIGN KEY (country_key) REFERENCES DIM_COUNTRY (country_key),
|
||||
CONSTRAINT fk_frb_star FOREIGN KEY (star_rating_key) REFERENCES DIM_STAR_RATING (star_rating_key),
|
||||
CONSTRAINT fk_frb_checkin FOREIGN KEY (checkin_date_key) REFERENCES DIM_DATE (date_key),
|
||||
CONSTRAINT fk_frb_checkout FOREIGN KEY (checkout_date_key) REFERENCES DIM_DATE (date_key)
|
||||
);
|
||||
154
sql/schema.sql
Normal file
154
sql/schema.sql
Normal file
@@ -0,0 +1,154 @@
|
||||
CREATE DATABASE IF NOT EXISTS hotel_reservations
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE hotel_reservations;
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- LOOKUP / REFERENCE TABLES
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE hotel_chain (
|
||||
hotel_chain_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code VARCHAR(10) NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
PRIMARY KEY (hotel_chain_id),
|
||||
UNIQUE KEY uq_chain_code (code)
|
||||
);
|
||||
|
||||
CREATE TABLE country (
|
||||
country_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code CHAR(2) NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
currency VARCHAR(10) NOT NULL,
|
||||
PRIMARY KEY (country_id),
|
||||
UNIQUE KEY uq_country_code (code)
|
||||
);
|
||||
|
||||
CREATE TABLE star_rating (
|
||||
star_rating_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code TINYINT UNSIGNED NOT NULL,
|
||||
description VARCHAR(20) NOT NULL,
|
||||
PRIMARY KEY (star_rating_id),
|
||||
UNIQUE KEY uq_star_code (code)
|
||||
);
|
||||
|
||||
CREATE TABLE hotel_characteristic (
|
||||
characteristic_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code VARCHAR(20) NOT NULL,
|
||||
description VARCHAR(100) NOT NULL,
|
||||
PRIMARY KEY (characteristic_id),
|
||||
UNIQUE KEY uq_char_code (code)
|
||||
);
|
||||
|
||||
CREATE TABLE room_type (
|
||||
room_type_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code VARCHAR(20) NOT NULL,
|
||||
description VARCHAR(100) NOT NULL,
|
||||
standard_rate DECIMAL(10,2) NOT NULL,
|
||||
smoking_yn BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
PRIMARY KEY (room_type_id),
|
||||
UNIQUE KEY uq_room_type_code (code)
|
||||
);
|
||||
|
||||
CREATE TABLE rate_period (
|
||||
rate_period_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code VARCHAR(20) NOT NULL,
|
||||
description VARCHAR(50) NOT NULL,
|
||||
month_from TINYINT UNSIGNED NOT NULL,
|
||||
month_to TINYINT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (rate_period_id),
|
||||
UNIQUE KEY uq_rate_period_code (code)
|
||||
);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- CORE ENTITIES
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE hotel (
|
||||
hotel_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
hotel_chain_id INT UNSIGNED,
|
||||
country_id INT UNSIGNED NOT NULL,
|
||||
star_rating_id INT UNSIGNED NOT NULL,
|
||||
code VARCHAR(20) NOT NULL,
|
||||
name VARCHAR(150) NOT NULL,
|
||||
address VARCHAR(200),
|
||||
postcode VARCHAR(20),
|
||||
city VARCHAR(100) NOT NULL,
|
||||
url VARCHAR(200),
|
||||
PRIMARY KEY (hotel_id),
|
||||
UNIQUE KEY uq_hotel_code (code),
|
||||
CONSTRAINT fk_hotel_chain FOREIGN KEY (hotel_chain_id) REFERENCES hotel_chain (hotel_chain_id),
|
||||
CONSTRAINT fk_hotel_country FOREIGN KEY (country_id) REFERENCES country (country_id),
|
||||
CONSTRAINT fk_hotel_star FOREIGN KEY (star_rating_id) REFERENCES star_rating (star_rating_id)
|
||||
);
|
||||
|
||||
CREATE TABLE hotel_room (
|
||||
room_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
hotel_id INT UNSIGNED NOT NULL,
|
||||
room_type_id INT UNSIGNED NOT NULL,
|
||||
room_number VARCHAR(10) NOT NULL,
|
||||
floor TINYINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (room_id),
|
||||
UNIQUE KEY uq_hotel_room (hotel_id, room_number),
|
||||
CONSTRAINT fk_room_hotel FOREIGN KEY (hotel_id) REFERENCES hotel (hotel_id),
|
||||
CONSTRAINT fk_room_type FOREIGN KEY (room_type_id) REFERENCES room_type (room_type_id)
|
||||
);
|
||||
|
||||
CREATE TABLE guest (
|
||||
guest_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
country_id INT UNSIGNED,
|
||||
name VARCHAR(150) NOT NULL,
|
||||
email VARCHAR(150),
|
||||
address VARCHAR(200),
|
||||
city VARCHAR(100),
|
||||
PRIMARY KEY (guest_id),
|
||||
CONSTRAINT fk_guest_country FOREIGN KEY (country_id) REFERENCES country (country_id)
|
||||
);
|
||||
|
||||
CREATE TABLE booking (
|
||||
booking_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
guest_id INT UNSIGNED NOT NULL,
|
||||
hotel_id INT UNSIGNED NOT NULL,
|
||||
date_from DATE NOT NULL,
|
||||
date_to DATE NOT NULL,
|
||||
status ENUM('confirmed', 'cancelled', 'completed', 'no_show') NOT NULL DEFAULT 'confirmed',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (booking_id),
|
||||
CONSTRAINT fk_booking_guest FOREIGN KEY (guest_id) REFERENCES guest (guest_id),
|
||||
CONSTRAINT fk_booking_hotel FOREIGN KEY (hotel_id) REFERENCES hotel (hotel_id)
|
||||
);
|
||||
|
||||
CREATE TABLE room_booking (
|
||||
room_booking_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
booking_id INT UNSIGNED NOT NULL,
|
||||
room_id INT UNSIGNED NOT NULL,
|
||||
date_from DATE NOT NULL,
|
||||
date_to DATE NOT NULL,
|
||||
nightly_rate DECIMAL(10,2) NOT NULL,
|
||||
total_amount DECIMAL(10,2) NOT NULL,
|
||||
PRIMARY KEY (room_booking_id),
|
||||
CONSTRAINT fk_rb_booking FOREIGN KEY (booking_id) REFERENCES booking (booking_id),
|
||||
CONSTRAINT fk_rb_room FOREIGN KEY (room_id) REFERENCES hotel_room (room_id)
|
||||
);
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- JUNCTION TABLES
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE hotel_hotel_characteristic (
|
||||
hotel_id INT UNSIGNED NOT NULL,
|
||||
characteristic_id INT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (hotel_id, characteristic_id),
|
||||
CONSTRAINT fk_hhc_hotel FOREIGN KEY (hotel_id) REFERENCES hotel (hotel_id),
|
||||
CONSTRAINT fk_hhc_char FOREIGN KEY (characteristic_id) REFERENCES hotel_characteristic (characteristic_id)
|
||||
);
|
||||
|
||||
CREATE TABLE period_room_rate (
|
||||
room_type_id INT UNSIGNED NOT NULL,
|
||||
rate_period_id INT UNSIGNED NOT NULL,
|
||||
rate DECIMAL(10,2) NOT NULL,
|
||||
PRIMARY KEY (room_type_id, rate_period_id),
|
||||
CONSTRAINT fk_prr_type FOREIGN KEY (room_type_id) REFERENCES room_type (room_type_id),
|
||||
CONSTRAINT fk_prr_period FOREIGN KEY (rate_period_id) REFERENCES rate_period (rate_period_id)
|
||||
);
|
||||
Reference in New Issue
Block a user