541 lines
24 KiB
C#
541 lines
24 KiB
C#
#:package MySqlConnector@2.3.7
|
|
|
|
using System.Text;
|
|
using System.Globalization;
|
|
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.");
|
|
await new MySqlCommand("SET foreign_key_checks=0, unique_checks=0", conn).ExecuteNonQueryAsync();
|
|
|
|
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 is IFormattable f) ? f.ToString(null, CultureInfo.InvariantCulture) : 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)},{N(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]},{N(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)})");
|
|
}
|
|
|
|
await Exec($"INSERT INTO booking (guest_id, hotel_id, date_from, date_to, status, created_at) VALUES {string.Join(',', bookingRows)}");
|
|
long firstId = await ExecScalar("SELECT LAST_INSERT_ID()");
|
|
|
|
// 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)},{N(nightly)},{N(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.");
|