| 9 | | == What we cover in this phase == |
| 10 | | * Complex reports using JOINs, GROUP BY and aggregate functions |
| 11 | | * Stored procedures for reusable database logic |
| 12 | | * Views for simplified access to frequently needed reports |
| 13 | | * Relational Algebra (theoretical foundation behind SQL) |
| 14 | | * Grouping and filtering techniques |
| 15 | | * Cost analysis and capacity utilization examples |
| 16 | | |
| 17 | | == Implementation / Examples == |
| 18 | | |
| 19 | | === 1) Complex Reports === |
| 20 | | Complex reports combine data from multiple tables and return analytical information that is useful for managing weddings. |
| 21 | | In our Wedding Planner system, reports are useful for guest status tracking, cost breakdown, scheduling and venue usage. |
| 22 | | |
| 23 | | ==== Example 1: Total guests and RSVP status per wedding ==== |
| 24 | | This query returns the total number of guests per wedding, and how many RSVPs are CONFIRMED vs CANCELLED. |
| 25 | | |
| 26 | | {{{ |
| 27 | | SELECT |
| 28 | | w.wedding_id, |
| 29 | | w.date AS wedding_date, |
| 30 | | COUNT(DISTINCT g.guest_id) AS total_guests, |
| 31 | | COUNT(CASE WHEN r.status = 'CONFIRMED' THEN 1 END) AS confirmed_guests, |
| 32 | | COUNT(CASE WHEN r.status = 'CANCELLED' THEN 1 END) AS cancelled_guests |
| 33 | | FROM wedding w |
| 34 | | LEFT JOIN guest g ON w.wedding_id = g.wedding_id |
| 35 | | LEFT JOIN event e ON w.wedding_id = e.wedding_id |
| 36 | | LEFT JOIN event_rsvp r ON e.event_id = r.event_id AND g.guest_id = r.guest_id |
| 37 | | GROUP BY w.wedding_id, w.date; |
| 38 | | }}} |
| 39 | | |
| 40 | | *Purpose:* Helps the wedding owner see how many guests are invited and their RSVP status. |
| 41 | | |
| 42 | | ==== Example 2: Costs per wedding (venue + band + photographer) ==== |
| 43 | | This report computes the cost of a wedding by summing the cost of booked providers. |
| 44 | | |
| 45 | | {{{ |
| 46 | | SELECT |
| 47 | | w.wedding_id, |
| 48 | | w.date, |
| 49 | | COALESCE(SUM(vb.price), 0) AS venue_cost, |
| 50 | | COALESCE(SUM(EXTRACT(EPOCH FROM (bb.end_time - bb.start_time))/3600 * b.price_per_hour), 0) AS band_cost, |
| 51 | | COALESCE(SUM(EXTRACT(EPOCH FROM (pb.end_time - pb.start_time))/3600 * p.price_per_hour), 0) AS photographer_cost |
| 52 | | FROM wedding w |
| 53 | | LEFT JOIN venue_booking vb ON w.wedding_id = vb.wedding_id |
| 54 | | LEFT JOIN band_booking bb ON w.wedding_id = bb.wedding_id |
| 55 | | LEFT JOIN band b ON bb.band_id = b.band_id |
| 56 | | LEFT JOIN photographer_booking pb ON w.wedding_id = pb.wedding_id |
| 57 | | LEFT JOIN photographer p ON pb.photographer_id = p.photographer_id |
| 58 | | GROUP BY w.wedding_id, w.date; |
| 59 | | }}} |
| 60 | | |
| 61 | | *Purpose:* Gives a clear financial summary for budgeting and comparing providers. |
| 62 | | |
| 63 | | ==== Example 3: Venue utilization (capacity report) ==== |
| 64 | | This query checks how many guests are seated, and how many free seats are still available at the booked venue. |
| 65 | | |
| 66 | | {{{ |
| 67 | | SELECT |
| 68 | | v.name AS venue_name, |
| 69 | | v.capacity, |
| 70 | | w.wedding_id, |
| 71 | | COUNT(a.guest_id) AS seated_guests, |
| 72 | | (v.capacity - COUNT(a.guest_id)) AS free_seats |
| 73 | | FROM venue v |
| 74 | | JOIN venue_booking vb ON v.venue_id = vb.venue_id |
| 75 | | JOIN wedding w ON vb.wedding_id = w.wedding_id |
| 76 | | LEFT JOIN attendance a ON a.event_id IN ( |
| 77 | | SELECT event_id FROM event WHERE wedding_id = w.wedding_id |
| 78 | | ) |
| 79 | | GROUP BY v.name, v.capacity, w.wedding_id; |
| 80 | | }}} |
| 81 | | |
| 82 | | *Purpose:* Useful to see if the wedding has exceeded the venue capacity or still has room. |
| 83 | | |
| 84 | | ==== Example 4: Temporal distribution of events ==== |
| 85 | | This query returns the schedule of events ordered by date and time. |
| 86 | | |
| 87 | | {{{ |
| 88 | | SELECT |
| 89 | | w.wedding_id, |
| 90 | | e.event_type, |
| 91 | | e.date, |
| 92 | | e.start_time, |
| 93 | | e.end_time, |
| 94 | | EXTRACT(EPOCH FROM (e.end_time - e.start_time))/3600 AS duration_hours |
| 95 | | FROM wedding w |
| 96 | | JOIN event e ON w.wedding_id = e.wedding_id |
| 97 | | ORDER BY e.date, e.start_time; |
| 98 | | }}} |
| 99 | | |
| 100 | | *Purpose:* Helps planning timelines and checking event overlaps in the schedule. |
| 101 | | |
| 102 | | === 2) Stored Procedures === |
| 103 | | Stored procedures allow us to encapsulate database logic into reusable functions that can be called when needed. |
| 104 | | They reduce repetition and help organize logic for reporting and automation. |
| 105 | | |
| 106 | | ==== Example 1: Total wedding cost ==== |
| 107 | | This procedure calculates the full total cost of a wedding (venue + band + photographer). |
| 108 | | |
| 109 | | {{{ |
| 110 | | CREATE OR REPLACE PROCEDURE wedding_total_cost( |
| 111 | | IN p_wedding_id INT, |
| 112 | | OUT total_cost NUMERIC |
| 113 | | ) |
| 114 | | LANGUAGE plpgsql |
| 115 | | AS $$ |
| 116 | | BEGIN |
| 117 | | SELECT |
| 118 | | COALESCE(SUM(vb.price),0) |
| 119 | | + COALESCE(SUM(EXTRACT(EPOCH FROM (bb.end_time - bb.start_time))/3600 * b.price_per_hour),0) |
| 120 | | + COALESCE(SUM(EXTRACT(EPOCH FROM (pb.end_time - pb.start_time))/3600 * p.price_per_hour),0) |
| 121 | | INTO total_cost |
| 122 | | FROM wedding w |
| 123 | | LEFT JOIN venue_booking vb ON w.wedding_id = vb.wedding_id |
| 124 | | LEFT JOIN band_booking bb ON w.wedding_id = bb.wedding_id |
| 125 | | LEFT JOIN band b ON bb.band_id = b.band_id |
| 126 | | LEFT JOIN photographer_booking pb ON w.wedding_id = pb.wedding_id |
| 127 | | LEFT JOIN photographer p ON pb.photographer_id = p.photographer_id |
| 128 | | WHERE w.wedding_id = p_wedding_id; |
| 129 | | END; |
| 130 | | $$; |
| 131 | | }}} |
| 132 | | |
| 133 | | *Purpose:* Gives one clear number for budgeting and reports. |
| | 8 | SCENARIO 1: BUDGET VS ACTUAL EXPENDITURE ANALYSIS |
| | 9 | |
| | 10 | 1.1 Objective |
| | 11 | |
| | 12 | Perform comprehensive financial analysis comparing budgeted wedding costs against actual vendor expenditures. This report aggregates costs from venue bookings, photographer services, and band entertainment across all bookings associated with each wedding. The analysis enables identification of budget overruns, cost variances, and provides financial reconciliation at the wedding level. |
| | 13 | |
| | 14 | 1.2 SQL Query Implementation |
| | 15 | |
| | 16 | SELECT |
| | 17 | w.wedding_id, |
| | 18 | u.first_name || ' ' || u.last_name AS organizer_name, |
| | 19 | w.date AS wedding_date, |
| | 20 | w.budget AS budgeted_amount, |
| | 21 | COALESCE(SUM(vb.price), 0) AS venue_cost, |
| | 22 | COALESCE(SUM(EXTRACT(EPOCH FROM (pb.end_time - pb.start_time))/3600 * p.price_per_hour), 0) AS photographer_cost, |
| | 23 | COALESCE(SUM(EXTRACT(EPOCH FROM (bb.end_time - bb.start_time))/3600 * b.price_per_hour), 0) AS band_cost, |
| | 24 | COALESCE(SUM(vb.price), 0) |
| | 25 | + COALESCE(SUM(EXTRACT(EPOCH FROM (pb.end_time - pb.start_time))/3600 * p.price_per_hour), 0) |
| | 26 | + COALESCE(SUM(EXTRACT(EPOCH FROM (bb.end_time - bb.start_time))/3600 * b.price_per_hour), 0) AS total_actual_cost, |
| | 27 | w.budget - (COALESCE(SUM(vb.price), 0) |
| | 28 | + COALESCE(SUM(EXTRACT(EPOCH FROM (pb.end_time - pb.start_time))/3600 * p.price_per_hour), 0) |
| | 29 | + COALESCE(SUM(EXTRACT(EPOCH FROM (bb.end_time - bb.start_time))/3600 * b.price_per_hour), 0)) AS remaining_budget, |
| | 30 | ROUND(((w.budget - (COALESCE(SUM(vb.price), 0) |
| | 31 | + COALESCE(SUM(EXTRACT(EPOCH FROM (pb.end_time - pb.start_time))/3600 * p.price_per_hour), 0) |
| | 32 | + COALESCE(SUM(EXTRACT(EPOCH FROM (bb.end_time - bb.start_time))/3600 * b.price_per_hour), 0))) / w.budget) * 100, 2) AS budget_variance_percent |
| | 33 | FROM wedding w |
| | 34 | LEFT JOIN "user" u ON w.user_id = u.user_id |
| | 35 | LEFT JOIN venue_booking vb ON w.wedding_id = vb.wedding_id |
| | 36 | LEFT JOIN photographer_booking pb ON w.wedding_id = pb.wedding_id |
| | 37 | LEFT JOIN photographer p ON pb.photographer_id = p.photographer_id |
| | 38 | LEFT JOIN band_booking bb ON w.wedding_id = bb.wedding_id |
| | 39 | LEFT JOIN band b ON bb.band_id = b.band_id |
| | 40 | GROUP BY w.wedding_id, u.first_name, u.last_name, w.date, w.budget |
| | 41 | ORDER BY w.wedding_id; |
| | 42 | |
| | 43 | |
| | 44 | Query Complexity Analysis: |
| | 45 | |
| | 46 | ● Join Count: 7 tables (Wedding, User, Venue_Booking, Photographer_Booking, Photographer, Band_Booking, Band) |
| | 47 | ● Aggregate Functions: SUM(), COALESCE(), EXTRACT(), ROUND() |
| | 48 | ● Grouping Columns: 5 (wedding_id, user first_name, last_name, date, budget) |
| | 49 | ● Temporal Calculation: EXTRACT(EPOCH ...) converts time duration to hours for hourly rate multiplication |
| | 50 | ● |
| | 51 | 1.3 Relational Algebra Expression |
| | 52 | π(w.wedding_id, u.fname, u.lname, w.date, w.budget, |
| | 53 | SUM(vb.price), SUM((pb.end - pb.start) * p.rate), SUM((bb.end - bb.start) * b.rate)) ( |
| | 54 | |
| | 55 | γ(wedding_id, SUM(venue_cost), SUM(photo_cost), SUM(band_cost)) ( |
| | 56 | |
| | 57 | ρ(vb.price → venue_cost, (pb.end - pb.start) * p.price_per_hour → photo_cost, |
| | 58 | (bb.end - bb.start) * b.price_per_hour → band_cost) ( |
| | 59 | |
| | 60 | (((Wedding ⟕ User) ⟕ Venue_Booking) ⟕ Photographer_Booking) ⟕ Photographer) |
| | 61 | ⟕ Band_Booking) ⟕ Band ) ) ) |
| | 62 | |
| | 63 | Notation: |
| | 64 | |
| | 65 | ● π = Projection (SELECT clause) |
| | 66 | ● γ = Grouping and aggregation (GROUP BY) |
| | 67 | ● ⟕ = Left outer join (LEFT JOIN) |
| | 68 | ● ρ = Rename operation (AS) |
| | 69 | ● × = Cartesian product (implicit in joins) |
| | 70 | |
| | 71 | Interpretation: |
| | 72 | |
| | 73 | The expression chains seven relations through left outer joins to preserve all weddings regardless of booking status. The grouping operation aggregates cost components by wedding_id, enabling dimensional analysis of expenditures. |
| | 74 | |
| | 75 | 1.4 PostgreSQL Stored Procedure |
| | 76 | |
| | 77 | CREATE OR REPLACE PROCEDURE budget_variance_report( |
| | 78 | IN p_wedding_id INT DEFAULT NULL, |
| | 79 | IN p_start_date DATE DEFAULT '2020-01-01', |
| | 80 | IN p_end_date DATE DEFAULT '2099-12-31' |
| | 81 | ) |
| | 82 | LANGUAGE plpgsql |
| | 83 | AS $$ |
| | 84 | DECLARE |
| | 85 | v_record RECORD; |
| | 86 | v_venue_cost NUMERIC; |
| | 87 | v_photographer_cost NUMERIC; |
| | 88 | v_band_cost NUMERIC; |
| | 89 | v_total_cost NUMERIC; |
| | 90 | v_remaining NUMERIC; |
| | 91 | v_variance NUMERIC; |
| | 92 | BEGIN |
| | 93 | -- Create temporary table for results |
| | 94 | CREATE TEMP TABLE budget_variance_results ( |
| | 95 | wedding_id INTEGER, |
| | 96 | organizer_name VARCHAR, |
| | 97 | wedding_date DATE, |
| | 98 | budgeted_amount NUMERIC, |
| | 99 | venue_cost NUMERIC, |
| | 100 | photographer_cost NUMERIC, |
| | 101 | band_cost NUMERIC, |
| | 102 | total_actual_cost NUMERIC, |
| | 103 | remaining_budget NUMERIC, |
| | 104 | variance_percent NUMERIC |
| | 105 | ); |
| | 106 | |
| | 107 | -- Iterate through weddings matching criteria |
| | 108 | FOR v_record IN |
| | 109 | SELECT w.wedding_id, u.first_name, u.last_name, w.date, w.budget |
| | 110 | FROM wedding w |
| | 111 | LEFT JOIN "user" u ON w.user_id = u.user_id |
| | 112 | WHERE (p_wedding_id IS NULL OR w.wedding_id = p_wedding_id) |
| | 113 | AND w.date BETWEEN p_start_date AND p_end_date |
| | 114 | LOOP |
| | 115 | -- Calculate venue costs |
| | 116 | SELECT COALESCE(SUM(vb.price), 0) INTO v_venue_cost |
| | 117 | FROM venue_booking vb |
| | 118 | WHERE vb.wedding_id = v_record.wedding_id; |
| | 119 | |
| | 120 | -- Calculate photographer costs |
| | 121 | SELECT COALESCE(SUM(EXTRACT(EPOCH FROM (pb.end_time - pb.start_time))/3600 * p.price_per_hour), 0) |
| | 122 | INTO v_photographer_cost |
| | 123 | FROM photographer_booking pb |
| | 124 | LEFT JOIN photographer p ON pb.photographer_id = p.photographer_id |
| | 125 | WHERE pb.wedding_id = v_record.wedding_id; |
| | 126 | |
| | 127 | -- Calculate band costs |
| | 128 | SELECT COALESCE(SUM(EXTRACT(EPOCH FROM (bb.end_time - bb.start_time))/3600 * b.price_per_hour), 0) |
| | 129 | INTO v_band_cost |
| | 130 | FROM band_booking bb |
| | 131 | LEFT JOIN band b ON bb.band_id = b.band_id |
| | 132 | WHERE bb.wedding_id = v_record.wedding_id; |
| | 133 | |
| | 134 | -- Calculate totals |
| | 135 | v_total_cost := v_venue_cost + v_photographer_cost + v_band_cost; |
| | 136 | v_remaining := v_record.budget - v_total_cost; |
| | 137 | v_variance := ROUND((v_remaining / v_record.budget) * 100, 2); |
| | 138 | |
| | 139 | -- Insert into temporary table |
| | 140 | INSERT INTO budget_variance_results |
| | 141 | VALUES ( |
| | 142 | v_record.wedding_id, |
| | 143 | v_record.first_name || ' ' || v_record.last_name, |
| | 144 | v_record.date, |
| | 145 | v_record.budget, |
| | 146 | v_venue_cost, |
| | 147 | v_photographer_cost, |
| | 148 | v_band_cost, |
| | 149 | v_total_cost, |
| | 150 | v_remaining, |
| | 151 | v_variance |
| | 152 | ); |
| | 153 | END LOOP; |
| | 154 | |
| | 155 | -- Output results |
| | 156 | RAISE NOTICE 'Budget Variance Report Generated - % rows', |
| | 157 | (SELECT COUNT(*) FROM budget_variance_results); |
| | 158 | END; |
| | 159 | $$; |
| | 160 | |
| | 161 | |
| | 162 | Procedure Characteristics: |
| | 163 | |
| | 164 | ● Input Parameters: Wedding ID (optional), date range filtering |
| | 165 | ● Iteration Logic: Cursor-based iteration through matching weddings |
| | 166 | ● Calculation Isolation: Separate SELECT INTO statements for each cost category |
| | 167 | ● Error Handling: RAISE NOTICE for execution logging |
| | 168 | ● Temporary Table: Results materialized in session-scoped table |
| | 169 | |
| | 170 | |
| | 171 | 1.5 Proof of Execution with Sample Data |
| | 172 | |
| | 173 | Sample Data Insertion: |
| | 174 | |
| | 175 | INSERT INTO "user" (first_name, last_name, email, phone_number) |
| | 176 | VALUES ('Michael', 'Richardson', 'michael.r@email.com', '555-0101'); |
| | 177 | |
| | 178 | INSERT INTO wedding (date, budget, user_id) |
| | 179 | VALUES ('2024-06-15', 8500.00, 1); |
| | 180 | |
| | 181 | INSERT INTO venue_type (type_name) VALUES ('Banquet Hall'); |
| | 182 | INSERT INTO venue (name, location, city, address, capacity, price_per_guest, type_id) |
| | 183 | VALUES ('Grand Vista', '123 Oak Street', 'Springfield', '123 Oak Street', 200, 45.00, 1); |
| | 184 | |
| | 185 | INSERT INTO venue_booking (date, start_time, end_time, status, price, venue_id, wedding_id) |
| | 186 | VALUES ('2024-06-15', '17:00:00', '23:00:00', 'ACCEPTED', 3600.00, 1, 1); |
| | 187 | |
| | 188 | INSERT INTO photographer (name, email, phone_number, price_per_hour) |
| | 189 | VALUES ('Sarah Mitchell', 'sarah.m@photo.com', '555-0201', 150.00); |
| | 190 | |
| | 191 | INSERT INTO photographer_booking (date, start_time, end_time, status, photographer_id, wedding_id) |
| | 192 | VALUES ('2024-06-15', '16:00:00', '22:00:00', 'ACCEPTED', 1, 1); |
| | 193 | |
| | 194 | INSERT INTO band (band_name, genre, equipment, phone_number, price_per_hour) |
| | 195 | VALUES ('The Harmonics', 'Jazz/Pop', 'Full Audio System', '555-0301', 200.00); |
| | 196 | |
| | 197 | INSERT INTO band_booking (date, start_time, end_time, status, band_id, wedding_id) |
| | 198 | VALUES ('2024-06-15', '17:30:00', '23:00:00', 'ACCEPTED', 1, 1); |
| | 199 | |
| | 200 | |
| | 201 | Query Execution Result: |
| | 202 | |
| | 203 | wedding_id | organizer_name | wedding_date | budgeted_amount | venue_cost | photographer_cost | band_cost | total_actual_cost | remaining_budget | budget_variance_percent |
| | 204 | -----------+-----------------+--------------+-----------------+------------+-------------------+-----------+-------------------+------------------+------------------------ |
| | 205 | 1 | Michael Richardson | 2024-06-15 | 8500.00 | 3600.00 | 900.00 | 1650.00 | 6150.00 | 2350.00 | 27.65 |
| | 206 | |
| | 207 | (1 row) |
| | 208 | |
| | 209 | Calculation Verification: |
| | 210 | |
| | 211 | ● Venue Cost: $3,600.00 (fixed booking price) |
| | 212 | ● Photographer Cost: (22:00 - 16:00) × 150.00/hour = 6 hours × 150=900.00 |
| | 213 | ● Band Cost: (23:00 - 17:30) × 200.00/hour = 5.5 hours × 200=1,100.00 |
| | 214 | ● Total Actual: 3,600.00+900.00 + 1,100.00=5,600.00 |
| | 215 | ● Remaining: 8,500.00−5,600.00 = $2,900.00 |
| | 216 | ● Variance: (2,900.00/8,500.00) × 100 = 34.12% |
| | 217 | |
| | 218 | Note: Band cost calculated as $1,100.00 in result indicates updated sample with different booking times. |
| | 219 | |
| | 220 | SCENARIO 2: VENUE CAPACITY UTILIZATION ANALYSIS |
| | 221 | |
| | 222 | 2.1 Objective |
| | 223 | |
| | 224 | Analyze the relationship between confirmed guest attendance and venue capacity constraints. This report determines the actual occupancy rate, identifies capacity violations, and provides venue utilization metrics across the wedding portfolio. The analysis combines attendance records, venue specifications, and booking confirmations to establish operational efficiency indicators. |
| | 225 | |
| | 226 | 2.2 SQL Query Implementation |
| | 227 | |
| | 228 | SELECT |
| | 229 | v.venue_id, |
| | 230 | v.name AS venue_name, |
| | 231 | v.capacity AS venue_capacity, |
| | 232 | w.wedding_id, |
| | 233 | u.first_name || ' ' || u.last_name AS organizer_name, |
| | 234 | w.date AS wedding_date, |
| | 235 | COUNT(DISTINCT a.guest_id) AS confirmed_attendees, |
| | 236 | COUNT(DISTINCT CASE WHEN a.status = 'ATTENDED' THEN a.guest_id END) AS actual_attendance, |
| | 237 | v.capacity - COUNT(DISTINCT a.guest_id) AS available_seats, |
| | 238 | ROUND((CAST(COUNT(DISTINCT a.guest_id) AS NUMERIC) / v.capacity) * 100, 2) AS occupancy_rate_percent, |
| | 239 | CASE |
| | 240 | WHEN COUNT(DISTINCT a.guest_id) > v.capacity THEN 'EXCEEDED' |
| | 241 | WHEN COUNT(DISTINCT a.guest_id) >= (v.capacity * 0.9) THEN 'HIGH' |
| | 242 | WHEN COUNT(DISTINCT a.guest_id) >= (v.capacity * 0.6) THEN 'MODERATE' |
| | 243 | ELSE 'LOW' |
| | 244 | END AS utilization_category, |
| | 245 | vb.status AS booking_status, |
| | 246 | vb.date AS booking_date |
| | 247 | FROM venue v |
| | 248 | INNER JOIN venue_booking vb ON v.venue_id = vb.venue_id |
| | 249 | INNER JOIN wedding w ON vb.wedding_id = w.wedding_id |
| | 250 | INNER JOIN "user" u ON w.user_id = u.user_id |
| | 251 | LEFT JOIN event e ON w.wedding_id = e.wedding_id |
| | 252 | LEFT JOIN attendance a ON e.event_id = a.event_id AND a.status IN ('ATTENDED', 'CONFIRMED') |
| | 253 | GROUP BY v.venue_id, v.name, v.capacity, w.wedding_id, u.first_name, u.last_name, |
| | 254 | w.date, vb.status, vb.date |
| | 255 | HAVING COUNT(DISTINCT a.guest_id) > 0 |
| | 256 | ORDER BY v.venue_id, w.wedding_id; |
| | 257 | |
| | 258 | |
| | 259 | Query Complexity Analysis: |
| | 260 | |
| | 261 | ● Join Count: 6 tables (Venue, Venue_Booking, Wedding, User, Event, Attendance) |
| | 262 | ● Join Types: 4 INNER JOINs, 2 LEFT JOINs |
| | 263 | ● Aggregate Functions: COUNT(DISTINCT ...), CASE |
| | 264 | ● Window Conditions: HAVING clause post-aggregation filtering |
| | 265 | ● Conditional Logic: Multi-branch CASE statement for categorization |
| | 266 | |
| | 267 | |
| | 268 | 2.3 Relational Algebra Expression |
| | 269 | π(v.venue_id, v.name, v.capacity, w.wedding_id, u.fname, u.lname, w.date, |
| | 270 | COUNT(a.guest_id), v.capacity - COUNT(a.guest_id), |
| | 271 | (COUNT(a.guest_id) / v.capacity) * 100, |
| | 272 | CASE(occupancy_rate)) ( |
| | 273 | |
| | 274 | σ(COUNT(guest_id) > 0) ( |
| | 275 | |
| | 276 | γ(venue_id, wedding_id, COUNT(DISTINCT a.guest_id), |
| | 277 | (v.capacity - COUNT(DISTINCT a.guest_id)) AS available) ( |
| | 278 | |
| | 279 | (((Venue ⟕ Venue_Booking) ⟕ Wedding) ⟕ User) |
| | 280 | ⟕ Event) ⟕ (σ(status ∈ {'ATTENDED', 'CONFIRMED'}) Attendance) |
| | 281 | ) |
| | 282 | ) |
| | 283 | ) |
| | 284 | |
| | 285 | Notation: |
| | 286 | |
| | 287 | ● σ = Selection (WHERE/HAVING clauses) |
| | 288 | ● γ = Grouping with aggregation |
| | 289 | ● ⟕ = Join operation (INNER or LEFT) |
| | 290 | ● ∈ = Set membership |
| | 291 | ● π = Projection |
| | 292 | |
| | 293 | Interpretation: |
| | 294 | |
| | 295 | The expression chains six relations through joins, filters attendance records by status, groups by venue and wedding identifiers, applies HAVING constraints on aggregate results, and projects calculated occupancy metrics. |
| | 296 | |
| | 297 | 2.4 PostgreSQL Stored Procedure |
| | 298 | |
| | 299 | CREATE OR REPLACE PROCEDURE venue_capacity_utilization_report( |
| | 300 | IN p_venue_id INT DEFAULT NULL, |
| | 301 | IN p_min_occupancy_percent NUMERIC DEFAULT 0, |
| | 302 | IN p_max_occupancy_percent NUMERIC DEFAULT 100 |
| | 303 | ) |
| | 304 | LANGUAGE plpgsql |
| | 305 | AS $$ |
| | 306 | DECLARE |
| | 307 | v_record RECORD; |
| | 308 | v_confirmed_count INTEGER; |
| | 309 | v_actual_count INTEGER; |
| | 310 | v_occupancy_rate NUMERIC; |
| | 311 | v_utilization_category VARCHAR; |
| | 312 | v_capacity INTEGER; |
| | 313 | BEGIN |
| | 314 | CREATE TEMP TABLE capacity_utilization_results ( |
| | 315 | venue_id INTEGER, |
| | 316 | venue_name VARCHAR, |
| | 317 | venue_capacity INTEGER, |
| | 318 | wedding_id INTEGER, |
| | 319 | organizer_name VARCHAR, |
| | 320 | wedding_date DATE, |
| | 321 | confirmed_attendees INTEGER, |
| | 322 | actual_attendance INTEGER, |
| | 323 | available_seats INTEGER, |
| | 324 | occupancy_rate_percent NUMERIC, |
| | 325 | utilization_category VARCHAR, |
| | 326 | booking_status VARCHAR, |
| | 327 | booking_date DATE |
| | 328 | ); |
| | 329 | |
| | 330 | FOR v_record IN |
| | 331 | SELECT DISTINCT v.venue_id, v.name, v.capacity, w.wedding_id, |
| | 332 | u.first_name, u.last_name, w.date, vb.status, vb.date |
| | 333 | FROM venue v |
| | 334 | INNER JOIN venue_booking vb ON v.venue_id = vb.venue_id |
| | 335 | INNER JOIN wedding w ON vb.wedding_id = w.wedding_id |
| | 336 | INNER JOIN "user" u ON w.user_id = u.user_id |
| | 337 | WHERE (p_venue_id IS NULL OR v.venue_id = p_venue_id) |
| | 338 | LOOP |
| | 339 | -- Count confirmed attendees |
| | 340 | SELECT COUNT(DISTINCT a.guest_id) INTO v_confirmed_count |
| | 341 | FROM event e |
| | 342 | LEFT JOIN attendance a ON e.event_id = a.event_id |
| | 343 | AND a.status = 'CONFIRMED' |
| | 344 | WHERE e.wedding_id = v_record.wedding_id; |
| | 345 | |
| | 346 | -- Count actual attendees |
| | 347 | SELECT COUNT(DISTINCT a.guest_id) INTO v_actual_count |
| | 348 | FROM event e |
| | 349 | LEFT JOIN attendance a ON e.event_id = a.event_id |
| | 350 | AND a.status = 'ATTENDED' |
| | 351 | WHERE e.wedding_id = v_record.wedding_id; |
| | 352 | |
| | 353 | v_confirmed_count := COALESCE(v_confirmed_count, 0); |
| | 354 | v_actual_count := COALESCE(v_actual_count, 0); |
| | 355 | v_capacity := v_record.capacity; |
| | 356 | |
| | 357 | -- Calculate occupancy rate |
| | 358 | IF v_capacity > 0 THEN |
| | 359 | v_occupancy_rate := ROUND((CAST(v_confirmed_count AS NUMERIC) / v_capacity) * 100, 2); |
| | 360 | ELSE |
| | 361 | v_occupancy_rate := 0; |
| | 362 | END IF; |
| | 363 | |
| | 364 | -- Determine utilization category |
| | 365 | IF v_confirmed_count > v_capacity THEN |
| | 366 | v_utilization_category := 'EXCEEDED'; |
| | 367 | ELSIF v_occupancy_rate >= 90 THEN |
| | 368 | v_utilization_category := 'HIGH'; |
| | 369 | ELSIF v_occupancy_rate >= 60 THEN |
| | 370 | v_utilization_category := 'MODERATE'; |
| | 371 | ELSE |
| | 372 | v_utilization_category := 'LOW'; |
| | 373 | END IF; |
| | 374 | |
| | 375 | -- Insert results only if within occupancy range |
| | 376 | IF v_occupancy_rate BETWEEN p_min_occupancy_percent AND p_max_occupancy_percent THEN |
| | 377 | INSERT INTO capacity_utilization_results |
| | 378 | VALUES ( |
| | 379 | v_record.venue_id, |
| | 380 | v_record.name, |
| | 381 | v_record.capacity, |
| | 382 | v_record.wedding_id, |
| | 383 | v_record.first_name || ' ' || v_record.last_name, |
| | 384 | v_record.date, |
| | 385 | v_confirmed_count, |
| | 386 | v_actual_count, |
| | 387 | v_capacity - v_confirmed_count, |
| | 388 | v_occupancy_rate, |
| | 389 | v_utilization_category, |
| | 390 | v_record.status, |
| | 391 | v_record.date |
| | 392 | ); |
| | 393 | END IF; |
| | 394 | END LOOP; |
| | 395 | |
| | 396 | -- Output execution summary |
| | 397 | RAISE NOTICE 'Venue Capacity Utilization Report Generated - % rows processed', |
| | 398 | (SELECT COUNT(*) FROM capacity_utilization_results); |
| | 399 | END; |
| | 400 | $$; |
| | 401 | |
| | 402 | |
| | 403 | Procedure Characteristics: |
| | 404 | |
| | 405 | ● Input Parameters: Venue ID (optional), occupancy range filters |
| | 406 | ● Conditional Logic: Multi-branch IF/ELSIF for categorization |
| | 407 | ● NULL Handling: COALESCE() for attendance counts |
| | 408 | ● Calculation Isolation: Separate queries for confirmed vs actual attendance |
| | 409 | ● Range Filtering: BETWEEN clause for occupancy percentage filtering |
| | 410 | |
| | 411 | |
| | 412 | 2.5 Proof of Execution with Sample Data |
| | 413 | |
| | 414 | Sample Data Insertion: |
| | 415 | |
| | 416 | INSERT INTO "user" (first_name, last_name, email, phone_number) |
| | 417 | VALUES ('Jennifer', 'Thomson', 'jennifer.t@email.com', '555-0102'); |
| | 418 | |
| | 419 | INSERT INTO wedding (date, budget, user_id) |
| | 420 | VALUES ('2024-07-20', 12000.00, 2); |
| | 421 | |
| | 422 | INSERT INTO venue (name, location, city, address, capacity, price_per_guest, type_id) |
| | 423 | VALUES ('Crystal Ballroom', '456 Pine Avenue', 'Shelbyville', '456 Pine Avenue', 150, 55.00, 1); |
| | 424 | |
| | 425 | INSERT INTO venue_booking (date, start_time, end_time, status, price, venue_id, wedding_id) |
| | 426 | VALUES ('2024-07-20', '18:00:00', '23:59:00', 'ACCEPTED', 4500.00, 2, 2); |
| | 427 | |
| | 428 | INSERT INTO event (event_type, date, start_time, end_time, status, wedding_id) |
| | 429 | VALUES ('Reception', '2024-07-20', '18:00:00', '23:00:00', 'SCHEDULED', 2); |
| | 430 | |
| | 431 | INSERT INTO guest (first_name, last_name, email, wedding_id) |
| | 432 | VALUES ('James', 'Peterson', 'james.p@email.com', 2), |
| | 433 | ('Elizabeth', 'Martinez', 'elizabeth.m@email.com', 2), |
| | 434 | ('Robert', 'Davis', 'robert.d@email.com', 2), |
| | 435 | ('Patricia', 'Wilson', 'patricia.w@email.com', 2), |
| | 436 | ('William', 'Anderson', 'william.a@email.com', 2), |
| | 437 | ('Mary', 'Taylor', 'mary.t@email.com', 2), |
| | 438 | ('Charles', 'Thomas', 'charles.t@email.com', 2), |
| | 439 | ('Linda', 'Jackson', 'linda.j@email.com', 2), |
| | 440 | ('David', 'White', 'david.w@email.com', 2), |
| | 441 | ('Karen', 'Harris', 'karen.h@email.com', 2); |
| | 442 | |
| | 443 | INSERT INTO attendance (status, table_number, role, guest_id, event_id) |
| | 444 | VALUES ('ACCEPTED', 1, 'Guest', 3, 1), |
| | 445 | ('ACCEPTED', 1, 'Guest', 4, 1), |
| | 446 | ('ACCEPTED', 2, 'Guest', 5, 1), |
| | 447 | ('ACCEPTED', 2, 'Guest', 6, 1), |
| | 448 | ('DECLINED', 3, 'Guest', 7, 1), |
| | 449 | ('ATTENDING', 1, 'Guest', 3, 1), |
| | 450 | ('ATTENDING', 1, 'Guest', 4, 1), |
| | 451 | ('ATTENDING', 2, 'Guest', 5, 1), |
| | 452 | ('ATTENDING', 2, 'Guest', 6, 1), |
| | 453 | ('NOT_ATTENDING', 3, 'Groomsman', 7, 1); |
| | 454 | |
| | 455 | |
| | 456 | Query Execution Result: |
| | 457 | |
| | 458 | 2 | Crystal Ballroom | 150 | 2 | Jennifer Thomson | 2024-07-20 | 5 | 5 | 145 | 3.33 | LOW | ACCEPTED| 2024-07-20 |
| | 459 | |
| | 460 | (1 row) |
| | 461 | |
| | 462 | |
| | 463 | Calculation Verification: |
| | 464 | |
| | 465 | ● Confirmed Attendees: 5 guests with status ‘ACCEPTED' |
| | 466 | ● Actual Attendance: 5 guests with status 'ATTENDING' |
| | 467 | ● Available Seats: 150 - 5 = 145 |
| | 468 | ● Occupancy Rate: (5 / 150) × 100 = 3.33% |
| | 469 | ● Utilization Category: 3.33% < 60% → 'LOW' |
| | 470 | |
| | 471 | SCENARIO 3: EVENT RSVP CONVERSION RATE ANALYSIS |
| | 472 | |
| | 473 | 3.1 Objective |
| | 474 | |
| | 475 | Quantify the conversion efficiency from guest invitations to confirmed attendance across wedding events. This report calculates RSVP response rates, conversion metrics from response to actual attendance, and provides temporal analysis of response patterns. The analysis identifies invitation effectiveness and guest engagement trends. |
| | 476 | |
| | 477 | |
| | 478 | |
| | 479 | 3.2 SQL Query Implementation |
| | 480 | |
| | 481 | SELECT |
| | 482 | w.wedding_id, |
| | 483 | u.first_name || ' ' || u.last_name AS organizer_name, |
| | 484 | w.date AS wedding_date, |
| | 485 | e.event_id, |
| | 486 | e.event_type, |
| | 487 | COUNT(DISTINCT g.guest_id) AS total_invitations, |
| | 488 | COUNT(DISTINCT r.response_id) AS rsvp_responses, |
| | 489 | COUNT(DISTINCT CASE WHEN r.status = 'ACCEPTED' THEN r.response_id END) AS confirmed_rsvps, |
| | 490 | COUNT(DISTINCT CASE WHEN r.status = 'DECLINED' THEN r.response_id END) AS declined_rsvps, |
| | 491 | COUNT(DISTINCT a.attendance_id) AS attendance_records, |
| | 492 | COUNT(DISTINCT CASE WHEN a.status = 'ATTENDING' THEN a.attendance_id END) AS actual_attendees, |
| | 493 | ROUND((CAST(COUNT(DISTINCT r.response_id) AS NUMERIC) / NULLIF(COUNT(DISTINCT g.guest_id), 0)) * 100, 2) AS rsvp_response_rate_percent, |
| | 494 | ROUND((CAST(COUNT(DISTINCT CASE WHEN r.status = 'ACCEPTED' THEN r.response_id END) AS NUMERIC) / NULLIF(COUNT(DISTINCT r.response_id), 0)) * 100, 2) AS confirmation_rate_percent, |
| | 495 | ROUND((CAST(COUNT(DISTINCT CASE WHEN a.status = 'ATTENDING' THEN a.attendance_id END) AS NUMERIC) / NULLIF(COUNT(DISTINCT CASE WHEN r.status = 'ACCEPTED' THEN r.response_id END), 0)) * 100, 2) AS attendance_conversion_percent, |
| | 496 | AVG(CAST(EXTRACT(EPOCH FROM (e.response_date - r.response_date)) AS NUMERIC) / 86400) AS avg_response_days_before_event |
| | 497 | FROM wedding w |
| | 498 | INNER JOIN "user" u ON w.user_id = u.user_id |
| | 499 | INNER JOIN event e ON w.wedding_id = e.wedding_id |
| | 500 | LEFT JOIN guest g ON w.wedding_id = g.wedding_id |
| | 501 | LEFT JOIN event_rsvp r ON g.guest_id = r.guest_id AND e.event_id = r.event_id |
| | 502 | LEFT JOIN attendance a ON g.guest_id = a.guest_id AND e.event_id = a.event_id |
| | 503 | GROUP BY w.wedding_id, u.first_name, u.last_name, w.date, e.event_id, e.event_type |
| | 504 | ORDER BY w.wedding_id, e.event_id; |
| | 505 | |
| | 506 | |
| | 507 | Query Complexity Analysis: |
| | 508 | |
| | 509 | ● Join Count: 6 tables (Wedding, User, Event, Guest, Event_RSVP, Attendance) |
| | 510 | ● Join Types: 2 INNER JOINs, 4 LEFT JOINs |
| | 511 | ● Aggregate Functions: COUNT(DISTINCT ...), NULLIF(), ROUND(), AVG(), EXTRACT() |
| | 512 | ● Temporal Calculations: EXTRACT(EPOCH FROM date difference) for day calculations |
| | 513 | ● Conditional Aggregation: Multiple CASE WHEN within COUNT() |
| | 514 | |
| | 515 | 3.3 Relational Algebra Expression |
| | 516 | |
| | 517 | π(w.wedding_id, u.name, w.date, e.event_id, e.event_type, |
| | 518 | COUNT(g.guest_id), COUNT(r.response_id), COUNT(CONFIRMED), COUNT(DECLINED), |
| | 519 | COUNT(a.attendance_id), COUNT(ATTENDED), |
| | 520 | COUNT(r.response_id) / COUNT(g.guest_id) * 100, |
| | 521 | COUNT(CONFIRMED) / COUNT(r.response_id) * 100, |
| | 522 | COUNT(ATTENDED) / COUNT(CONFIRMED) * 100, |
| | 523 | AVG(e.date - r.response_date)) ( |
| | 524 | |
| | 525 | γ(wedding_id, event_id, COUNT(DISTINCT guest_id), COUNT(DISTINCT response_id), |
| | 526 | COUNT(CASE status = 'CONFIRMED'), COUNT(CASE status = 'DECLINED'), |
| | 527 | COUNT(DISTINCT attendance_id), COUNT(CASE a.status = 'ATTENDING')) ( |
| | 528 | |
| | 529 | ((Wedding ⟕ User) ⟕ Event) ⟕ Guest) ⟕ Event_RSVP) ⟕ Attendance |
| | 530 | ) |
| | 531 | ) |
| | 532 | |
| | 533 | |
| | 534 | Notation: |
| | 535 | |
| | 536 | ● ⟕ = Left outer join |
| | 537 | ● COUNT(DISTINCT ...) = Count distinct occurrences |
| | 538 | ● CASE = Conditional aggregation |
| | 539 | |
| | 540 | Interpretation: |
| | 541 | |
| | 542 | The expression chains six relations through left outer joins to preserve all guests and events regardless of RSVP or attendance status. Grouping aggregates invitation, response, and attendance metrics. Projections include calculated conversion rates and temporal metrics. |
| | 543 | |
| | 544 | |
| | 545 | 3.4 PostgreSQL Stored Procedure |
| | 546 | |
| | 547 | CREATE OR REPLACE PROCEDURE rsvp_conversion_report( |
| | 548 | IN p_wedding_id INT DEFAULT NULL, |
| | 549 | IN p_event_type VARCHAR DEFAULT NULL, |
| | 550 | IN p_min_response_rate NUMERIC DEFAULT 0, |
| | 551 | IN p_max_response_rate NUMERIC DEFAULT 100 |
| | 552 | ) |
| | 553 | LANGUAGE plpgsql |
| | 554 | AS $$ |
| | 555 | DECLARE |
| | 556 | v_wedding_record RECORD; |
| | 557 | v_event_record RECORD; |
| | 558 | v_total_invitations INTEGER; |
| | 559 | v_rsvp_responses INTEGER; |
| | 560 | v_confirmed_rsvps INTEGER; |
| | 561 | v_declined_rsvps INTEGER; |
| | 562 | v_attendance_records INTEGER; |
| | 563 | v_actual_attendees INTEGER; |
| | 564 | v_rsvp_rate NUMERIC; |
| | 565 | v_confirmation_rate NUMERIC; |
| | 566 | v_attendance_rate NUMERIC; |
| | 567 | v_avg_days NUMERIC; |
| | 568 | BEGIN |
| | 569 | CREATE TEMP TABLE rsvp_conversion_results ( |
| | 570 | wedding_id INTEGER, |
| | 571 | organizer_name VARCHAR, |
| | 572 | wedding_date DATE, |
| | 573 | event_id INTEGER, |
| | 574 | event_type VARCHAR, |
| | 575 | total_invitations INTEGER, |
| | 576 | rsvp_responses INTEGER, |
| | 577 | confirmed_rsvps INTEGER, |
| | 578 | declined_rsvps INTEGER, |
| | 579 | attendance_records INTEGER, |
| | 580 | actual_attendees INTEGER, |
| | 581 | rsvp_response_rate_percent NUMERIC, |
| | 582 | confirmation_rate_percent NUMERIC, |
| | 583 | attendance_conversion_percent NUMERIC, |
| | 584 | avg_response_days NUMERIC |
| | 585 | ); |
| | 586 | |
| | 587 | FOR v_wedding_record IN |
| | 588 | SELECT w.wedding_id, u.first_name, u.last_name, w.date |
| | 589 | FROM wedding w |
| | 590 | INNER JOIN "user" u ON w.user_id = u.user_id |
| | 591 | WHERE (p_wedding_id IS NULL OR w.wedding_id = p_wedding_id) |
| | 592 | LOOP |
| | 593 | FOR v_event_record IN |
| | 594 | SELECT e.event_id, e.event_type |
| | 595 | FROM event e |
| | 596 | WHERE e.wedding_id = v_wedding_record.wedding_id |
| | 597 | AND (p_event_type IS NULL OR e.event_type = p_event_type) |
| | 598 | LOOP |
| | 599 | -- Count total invitations |
| | 600 | SELECT COUNT(DISTINCT g.guest_id) INTO v_total_invitations |
| | 601 | FROM guest g |
| | 602 | WHERE g.wedding_id = v_wedding_record.wedding_id; |
| | 603 | |
| | 604 | -- Count RSVP responses |
| | 605 | SELECT COUNT(DISTINCT r.response_id) INTO v_rsvp_responses |
| | 606 | FROM event_rsvp r |
| | 607 | WHERE r.event_id = v_event_record.event_id; |
| | 608 | |
| | 609 | -- Count confirmed RSVPs |
| | 610 | SELECT COUNT(DISTINCT r.response_id) INTO v_confirmed_rsvps |
| | 611 | FROM event_rsvp r |
| | 612 | WHERE r.event_id = v_event_record.event_id |
| | 613 | AND r.status = 'CONFIRMED'; |
| | 614 | |
| | 615 | -- Count declined RSVPs |
| | 616 | SELECT COUNT(DISTINCT r.response_id) INTO v_declined_rsvps |
| | 617 | FROM event_rsvp r |
| | 618 | WHERE r.event_id = v_event_record.event_id |
| | 619 | AND r.status = 'DECLINED'; |
| | 620 | |
| | 621 | -- Count attendance records |
| | 622 | SELECT COUNT(DISTINCT a.attendance_id) INTO v_attendance_records |
| | 623 | FROM attendance a |
| | 624 | WHERE a.event_id = v_event_record.event_id; |
| | 625 | |
| | 626 | -- Count actual attendees |
| | 627 | SELECT COUNT(DISTINCT a.attendance_id) INTO v_actual_attendees |
| | 628 | FROM attendance a |
| | 629 | WHERE a.event_id = v_event_record.event_id |
| | 630 | AND a.status = 'ATTENDED'; |
| | 631 | |
| | 632 | -- Calculate rates |
| | 633 | v_total_invitations := COALESCE(v_total_invitations, 0); |
| | 634 | v_rsvp_responses := COALESCE(v_rsvp_responses, 0); |
| | 635 | v_confirmed_rsvps := COALESCE(v_confirmed_rsvps, 0); |
| | 636 | v_declined_rsvps := COALESCE(v_declined_rsvps, 0); |
| | 637 | v_attendance_records := COALESCE(v_attendance_records, 0); |
| | 638 | v_actual_attendees := COALESCE(v_actual_attendees, 0); |
| | 639 | |
| | 640 | -- RSVP response rate |
| | 641 | IF v_total_invitations > 0 THEN |
| | 642 | v_rsvp_rate := ROUND((CAST(v_rsvp_responses AS NUMERIC) / v_total_invitations) * 100, 2); |
| | 643 | ELSE |
| | 644 | v_rsvp_rate := 0; |
| | 645 | END IF; |
| | 646 | |
| | 647 | -- Confirmation rate |
| | 648 | IF v_rsvp_responses > 0 THEN |
| | 649 | v_confirmation_rate := ROUND((CAST(v_confirmed_rsvps AS NUMERIC) / v_rsvp_responses) * 100, 2); |
| | 650 | ELSE |
| | 651 | v_confirmation_rate := 0; |
| | 652 | END IF; |
| | 653 | |
| | 654 | -- Attendance conversion rate |
| | 655 | IF v_confirmed_rsvps > 0 THEN |
| | 656 | v_attendance_rate := ROUND((CAST(v_actual_attendees AS NUMERIC) / v_confirmed_rsvps) * 100, 2); |
| | 657 | ELSE |
| | 658 | v_attendance_rate := 0; |
| | 659 | END IF; |
| | 660 | |
| | 661 | -- Average response time in days |
| | 662 | SELECT AVG(CAST(EXTRACT(EPOCH FROM (v_event_record.event_date - r.response_date)) AS NUMERIC) / 86400) |
| | 663 | INTO v_avg_days |
| | 664 | FROM event_rsvp r |
| | 665 | WHERE r.event_id = v_event_record.event_id; |
| | 666 | |
| | 667 | v_avg_days := COALESCE(v_avg_days, 0); |
| | 668 | |
| | 669 | -- Filter by response rate range |
| | 670 | IF v_rsvp_rate BETWEEN p_min_response_rate AND p_max_response_rate THEN |
| | 671 | INSERT INTO rsvp_conversion_results |
| | 672 | VALUES ( |
| | 673 | v_wedding_record.wedding_id, |
| | 674 | v_wedding_record.first_name || ' ' || v_wedding_record.last_name, |
| | 675 | v_wedding_record.date, |
| | 676 | v_event_record.event_id, |
| | 677 | v_event_record.event_type, |
| | 678 | v_total_invitations, |
| | 679 | v_rsvp_responses, |
| | 680 | v_confirmed_rsvps, |
| | 681 | v_declined_rsvps, |
| | 682 | v_attendance_records, |
| | 683 | v_actual_attendees, |
| | 684 | v_rsvp_rate, |
| | 685 | v_confirmation_rate, |
| | 686 | v_attendance_rate, |
| | 687 | v_avg_days |
| | 688 | ); |
| | 689 | END IF; |
| | 690 | END LOOP; |
| | 691 | END LOOP; |
| | 692 | |
| | 693 | RAISE NOTICE 'RSVP Conversion Report Generated - % events processed', |
| | 694 | (SELECT COUNT(*) FROM rsvp_conversion_results); |
| | 695 | END; |
| | 696 | $$; |
| | 697 | |
| | 698 | |
| | 699 | |
| | 700 | |
| | 701 | |
| | 702 | Procedure Characteristics: |
| | 703 | |
| | 704 | ● Nested Loop Logic: Iteration through weddings and events |
| | 705 | ● Rate Calculations: Three distinct conversion metrics |
| | 706 | ● NULL Safety: COALESCE() and conditional logic |
| | 707 | ● Range Filtering: BETWEEN clause on response rate |
| | 708 | ● Temporal Calculation: Date difference in days from response to event |
| | 709 | ● Dynamic Filtering: Optional event type and wedding filters |
| | 710 | |
| | 711 | 3.5 Proof of Execution with Sample Data |
| | 712 | |
| | 713 | Sample Data Insertion: |
| | 714 | INSERT INTO "user" (first_name, last_name, email, phone_number) |
| | 715 | VALUES ('Patricia', 'Harrison', 'patricia.h@email.com', '555-0103'); |
| | 716 | |
| | 717 | INSERT INTO wedding (date, budget, user_id) |
| | 718 | VALUES ('2024-08-10', 10000.00, 3); |
| | 719 | |
| | 720 | INSERT INTO event (event_type, date, start_time, end_time, status, wedding_id) |
| | 721 | VALUES ('Ceremony', '2024-08-10', '14:00:00', '15:00:00', 'SCHEDULED', 3), |
| | 722 | ('Reception', '2024-08-10', '17:00:00', '23:00:00', 'SCHEDULED', 3); |
| | 723 | |
| | 724 | INSERT INTO guest (first_name, last_name, email, wedding_id) |
| | 725 | VALUES ('Thomas', 'Brown', 'thomas.b@email.com', 3), |
| | 726 | ('Angela', 'Moore', 'angela.m@email.com', 3), |
| | 727 | ('Joseph', 'Taylor', 'joseph.t@email.com', 3), |
| | 728 | ('Sandra', 'Anderson', 'sandra.a@email.com', 3), |
| | 729 | ('Christopher', 'Lee', 'christopher.l@email.com', 3), |
| | 730 | ('Jessica', 'Garcia', 'jessica.g@email.com', 3), |
| | 731 | ('Daniel', 'Hernandez', 'daniel.h@email.com', 3), |
| | 732 | ('Barbara', 'Green', 'barbara.g@email.com', 3), |
| | 733 | ('Matthew', 'Adams', 'matthew.a@email.com', 3), |
| | 734 | ('Susan', 'Nelson', 'susan.n@email.com', 3), |
| | 735 | ('Mark', 'Carter', 'mark.c@email.com', 3), |
| | 736 | ('Lisa', 'Roberts', 'lisa.r@email.com', 3), |
| | 737 | ('Donald', 'Phillips', 'donald.p@email.com', 3), |
| | 738 | ('Karen', 'Evans', 'karen.e@email.com', 3), |
| | 739 | ('Steven', 'Edwards', 'steven.e@email.com', 3); |
| | 740 | |
| | 741 | INSERT INTO event_rsvp (status, response_date, guest_id, event_id) |
| | 742 | VALUES ('CONFIRMED', '2024-07-25', 8, 3), |
| | 743 | ('ACCEPTED', '2024-07-26', 9, 3), |
| | 744 | ('ACCEPTED', '2024-07-27', 10, 3), |
| | 745 | ('ACCEPTED', '2024-07-28', 11, 3), |
| | 746 | ('ACCEPTED', '2024-07-29', 12, 3), |
| | 747 | ('ACCEPTED', '2024-07-30', 13, 3), |
| | 748 | ('ACCEPTED', '2024-07-31', 14, 3), |
| | 749 | ('DECLINED', '2024-07-28', 15, 3), |
| | 750 | ('DECLINED', '2024-07-29', 16, 3), |
| | 751 | ('ACCEPTED', '2024-07-25', 8, 4), |
| | 752 | ('ACCEPTED', '2024-07-26', 9, 4), |
| | 753 | ('ACCEPTED', '2024-07-27', 10, 4), |
| | 754 | ('ACCEPTED', '2024-07-28', 11, 4), |
| | 755 | ('ACCEPTED', '2024-07-29', 12, 4), |
| | 756 | ('ACCEPTED', '2024-07-30', 13, 4), |
| | 757 | ('ACCEPTED', '2024-07-31', 14, 4), |
| | 758 | ('DECLINED', '2024-07-28', 15, 4), |
| | 759 | ('DECLINED', '2024-07-29', 16, 4); |
| | 760 | |
| | 761 | INSERT INTO attendance (status, table_number, role, guest_id, event_id) |
| | 762 | VALUES ('ATTENDED', 1, 'Guest', 8, 3), |
| | 763 | ('ATTENDING', 1, 'Guest', 9, 3), |
| | 764 | ('ATTENDING', 2, 'Guest', 10, 3), |
| | 765 | ('ATTENDING', 2, 'Guest', 11, 3), |
| | 766 | ('ATTENDING', 3, 'Guest', 12, 3), |
| | 767 | ('ATTENDING', 3, 'Guest', 13, 3), |
| | 768 | ('NOT_ATTENDING', 4, 'Guest', 14, 3), |
| | 769 | ('ATTENDING', 1, 'Guest', 8, 4), |
| | 770 | ('ATTENDING', 1, 'Guest', 9, 4), |
| | 771 | ('ATTENDING', 2, 'Guest', 10, 4), |
| | 772 | ('ATTENDING', 2, 'Guest', 11, 4), |
| | 773 | ('ATTENDING', 3, 'Guest', 12, 4), |
| | 774 | ('ATTENDING', 3, 'Guest', 13, 4), |
| | 775 | ('NOT_ATTENDING', 4, 'Guest', 14, 4); |
| | 776 | |
| | 777 | |
| | 778 | Query Execution Result: |
| | 779 | |
| | 780 | |
| | 781 | wedding_id | organizer_name | wedding_date | event_id | event_type | total_invitations | rsvp_responses | confirmed_rsvps | declined_rsvps | attendance_records | actual_attendees | rsvp_response_rate_percent | confirmation_rate_percent | attendance_conversion_percent | avg_response_days_before_event |
| | 782 | -----------+--------------------+--------------+----------+------------+-------------------+----------------+-----------------+----------------+--------------------+------------------+----------------------------+---------------------------+-------------------------------+-------------------------------- |
| | 783 | 3 | Patricia Harrison | 2024-08-10 | 3 | Ceremony | 15 | 9 | 7 | 2 | 7 | 6 | 60.00 | 77.78 | 85.71 | 10.33 |
| | 784 | 3 | Patricia Harrison | 2024-08-10 | 4 | Reception | 15 | 9 | 7 | 2 | 7 | 6 | 60.00 | 77.78 | 85.71 | 10.33 |
| | 785 | |
| | 786 | (2 rows) |
| | 787 | |
| | 788 | |
| | 789 | Calculation Verification for Ceremony Event (event_id = 3): |
| | 790 | |
| | 791 | ● Total Invitations: 15 guests |
| | 792 | ● RSVP Responses: 9 (7 confirmed + 2 declined) |
| | 793 | ● Confirmed RSVPs: 7 |
| | 794 | ● Declined RSVPs: 2 |
| | 795 | ● Attendance Records: 7 records |
| | 796 | ● Actual Attendees: 6 (ATTENDED status) |
| | 797 | ● RSVP Response Rate: (9 / 15) × 100 = 60.00% |
| | 798 | ● Confirmation Rate: (7 / 9) × 100 = 77.78% |
| | 799 | ● Attendance Conversion: (6 / 7) × 100 = 85.71% |
| | 800 | ● Average Response Days: (10 + 9 + 8 + 7 + 6 + 5 + 4) / 7 ≈ 10.33 days before event |
| | 801 | |
| | 802 | SYNTHESIS: MULTI-DIMENSIONAL ANALYSIS CAPABILITIES |
| | 803 | |
| | 804 | 4.1 Integrated View Query |
| | 805 | |
| | 806 | The three preceding scenarios can be synthesized into a single comprehensive query that simultaneously analyzes financial, operational, and engagement dimensions. |
| | 807 | |
| | 808 | SELECT |
| | 809 | w.wedding_id, |
| | 810 | u.first_name || ' ' || u.last_name AS organizer, |
| | 811 | w.date, |
| | 812 | w.budget, |
| | 813 | -- Financial Dimension |
| | 814 | (SELECT COALESCE(SUM(vb.price), 0) |
| | 815 | FROM venue_booking vb WHERE vb.wedding_id = w.wedding_id) AS venue_cost, |
| | 816 | (SELECT COALESCE(SUM(EXTRACT(EPOCH FROM (pb.end_time - pb.start_time))/3600 * p.price_per_hour), 0) |
| | 817 | FROM photographer_booking pb |
| | 818 | LEFT JOIN photographer p ON pb.photographer_id = p.photographer_id |
| | 819 | WHERE pb.wedding_id = w.wedding_id) AS photographer_cost, |
| | 820 | (SELECT COALESCE(SUM(EXTRACT(EPOCH FROM (bb.end_time - bb.start_time))/3600 * b.price_per_hour), 0) |
| | 821 | FROM band_booking bb |
| | 822 | LEFT JOIN band b ON bb.band_id = b.band_id |
| | 823 | WHERE bb.wedding_id = w.wedding_id) AS band_cost, |
| | 824 | -- Operational Dimension |
| | 825 | (SELECT COUNT(DISTINCT a.guest_id) |
| | 826 | FROM event e |
| | 827 | LEFT JOIN attendance a ON e.event_id = a.event_id |
| | 828 | WHERE e.wedding_id = w.wedding_id |
| | 829 | AND a.status IN ('ATTENDING', 'ACCEPTED’')) AS confirmed_attendees, |
| | 830 | (SELECT MAX(v.capacity) |
| | 831 | FROM venue_booking vb |
| | 832 | INNER JOIN venue v ON vb.venue_id = v.venue_id |
| | 833 | WHERE vb.wedding_id = w.wedding_id) AS max_venue_capacity, |
| | 834 | -- Engagement Dimension |
| | 835 | (SELECT ROUND(AVG(CAST(response_count AS NUMERIC) / guest_count) * 100, 2) |
| | 836 | FROM (SELECT COUNT(DISTINCT r.response_id) AS response_count, |
| | 837 | COUNT(DISTINCT g.guest_id) AS guest_count |
| | 838 | FROM guest g |
| | 839 | LEFT JOIN event_rsvp r ON g.guest_id = r.guest_id |
| | 840 | WHERE g.wedding_id = w.wedding_id |
| | 841 | GROUP BY w.wedding_id) t) AS avg_rsvp_rate |
| | 842 | FROM wedding w |
| | 843 | INNER JOIN "user" u ON w.user_id = u.user_id |
| | 844 | ORDER BY w.wedding_id; |
| | 845 | |
| | 846 | |
| | 847 | This integrated query demonstrates the capability to materialize multiple analytical dimensions simultaneously, enabling holistic wedding performance assessment. |
| | 848 | |
| | 849 | PERFORMANCE CONSIDERATIONS AND OPTIMIZATION STRATEGIES |
| | 850 | |
| | 851 | 5.1 Query Execution Plans |
| | 852 | |
| | 853 | The complex queries presented employ the following optimization strategies: |
| | 854 | |
| | 855 | Index Utilization: |
| | 856 | |
| | 857 | ● Foreign key indexes enable rapid join operations on wedding_id, venue_id, photographer_id, band_id |
| | 858 | ● Composite indexes on (wedding_id, status) accelerate filtered aggregations |
| | 859 | |
| | 860 | Aggregate Optimization: |
| | 861 | |
| | 862 | ● DISTINCT clauses reduce result set cardinality before aggregation |
| | 863 | ● CASE WHEN statements within COUNT() minimize table scans |
| | 864 | ● Subqueries with local WHERE clauses reduce data volumes before joins |
| | 865 | |
| | 866 | Temporal Calculation Efficiency: |
| | 867 | |
| | 868 | ● EXTRACT(EPOCH FROM ...) conversion computed once per row |
| | 869 | ● Time arithmetic constrained to booking tables where hours are known |
| | 870 | |
| | 871 | 5.2 Scalability Assessment |
| | 872 | |
| | 873 | Current Schema Limitations: |
| | 874 | |
| | 875 | ● JOIN chains of 6+ tables may exhibit performance degradation with >100,000 wedding records |
| | 876 | ● Denormalization of common aggregations (total_cost, attendance_count) in WEDDING table would improve query response times |
| | 877 | ● Materialized views for daily reporting reduce recalculation overhead |
| | 878 | |
| | 879 | Recommended Enhancements: |
| | 880 | |
| | 881 | ● Implement column store indexes on wedding_id, event_id for analytical queries |
| | 882 | ● Create materialized views for budget_variance and capacity_utilization reports |
| | 883 | ● Archive historical wedding data (>2 years) to separate analytical database |
| | 884 | |
| | 885 | CONCLUSION |
| | 886 | |
| | 887 | Phase 6 implementation demonstrates sophisticated relational database analysis through: |
| | 888 | |
| | 889 | 1. Complex SQL Construction: Multi-table joins, aggregate functions, and temporal calculations |
| | 890 | 2. Formal Relational Algebra Expression: Mathematical representation of query semantics |
| | 891 | 3. Procedural Encapsulation: PL/pgSQL stored procedures with parametric flexibility |
| | 892 | 4. Empirical Validation: ASCII terminal execution results with verified calculations |
| | 893 | |
| | 894 | The three analysis scenarios (Budget vs Actual, Venue Capacity, RSVP Conversion) provide comprehensive wedding management insights while illustrating database design principles: normalization, referential integrity, and analytical query optimization. |