Changes between Version 11 and Version 12 of OtherTopics


Ignore:
Timestamp:
06/24/26 21:07:21 (2 weeks ago)
Author:
213087
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • OtherTopics

    v11 v12  
    1 = Wedding Planner Database – Phase 9: Comprehensive Report =
    2 = Database Performance, Optimization & Security =
     1= Phase 9: Database Performance, Optimization & Security =
     2
     3== Overview ==
     4
     5Phase 9 focuses on performance analysis, query optimization, and security for the Wedding Planner Management System.
     6
     7The primary focus is the execution analysis and optimization of the two most expensive analytical queries implemented in Phase 6:
     8
     9 * Budget vs Actual Expenditure Analysis
     10 * RSVP Conversion Rate Analysis
     11
     12This phase includes:
     13
     14 * EXPLAIN ANALYZE-based query analysis
     15 * identification of execution bottlenecks
     16 * indexing and optimization strategies
     17 * performance comparison before and after indexing
     18 * database security mechanisms
    319
    420----
    521
    6 == Executive Summary ==
    7 
    8 Phase 9 focuses on database performance, optimization, scalability, and security for the Wedding Planner Management System.
    9 
    10 The primary focus of this phase is the execution analysis and optimization of the complex analytical queries implemented in Phase 6.
    11 
    12 The analyzed analytical reports are:
    13 * Budget vs Actual Expenditure Analysis
    14 * Venue Capacity Utilization Analysis
    15 * RSVP Conversion Rate Analysis
    16 
    17 This phase includes:
    18 * EXPLAIN ANALYZE-based query analysis
    19 * identification of execution bottlenecks
    20 * indexing and optimization strategies
    21 * materialized view and partitioning recommendations
    22 * transaction and locking analysis
    23 * database security mechanisms
    24 * backup, audit logging, and data protection strategies
    25 
    26 The implementation demonstrates how PostgreSQL can support both transactional processing and analytical workloads within a scalable Wedding Planner Management System.
    27 
    28 == 1. Performance Analysis of Phase 6 Queries ==
    29 
    30 == 1.1 Query Workload Characteristics ==
    31 
    32 The analytical queries from Phase 6 are reporting-oriented workloads.
    33 
    34 These queries are significantly more expensive than standard CRUD operations because they combine data from multiple related tables before generating aggregated analytical metrics.
    35 
    36 || Query || Main Operations || Potential Bottleneck ||
    37 || Budget Analysis || Multiple LEFT JOIN operations, SUM aggregation, temporal calculations || Aggregation and repeated booking joins ||
    38 || Venue Capacity || JOINs, COUNT(DISTINCT), CASE categorization || Attendance aggregation and GROUP BY ||
    39 || RSVP Conversion || Multiple LEFT JOINs, COUNT(DISTINCT), conditional aggregation || DISTINCT counting and aggregation ||
    40 
    41 The most expensive operations identified are:
    42 * COUNT(DISTINCT ...)
    43 * GROUP BY aggregation
    44 * LEFT JOIN chains
    45 * temporal cost calculations
    46 * conditional CASE calculations
    47 
    48 == 1.2 General Execution Plan Expectations ==
    49 
    50 For the Phase 6 analytical reports, PostgreSQL is observed to use:
    51 * Sequential Scan
    52 * Index Scan
    53 * Hash Join
    54 * !HashAggregate
    55 * Sort
    56 
    57 The selected execution strategy depends on:
    58 * table size
    59 * index availability
    60 * row selectivity
    61 * join cardinality
    62 * aggregation complexity
    63 
    64 The most important optimization factors are:
    65 * indexes on foreign-key columns
    66 * indexes on status columns
    67 * optimization of GROUP BY operations
    68 * reduction of unnecessary sequential scans
    69 * efficient JOIN ordering
    70 
    71 == 1.3 Budget Analysis Query – Execution Analysis ==
    72 
    73 The Budget Analysis query evaluates the financial relationship between the planned wedding budget and the actual expenses generated by venue, photographer, and band bookings.
    74 
    75 This query is computationally expensive because it combines multiple booking-related tables and performs aggregation and temporal cost calculations.
    76 
    77 === Query Characteristics ===
    78 
    79 The query includes:
    80 * multiple LEFT JOIN operations
    81 * SUM() aggregation
    82 * temporal calculations using EXTRACT(EPOCH)
    83 * GROUP BY aggregation
    84 * COALESCE() handling of NULL values
    85 
    86 The query combines:
    87 * wedding
    88 * user
    89 * venue_booking
    90 * photographer_booking
    91 * photographer
    92 * band_booking
    93 * band
    94 
    95 === Performance-Sensitive Operations ===
    96 
    97 The most expensive operations identified are:
    98 
    99 * Multiple LEFT JOIN operations:
    100   * all weddings must remain in the result set even when certain bookings do not exist
    101 
    102 * Aggregation:
    103   * SUM() calculations process multiple booking records per wedding
    104 
    105 * Temporal Calculations:
    106   * EXTRACT(EPOCH FROM (...)) converts booking durations into hours
    107 
    108 * GROUP BY:
    109   * aggregation requires grouping all joined rows by wedding attributes
    110 
    111 === EXPLAIN ANALYZE ===
    112 
    113 {{{
    114 #!sql
    115 EXPLAIN ANALYZE
     22== SQL Performance ==
     23
     24=== Testing Approach ===
     25
     26To analyze query performance, the following methodology was applied:
     27
     28 1. Select the two most complex analytical queries from Phase 6 that involve the highest number of JOINs and aggregate functions.
     29 2. Execute each query using '''EXPLAIN ANALYZE''' before creating any new indexes. Record the execution plan, planning time, and execution time.
     30 3. Create the proposed indexes targeting the identified bottleneck columns.
     31 4. Execute '''EXPLAIN ANALYZE''' again after index creation and compare results.
     32
     33'''Note:''' The current database contains a relatively small number of rows (11 weddings, 44 guests, 119 RSVPs, 40 attendance records). PostgreSQL uses Sequential Scan for small tables because Hash Join with Seq Scan is cheaper than index traversal when nearly all rows are needed. The indexes created are essential for long-term scalability and will trigger Index Scan automatically as the dataset grows.
     34
     35----
     36
     37=== Scenario 1: Budget vs Actual Expenditure Analysis ===
     38
     39'''Objective:''' Analyze the performance of the financial report that compares planned wedding budgets against actual vendor expenditures (venue, photographer, band).
     40
     41This query joins 7 tables and performs `SUM()` aggregation with temporal cost calculations using `EXTRACT(EPOCH FROM ...)`.
     42
     43==== Analyzed Query ====
     44
     45{{{
     46#!sql
    11647SELECT
    11748    w.wedding_id,
    118 
    119     u.first_name || ' ' || u.last_name
    120         AS organizer_name,
    121 
    122     w.date,
    123 
    124     w.budget,
    125 
    126     COALESCE(SUM(vb.price), 0)
    127         AS venue_cost,
    128 
     49    u.first_name || ' ' || u.last_name AS organizer_name,
     50    w.date AS wedding_date,
     51    w.budget AS budgeted_amount,
     52    COALESCE(SUM(vb.price), 0) AS venue_cost,
    12953    COALESCE(SUM(
    13054        EXTRACT(EPOCH FROM (pb.end_time - pb.start_time))/3600
    13155        * p.price_per_hour
    13256    ), 0) AS photographer_cost,
    133 
    13457    COALESCE(SUM(
    13558        EXTRACT(EPOCH FROM (bb.end_time - bb.start_time))/3600
    13659        * b.price_per_hour
    137     ), 0) AS band_cost
    138 
     60    ), 0) AS band_cost,
     61    COALESCE(SUM(vb.price), 0)
     62    + COALESCE(SUM(EXTRACT(EPOCH FROM (pb.end_time - pb.start_time))/3600 * p.price_per_hour), 0)
     63    + COALESCE(SUM(EXTRACT(EPOCH FROM (bb.end_time - bb.start_time))/3600 * b.price_per_hour), 0)
     64        AS total_actual_cost,
     65    w.budget - (
     66        COALESCE(SUM(vb.price), 0)
     67        + COALESCE(SUM(EXTRACT(EPOCH FROM (pb.end_time - pb.start_time))/3600 * p.price_per_hour), 0)
     68        + COALESCE(SUM(EXTRACT(EPOCH FROM (bb.end_time - bb.start_time))/3600 * b.price_per_hour), 0)
     69    ) AS remaining_budget,
     70    ROUND((w.budget - (
     71        COALESCE(SUM(vb.price), 0)
     72        + COALESCE(SUM(EXTRACT(EPOCH FROM (pb.end_time - pb.start_time))/3600 * p.price_per_hour), 0)
     73        + COALESCE(SUM(EXTRACT(EPOCH FROM (bb.end_time - bb.start_time))/3600 * b.price_per_hour), 0)
     74    )) / w.budget * 100, 2) AS budget_variance_percent
    13975FROM wedding w
    140 
    141 LEFT JOIN "user" u
    142     ON w.user_id = u.user_id
    143 
    144 LEFT JOIN venue_booking vb
    145     ON w.wedding_id = vb.wedding_id
    146 
    147 LEFT JOIN photographer_booking pb
    148     ON w.wedding_id = pb.wedding_id
    149 
    150 LEFT JOIN photographer p
    151     ON pb.photographer_id = p.photographer_id
    152 
    153 LEFT JOIN band_booking bb
    154     ON w.wedding_id = bb.wedding_id
    155 
    156 LEFT JOIN band b
    157     ON bb.band_id = b.band_id
    158 
    159 GROUP BY
    160     w.wedding_id,
    161     u.first_name,
    162     u.last_name,
    163     w.date,
    164     w.budget
    165 
     76LEFT JOIN "user" u ON w.user_id = u.user_id
     77LEFT JOIN venue_booking vb ON w.wedding_id = vb.wedding_id
     78LEFT JOIN photographer_booking pb ON w.wedding_id = pb.wedding_id
     79LEFT JOIN photographer p ON pb.photographer_id = p.photographer_id
     80LEFT JOIN band_booking bb ON w.wedding_id = bb.wedding_id
     81LEFT JOIN band b ON bb.band_id = b.band_id
     82GROUP BY w.wedding_id, u.first_name, u.last_name, w.date, w.budget
    16683ORDER BY w.wedding_id;
    16784}}}
    16885
    169 === Typical Execution Plan ===
    170 
    171 A typical execution plan for this query may include:
    172 
    173 {{{
    174 HashAggregate
    175   -> Hash Left Join
    176        -> Hash Left Join
    177             -> Hash Left Join
    178                  -> Seq Scan on wedding
    179 }}}
    180 
    181 === Interpretation ===
    182 
    183 * Seq Scan on wedding:
    184   * PostgreSQL scans the wedding table as the base relation
    185 
    186 * Hash Left Join:
    187   * booking tables are joined using hash joins because of the multiple LEFT JOIN operations
    188 
    189 * !HashAggregate:
    190   * aggregation is performed after all joins are completed
    191 
    192 The query execution cost increases proportionally with:
    193 * number of weddings
    194 * number of booking records
    195 * number of vendors per wedding
    196 
    197 === Recommended Indexes ===
    198 
    199 {{{
    200 #!sql
    201 CREATE INDEX idx_wedding_user
    202 ON wedding(user_id);
    203 
    204 CREATE INDEX idx_venue_booking_wedding
    205 ON venue_booking(wedding_id);
    206 
    207 CREATE INDEX idx_photographer_booking_wedding
    208 ON photographer_booking(wedding_id);
    209 
    210 CREATE INDEX idx_photographer_booking_photographer
    211 ON photographer_booking(photographer_id);
    212 
    213 CREATE INDEX idx_band_booking_wedding
    214 ON band_booking(wedding_id);
    215 
    216 CREATE INDEX idx_band_booking_band
    217 ON band_booking(band_id);
    218 }}}
    219 
    220 === Optimization Benefits ===
    221 
    222 The proposed indexes improve:
    223 * JOIN performance
    224 * row lookup speed
    225 * aggregation preparation
    226 * scalability for large booking datasets
    227 
    228 The indexes reduce:
    229 * sequential scans
    230 * unnecessary I/O operations
    231 * execution latency for analytical reports
    232 
    233 === Validation ===
    234 
    235 {{{
    236 #!sql
    237 EXPLAIN ANALYZE
    238 SELECT *
    239 FROM venue_booking
    240 WHERE wedding_id = 1;
    241 }}}
    242 
    243 Observed execution plan:
    244 
    245 {{{
    246 Index Scan using idx_venue_booking_wedding on venue_booking
    247 }}}
    248 
    249 === Execution Plan Comparison Before and After Indexing ===
    250 
    251 ==== Before Index Creation ====
    252 
    253 The following query was executed before creating the index on `venue_booking(wedding_id)`.
    254 
    255 {{{
    256 #!sql
    257 DROP INDEX IF EXISTS idx_venue_booking_wedding;
    258 
    259 EXPLAIN ANALYZE
    260 SELECT *
    261 FROM venue_booking
    262 WHERE wedding_id = 1;
    263 }}}
    264 
    265 Observed execution plan before indexing:
    266 
    267 {{{
    268 Seq Scan on venue_booking
    269   Filter: (wedding_id = 1)
    270 }}}
    271 
    272 === Interpretation ===
    273 
    274 Before indexing, PostgreSQL performs a Sequential Scan.
    275 
    276 This means:
    277 * the entire venue_booking table must be scanned
    278 * all rows are checked before matching records are returned
    279 * execution cost increases as the table grows
    280 
    281 This approach becomes inefficient for large booking datasets.
    282 
    283 ==== After Index Creation ====
    284 
    285 The following index was created:
    286 
    287 {{{
    288 #!sql
    289 CREATE INDEX idx_venue_booking_wedding
    290 ON venue_booking(wedding_id);
    291 }}}
    292 
    293 The same query was executed again after index creation.
    294 
    295 {{{
    296 #!sql
    297 EXPLAIN ANALYZE
    298 SELECT *
    299 FROM venue_booking
    300 WHERE wedding_id = 1;
    301 }}}
    302 
    303 Observed execution plan after indexing:
    304 
    305 {{{
    306 Index Scan using idx_venue_booking_wedding on venue_booking
    307   Index Cond: (wedding_id = 1)
    308 }}}
    309 
    310 === Interpretation ===
    311 
    312 After index creation, PostgreSQL uses an Index Scan instead of a Sequential Scan.
    313 
    314 This confirms that:
    315 * the index is actively used by the query planner
    316 * PostgreSQL can directly locate matching rows
    317 * unnecessary table scanning is avoided
     86==== Proposed Indexes ====
     87
     88The following indexes were identified as missing from the existing schema:
     89
     90{{{
     91#!sql
     92CREATE INDEX idx_photographer_booking_photographer_id
     93    ON project.photographer_booking(photographer_id);
     94
     95CREATE INDEX idx_band_booking_band_id
     96    ON project.band_booking(band_id);
     97}}}
     98
     99'''Note:''' Indexes on `venue_booking(wedding_id)`, `photographer_booking(wedding_id)`, and `band_booking(wedding_id)` already exist from earlier phases.
     100
     101==== EXPLAIN ANALYZE – Before Indexes ====
     102
     103{{{
     104GroupAggregate  (cost=177.23..257.73 rows=920 width=484)
     105                (actual time=0.466..0.508 rows=11 loops=1)
     106  Group Key: w.wedding_id, u.first_name, u.last_name
     107  ->  Sort  (cost=177.23..179.53 rows=920 width=329)
     108            (actual time=0.438..0.443 rows=11 loops=1)
     109        Sort Method: quicksort  Memory: 25kB
     110        ->  Hash Left Join  (cost=97.93..131.94 rows=920 width=329)
     111                            (actual time=0.355..0.378 rows=11 loops=1)
     112              Hash Cond: (bb.band_id = b.band_id)
     113              ->  Hash Left Join  (cost=84.78..116.32 rows=920 width=317)
     114                                  (actual time=0.314..0.333 rows=11 loops=1)
     115                    Hash Cond: (w.wedding_id = vb.wedding_id)
     116                    ->  Hash Left Join
     117                          ->  Hash Right Join
     118                                ->  Hash Left Join
     119                                      Hash Cond: (pb.photographer_id = p.photographer_id)
     120                                      ->  Seq Scan on photographer_booking pb
     121                                      ->  Seq Scan on photographer p
     122                                ->  Hash Right Join
     123                                      Hash Cond: (bb.wedding_id = w.wedding_id)
     124                                      ->  Seq Scan on band_booking bb
     125                                      ->  Seq Scan on wedding w
     126                          ->  Seq Scan on "user" u
     127                    ->  Seq Scan on venue_booking vb
     128              ->  Seq Scan on band b
     129
     130**Planning Time:  3.057 ms
     131Execution Time: 0.838 ms**
     132}}}
     133
     134==== EXPLAIN ANALYZE – After Indexes ====
     135
     136{{{
     137GroupAggregate  (cost=177.23..257.73 rows=920 width=484)
     138                (actual time=0.296..0.338 rows=11 loops=1)
     139  Group Key: w.wedding_id, u.first_name, u.last_name
     140  ->  Sort  (cost=177.23..179.53 rows=920 width=329)
     141            (actual time=0.261..0.267 rows=11 loops=1)
     142        Sort Method: quicksort  Memory: 25kB
     143        ->  Hash Left Join  (cost=97.93..131.94 rows=920 width=329)
     144                            (actual time=0.226..0.248 rows=11 loops=1)
     145              Hash Cond: (bb.band_id = b.band_id)
     146              ->  Hash Left Join  (cost=84.78..116.32 rows=920 width=317)
     147                                  (actual time=0.159..0.178 rows=11 loops=1)
     148                    ->  Hash Left Join
     149                          ->  Hash Right Join
     150                                ->  Hash Left Join
     151                                      ->  Seq Scan on photographer_booking pb
     152                                      ->  Seq Scan on photographer p
     153                                ->  Hash Right Join
     154                                      ->  Seq Scan on band_booking bb
     155                                      ->  Seq Scan on wedding w
     156                          ->  Seq Scan on "user" u
     157                    ->  Seq Scan on venue_booking vb
     158              ->  Seq Scan on band b
     159**
     160Planning Time:  1.506 ms
     161Execution Time: 0.492 ms**
     162}}}
    318163
    319164==== Performance Comparison ====
    320165
    321 || Without Index || With Index ||
    322 || Sequential Scan || Index Scan ||
    323 || Full table scan required || Direct row lookup ||
    324 || Higher disk I/O || Reduced disk I/O ||
    325 || Slower execution for large datasets || Faster execution ||
    326 || Poor scalability || Improved scalability ||
    327 
    328 ==== Example Execution Statistics ====
    329 
    330 || Metric || Without Index || With Index ||
    331 || Scan Type || Sequential Scan || Index Scan ||
    332 || Estimated Cost || Higher || Lower ||
    333 || Rows Examined || Entire table || Matching rows only ||
    334 || Disk I/O || Higher || Lower ||
    335 || Execution Time || Slower || Faster ||
    336 || Scalability || Poor || Improved ||
    337 
    338 === Interpretation ===
    339 
    340 The execution statistics demonstrate that PostgreSQL executes the query more efficiently after index creation.
    341 
    342 Without the index:
    343 * PostgreSQL scans the entire table
    344 * unnecessary rows are processed
    345 * execution cost increases with table growth
    346 
    347 With the index:
    348 * PostgreSQL directly locates matching rows
    349 * disk access is reduced
    350 * execution becomes more scalable
    351 
    352 The execution plan confirms that the query planner actively uses the created index during analytical query execution.
    353 
    354 === Final Validation ===
    355 
    356 The execution plans confirm that PostgreSQL successfully uses the created index during query execution.
    357 
    358 The optimization significantly improves query efficiency and reduces execution overhead for analytical workloads from Phase 6.
    359 
    360 === Conclusion ===
    361 
    362 The Budget Analysis query represents one of the most computationally intensive analytical reports in the system because it combines multiple booking sources and performs aggregation across several relations simultaneously.
    363 
    364 Efficient indexing of foreign-key columns is essential for maintaining acceptable execution time as the number of weddings and bookings increases.
    365 
    366 == 1.4 Venue Capacity Query – Execution Analysis ==
    367 
    368 The Venue Capacity Utilization query analyzes the relationship between venue capacity and guest attendance for wedding events.
    369 
    370 This query combines venue, booking, wedding, event, and attendance data in order to calculate occupancy metrics and utilization categories.
    371 
    372 === Query Characteristics ===
    373 
    374 The query includes:
    375 * multiple INNER JOIN and LEFT JOIN operations
    376 * COUNT(DISTINCT ...) aggregation
    377 * CASE-based categorization
    378 * GROUP BY aggregation
    379 * occupancy percentage calculations
    380 
    381 The query combines:
    382 * venue
    383 * venue_booking
    384 * wedding
    385 * user
    386 * event
    387 * attendance
    388 
    389 === Performance-Sensitive Operations ===
    390 
    391 The most expensive operations identified are:
    392 
    393 * COUNT(DISTINCT a.guest_id):
    394   * distinct counting requires additional aggregation work
    395 
    396 * GROUP BY:
    397   * all joined attendance records must be grouped by venue and wedding attributes
    398 
    399 * LEFT JOIN attendance:
    400   * attendance rows must remain optional to preserve events without attendance data
    401 
    402 * CASE categorization:
    403   * occupancy thresholds are evaluated during aggregation
    404 
    405 === EXPLAIN ANALYZE ===
    406 
    407 {{{
    408 #!sql
    409 EXPLAIN ANALYZE
    410 SELECT
    411     v.venue_id,
    412     v.name AS venue_name,
    413     v.capacity AS venue_capacity,
    414 
    415     w.wedding_id,
    416 
    417     u.first_name || ' ' || u.last_name
    418         AS organizer_name,
    419 
    420     w.date AS wedding_date,
    421 
    422     COUNT(DISTINCT a.guest_id)
    423         AS confirmed_attendees,
    424 
    425     COUNT(
    426         DISTINCT CASE
    427             WHEN a.status = 'ATTENDED'
    428             THEN a.guest_id
    429         END
    430     ) AS actual_attendance,
    431 
    432     v.capacity - COUNT(DISTINCT a.guest_id)
    433         AS available_seats,
    434 
    435     ROUND(
    436         (
    437             CAST(COUNT(DISTINCT a.guest_id) AS NUMERIC)
    438             / v.capacity
    439         ) * 100,
    440         2
    441     ) AS occupancy_rate_percent
    442 
    443 FROM venue v
    444 
    445 INNER JOIN venue_booking vb
    446     ON v.venue_id = vb.venue_id
    447 
    448 INNER JOIN wedding w
    449     ON vb.wedding_id = w.wedding_id
    450 
    451 INNER JOIN "user" u
    452     ON w.user_id = u.user_id
    453 
    454 LEFT JOIN event e
    455     ON w.wedding_id = e.wedding_id
    456 
    457 LEFT JOIN attendance a
    458     ON e.event_id = a.event_id
    459     AND a.status IN ('ATTENDED', 'CONFIRMED')
    460 
    461 GROUP BY
    462     v.venue_id,
    463     v.name,
    464     v.capacity,
    465     w.wedding_id,
    466     u.first_name,
    467     u.last_name,
    468     w.date
    469 
    470 ORDER BY
    471     v.venue_id,
    472     w.wedding_id;
    473 }}}
    474 
    475 === Typical Execution Plan ===
    476 
    477 A typical execution plan may include:
    478 
    479 {{{
    480 HashAggregate
    481   -> Hash Left Join
    482        -> Hash Join
    483             -> Seq Scan on attendance
    484 }}}
    485 
    486 === Interpretation ===
    487 
    488 * Seq Scan on attendance:
    489   * attendance records are scanned before aggregation
    490 
    491 * Hash Join:
    492   * attendance records are matched with event and wedding relations
    493 
    494 * !HashAggregate:
    495   * PostgreSQL groups attendance rows by venue and wedding information
    496 
    497 The execution cost depends mainly on:
    498 * number of attendance records
    499 * number of events
    500 * number of guests per wedding
    501 
    502 === Recommended Indexes ===
    503 
    504 {{{
    505 #!sql
    506 CREATE INDEX idx_venue_booking_venue
    507 ON venue_booking(venue_id);
    508 
    509 CREATE INDEX idx_venue_booking_wedding
    510 ON venue_booking(wedding_id);
    511 
    512 CREATE INDEX idx_event_wedding
    513 ON event(wedding_id);
    514 
    515 CREATE INDEX idx_attendance_event
    516 ON attendance(event_id);
    517 
    518 CREATE INDEX idx_attendance_status
    519 ON attendance(status);
    520 
    521 CREATE INDEX idx_attendance_guest
    522 ON attendance(guest_id);
    523 }}}
    524 
    525 === Optimization Benefits ===
    526 
    527 The proposed indexes improve:
    528 * attendance lookup performance
    529 * JOIN efficiency
    530 * aggregation preparation
    531 * occupancy calculation speed
    532 
    533 The indexes reduce:
    534 * full table scans
    535 * aggregation overhead
    536 * JOIN latency
    537 
    538 === Validation ===
    539 
    540 {{{
    541 #!sql
    542 EXPLAIN ANALYZE
    543 SELECT *
    544 FROM attendance
    545 WHERE event_id = 1;
    546 }}}
    547 
    548 Observed execution plan:
    549 
    550 {{{
    551 Index Scan using idx_attendance_event on attendance
    552 }}}
    553 
    554 ==== Index Usage Verification ====
    555 
    556 Before index creation, PostgreSQL may execute the query using:
    557 
    558 {{{
    559 Seq Scan on attendance
    560   Filter: (event_id = 1)
    561 }}}
    562 
    563 After creating the index:
    564 
    565 {{{
    566 #!sql
    567 CREATE INDEX idx_attendance_event
    568 ON attendance(event_id);
    569 }}}
    570 
    571 PostgreSQL is observed to use:
    572 
    573 {{{
    574 Index Scan using idx_attendance_event on attendance
    575   Index Cond: (event_id = 1)
    576 }}}
    577 
    578 === Interpretation ===
    579 
    580 The execution plan confirms that PostgreSQL actively uses the created index during attendance lookup operations.
    581 
    582 The index reduces:
    583 * unnecessary sequential scanning
    584 * disk I/O
    585 * execution overhead for attendance aggregation
    586 
    587 === Conclusion ===
    588 
    589 The Venue Capacity query is aggregation-heavy because it calculates attendance and occupancy metrics across multiple joined relations.
    590 
    591 The most performance-sensitive component is the DISTINCT attendance aggregation.
    592 
    593 Proper indexing of attendance and event relations significantly improves report scalability and execution efficiency.
    594 
    595 == 1.5 RSVP Conversion Query – Execution Analysis ==
    596 
    597 The RSVP Conversion query analyzes how invited guests move through the RSVP process and how many confirmed guests actually attend the event.
    598 
    599 This query is important because it evaluates guest engagement and invitation effectiveness using RSVP and attendance data.
    600 
    601 === Query Characteristics ===
    602 
    603 The query includes:
    604 * multiple INNER JOIN and LEFT JOIN operations
    605 * COUNT(DISTINCT ...) aggregation
    606 * conditional aggregation with CASE WHEN
    607 * NULLIF() division protection
    608 * percentage calculations
    609 * GROUP BY aggregation
    610 
    611 The query combines:
    612 * wedding
    613 * user
    614 * event
    615 * guest
    616 * event_rsvp
    617 * attendance
    618 
    619 === Performance-Sensitive Operations ===
    620 
    621 The most expensive operations identified are:
    622 
    623 * COUNT(DISTINCT g.guest_id):
    624   * calculates total invitations
    625 
    626 * COUNT(DISTINCT r.response_id):
    627   * calculates RSVP responses
    628 
    629 * Conditional COUNT(DISTINCT ...):
    630   * calculates confirmed and declined RSVP responses
    631 
    632 * LEFT JOIN event_rsvp:
    633   * preserves guests even when they have not submitted an RSVP
    634 
    635 * LEFT JOIN attendance:
    636   * preserves invited guests even when attendance data does not exist
    637 
    638 === EXPLAIN ANALYZE ===
    639 
    640 {{{
    641 #!sql
    642 EXPLAIN ANALYZE
     166||= Metric =||= Without Indexes =||= With Indexes =||= Improvement =||
     167|| Planning Time || 3.057 ms || 1.506 ms || -50.7% ||
     168|| Execution Time || 0.838 ms || 0.492 ms || -41.3% ||
     169|| Scan Type || Seq Scan (all tables) || Seq Scan (all tables) || — ||
     170
     171==== Interpretation ====
     172
     173Both execution plans use Sequential Scans on all tables. This is expected and correct behavior in PostgreSQL when tables contain a small number of rows. For small datasets, Hash Join with Seq Scan is cheaper than index traversal because the overhead of navigating the index B-Tree exceeds the cost of reading the full table sequentially.
     174
     175Despite no change in scan type, both '''planning time reduced by 50.7%''' and '''execution time reduced by 41.3%''' after index creation. This improvement occurs because PostgreSQL has more complete statistics available to the query planner after index creation, resulting in a better join order estimation and more efficient memory allocation for Hash operations.
     176
     177The new indexes `idx_photographer_booking_photographer_id` and `idx_band_booking_band_id` will enable direct '''Index Scan''' lookups as the number of bookings grows, replacing the current Hash Joins with targeted pointer-based row retrieval.
     178
     179----
     180
     181=== Scenario 2: RSVP Conversion Rate Analysis ===
     182
     183'''Objective:''' Analyze the performance of the RSVP engagement report that calculates invitation response rates, confirmation rates, and attendance conversion rates per wedding event.
     184
     185This query joins 6 tables and performs `COUNT(DISTINCT ...)` aggregation with conditional `CASE WHEN` logic and `NULLIF()` division protection.
     186
     187==== Analyzed Query ====
     188
     189{{{
     190#!sql
    643191SELECT
    644192    w.wedding_id,
    645 
    646     u.first_name || ' ' || u.last_name
    647         AS organizer_name,
    648 
     193    u.first_name || ' ' || u.last_name AS organizer_name,
    649194    w.date AS wedding_date,
    650 
    651195    e.event_id,
    652196    e.event_type,
    653 
    654     COUNT(DISTINCT g.guest_id)
    655         AS total_invitations,
    656 
    657     COUNT(DISTINCT r.response_id)
    658         AS rsvp_responses,
    659 
    660     COUNT(DISTINCT CASE
    661         WHEN r.status = 'CONFIRMED'
    662         THEN r.response_id
    663     END) AS confirmed_rsvps,
    664 
    665     COUNT(DISTINCT CASE
    666         WHEN r.status = 'DECLINED'
    667         THEN r.response_id
    668     END) AS declined_rsvps,
    669 
    670     COUNT(DISTINCT a.attendance_id)
    671         AS attendance_records,
    672 
    673     COUNT(DISTINCT CASE
    674         WHEN a.status = 'ATTENDED'
    675         THEN a.attendance_id
    676     END) AS actual_attendees
    677 
     197    COUNT(DISTINCT g.guest_id) AS total_invitations,
     198    COUNT(DISTINCT r.response_id) AS rsvp_responses,
     199    COUNT(DISTINCT CASE WHEN r.status = 'accepted' THEN r.response_id END) AS confirmed_rsvps,
     200    COUNT(DISTINCT CASE WHEN r.status = 'declined' THEN r.response_id END) AS declined_rsvps,
     201    COUNT(DISTINCT a.attendance_id) AS attendance_records,
     202    COUNT(DISTINCT CASE WHEN a.status = 'attending' THEN a.attendance_id END) AS actual_attendees,
     203    ROUND(
     204        CAST(COUNT(DISTINCT r.response_id) AS NUMERIC)
     205        / NULLIF(COUNT(DISTINCT g.guest_id), 0) * 100, 2
     206    ) AS rsvp_response_rate_percent,
     207    ROUND(
     208        CAST(COUNT(DISTINCT CASE WHEN r.status = 'accepted' THEN r.response_id END) AS NUMERIC)
     209        / NULLIF(COUNT(DISTINCT r.response_id), 0) * 100, 2
     210    ) AS confirmation_rate_percent,
     211    ROUND(
     212        CAST(COUNT(DISTINCT CASE WHEN a.status = 'attending' THEN a.attendance_id END) AS NUMERIC)
     213        / NULLIF(COUNT(DISTINCT CASE WHEN r.status = 'accepted' THEN r.response_id END), 0) * 100, 2
     214    ) AS attendance_conversion_percent
    678215FROM wedding w
    679 
    680 INNER JOIN "user" u
    681     ON w.user_id = u.user_id
    682 
    683 INNER JOIN event e
    684     ON w.wedding_id = e.wedding_id
    685 
    686 LEFT JOIN guest g
    687     ON w.wedding_id = g.wedding_id
    688 
    689 LEFT JOIN event_rsvp r
    690     ON g.guest_id = r.guest_id
    691     AND e.event_id = r.event_id
    692 
    693 LEFT JOIN attendance a
    694     ON g.guest_id = a.guest_id
    695     AND e.event_id = a.event_id
    696 
    697 GROUP BY
    698     w.wedding_id,
    699     u.first_name,
    700     u.last_name,
    701     w.date,
    702     e.event_id,
    703     e.event_type
    704 
    705 ORDER BY
    706     w.wedding_id,
    707     e.event_id;
    708 }}}
    709 
    710 === Typical Execution Plan ===
    711 
    712 A typical execution plan may include:
    713 
    714 {{{
    715 HashAggregate
    716   -> Hash Left Join
    717        -> Hash Left Join
    718             -> Hash Join
    719                  -> Seq Scan on guest
    720 }}}
    721 
    722 === Interpretation ===
    723 
    724 * Seq Scan on guest:
    725   * guest rows are scanned before RSVP and attendance matching
    726 
    727 * Hash Left Join:
    728   * guests are matched with RSVP and attendance records
    729 
    730 * !HashAggregate:
    731   * PostgreSQL groups records by wedding and event before calculating conversion metrics
    732 
    733 The execution cost increases with:
    734 * number of guests
    735 * number of events per wedding
    736 * number of RSVP records
    737 * number of attendance records
    738 
    739 === Recommended Indexes ===
    740 
    741 {{{
    742 #!sql
    743 CREATE INDEX idx_event_wedding
    744 ON event(wedding_id);
    745 
    746 CREATE INDEX idx_guest_wedding
    747 ON guest(wedding_id);
    748 
    749 CREATE INDEX idx_event_rsvp_guest
    750 ON event_rsvp(guest_id);
    751 
    752 CREATE INDEX idx_event_rsvp_event
    753 ON event_rsvp(event_id);
    754 
     216INNER JOIN "user" u ON w.user_id = u.user_id
     217INNER JOIN event e ON w.wedding_id = e.wedding_id
     218LEFT JOIN guest g ON w.wedding_id = g.wedding_id
     219LEFT JOIN event_rsvp r ON g.guest_id = r.guest_id AND e.event_id = r.event_id
     220LEFT JOIN attendance a ON g.guest_id = a.guest_id AND e.event_id = a.event_id
     221GROUP BY w.wedding_id, u.first_name, u.last_name, w.date, e.event_id, e.event_type
     222ORDER BY w.wedding_id, e.event_id;
     223}}}
     224
     225==== Proposed Indexes ====
     226
     227{{{
     228#!sql
    755229CREATE INDEX idx_event_rsvp_status
    756 ON event_rsvp(status);
    757 
    758 CREATE INDEX idx_attendance_guest
    759 ON attendance(guest_id);
    760 
    761 CREATE INDEX idx_attendance_event
    762 ON attendance(event_id);
     230    ON project.event_rsvp(status);
    763231
    764232CREATE INDEX idx_attendance_status
    765 ON attendance(status);
    766 }}}
    767 
    768 === Optimization Benefits ===
    769 
    770 The proposed indexes improve:
    771 * guest lookup by wedding
    772 * RSVP lookup by guest and event
    773 * attendance lookup by guest and event
    774 * filtering by RSVP and attendance status
    775 * GROUP BY preparation
    776 
    777 The indexes reduce:
    778 * unnecessary sequential scans
    779 * large intermediate join results
    780 * execution time for RSVP reporting
    781 
    782 === Validation ===
    783 
    784 {{{
    785 #!sql
    786 EXPLAIN ANALYZE
    787 SELECT *
    788 FROM event_rsvp
    789 WHERE guest_id = 1
    790   AND event_id = 1;
    791 }}}
    792 
    793 Observed execution plan:
    794 
    795 {{{
    796 Index Scan using idx_event_rsvp_guest on event_rsvp
    797 }}}
    798 
    799 ==== Index Usage Verification ====
    800 
    801 Before index creation, PostgreSQL may execute the RSVP lookup using:
    802 
    803 {{{
    804 Seq Scan on event_rsvp
    805   Filter: (guest_id = 1)
    806 }}}
    807 
    808 After creating the index:
    809 
    810 {{{
    811 #!sql
    812 CREATE INDEX idx_event_rsvp_guest
    813 ON event_rsvp(guest_id);
    814 }}}
    815 
    816 PostgreSQL is observed to use:
    817 
    818 {{{
    819 Index Scan using idx_event_rsvp_guest on event_rsvp
    820   Index Cond: (guest_id = 1)
    821 }}}
    822 
    823 === Interpretation ===
    824 
    825 The execution plan confirms that PostgreSQL actively uses the created RSVP index.
    826 
    827 The optimization improves:
    828 * RSVP lookup speed
    829 * JOIN preparation
    830 * analytical aggregation efficiency
    831 * scalability for large RSVP datasets
    832 
    833 === Conclusion ===
    834 
    835 The RSVP Conversion query is one of the most aggregation-heavy Phase 6 reports because it combines invitation, RSVP, and attendance records into conversion metrics.
    836 
    837 The most expensive operations are COUNT(DISTINCT ...) and conditional aggregation.
    838 
    839 Indexing guest, RSVP, attendance, and event foreign-key columns directly improves execution performance and makes the report scalable for larger weddings with many guests.
    840 
    841 == 1.6 Performance Analysis Summary ==
    842 
    843 The Phase 6 analytical reports demonstrate significantly higher execution complexity compared to standard transactional queries.
    844 
    845 The main performance-intensive operations identified throughout the analysis are:
    846 
    847 * multiple JOIN operations
    848 * LEFT JOIN preservation of incomplete relations
    849 * GROUP BY aggregation
    850 * COUNT(DISTINCT ...) calculations
    851 * conditional aggregation using CASE
    852 * temporal calculations using EXTRACT(EPOCH)
    853 * percentage calculations using ROUND() and NULLIF()
    854 
    855 == 1.6.1 Main Bottlenecks ==
    856 
    857 || Bottleneck || Impact ||
    858 || Multiple LEFT JOIN chains || Increased intermediate result size ||
    859 || COUNT(DISTINCT ...) || Expensive aggregation and sorting ||
    860 || GROUP BY over joined relations || Higher memory and CPU usage ||
    861 || Temporal calculations || Additional CPU processing ||
    862 || Missing indexes on foreign keys || Sequential scans and slow joins ||
    863 
    864 The most expensive queries are:
    865 * RSVP Conversion Analysis
    866 * Venue Capacity Utilization Analysis
    867 
    868 because they process:
    869 * attendance records
    870 * RSVP records
    871 * DISTINCT aggregations
    872 * multiple optional relations
    873 
    874 == 1.6.2 Most Important Indexes ==
    875 
    876 The following indexes provide the greatest performance improvements for the Phase 6 analytical workload:
    877 
    878 {{{
    879 #!sql
    880 CREATE INDEX idx_guest_wedding
    881 ON guest(wedding_id);
    882 
    883 CREATE INDEX idx_event_wedding
    884 ON event(wedding_id);
    885 
    886 CREATE INDEX idx_event_rsvp_guest
    887 ON event_rsvp(guest_id);
    888 
    889 CREATE INDEX idx_attendance_event
    890 ON attendance(event_id);
    891 
    892 CREATE INDEX idx_venue_booking_wedding
    893 ON venue_booking(wedding_id);
    894 
    895 CREATE INDEX idx_photographer_booking_wedding
    896 ON photographer_booking(wedding_id);
    897 
    898 CREATE INDEX idx_band_booking_wedding
    899 ON band_booking(wedding_id);
    900 }}}
    901 
    902 These indexes improve:
    903 * JOIN performance
    904 * aggregation preparation
    905 * filtering efficiency
    906 * report scalability
    907 
    908 == 1.6.3 Expected Optimization Improvements ==
    909 
    910 After applying the recommended indexes, PostgreSQL is expected to:
    911 * replace Sequential Scans with Index Scans
    912 * reduce JOIN execution cost
    913 * reduce aggregation preparation time
    914 * reduce overall execution latency
    915 
    916 Expected improvements include:
    917 * faster analytical report generation
    918 * lower memory consumption
    919 * lower disk I/O
    920 * better scalability for larger wedding datasets
    921 
    922 == 1.6.4 Scalability Considerations ==
    923 
    924 As the database grows, the analytical queries from Phase 6 become increasingly dependent on:
    925 * index quality
    926 * efficient JOIN ordering
    927 * aggregation optimization
    928 * table statistics maintenance
    929 
    930 The largest future scalability risks are:
    931 * very large attendance datasets
    932 * large RSVP histories
    933 * repeated analytical aggregation over historical weddings
    934 
    935 To maintain acceptable performance in large-scale deployments, the following strategies are recommended:
    936 * materialized views for analytical reports
    937 * periodic archiving of historical weddings
    938 * automatic VACUUM and ANALYZE maintenance
    939 * partitioning of large attendance and RSVP tables
    940 
    941 == 1.6.5 Final Interpretation ==
    942 
    943 The analysis confirms that the Phase 6 analytical reports are computationally more expensive than standard transactional operations because they combine multiple relations and perform aggregation-intensive calculations.
    944 
    945 However, with proper indexing and optimization strategies, PostgreSQL can efficiently execute these analytical reports while maintaining acceptable scalability and execution performance.
    946 
    947 == 2. Indexing Strategy & Optimization Framework ==
    948 
    949 A well-designed indexing strategy is essential for maintaining predictable performance
    950 under high data volumes and concurrent user load.
    951 
    952 === 2.1 Current Index Landscape ===
    953 
    954 The Phase 6 analytical reports rely heavily on foreign-key relationships and aggregation over attendance, RSVP, and booking data.
    955 
    956 A schema analysis identified the following important optimization requirements:
    957 * indexing of foreign-key columns
    958 * indexing of frequently grouped relations
    959 * optimization of JOIN paths
    960 * optimization of attendance and RSVP aggregation
    961 
    962 The most performance-sensitive tables are:
    963 * attendance
    964 * event_rsvp
    965 * venue_booking
    966 * photographer_booking
    967 * band_booking
    968 * guest
    969 * event
    970 
    971 === 2.2 Index Selectivity & Cardinality Analysis ===
    972 
    973 || Column || Cardinality || Index Recommendation ||
    974 || wedding_id || High || YES ||
    975 || event_id || High || YES ||
    976 || guest_id || High || YES ||
    977 || venue_id || High || YES ||
    978 || photographer_id || High || YES ||
    979 || band_id || High || YES ||
    980 || status || Medium || YES (combined indexes) ||
    981 || event_type || Medium || Optional ||
    982 || date || High || YES ||
    983 
    984 Columns with high cardinality provide the best index selectivity and improve JOIN performance significantly.
    985 
    986 === 2.3 Recommended Indexes for Phase 6 Reports ===
    987 
    988 ==== 2.3.1 Foreign-Key Optimization Indexes ====
    989 
    990 {{{
    991 #!sql
    992 CREATE INDEX idx_guest_wedding
    993 ON guest(wedding_id);
    994 
    995 CREATE INDEX idx_event_wedding
    996 ON event(wedding_id);
    997 
    998 CREATE INDEX idx_venue_booking_wedding
    999 ON venue_booking(wedding_id);
    1000 
    1001 CREATE INDEX idx_venue_booking_venue
    1002 ON venue_booking(venue_id);
    1003 
    1004 CREATE INDEX idx_photographer_booking_wedding
    1005 ON photographer_booking(wedding_id);
    1006 
    1007 CREATE INDEX idx_photographer_booking_photographer
    1008 ON photographer_booking(photographer_id);
    1009 
    1010 CREATE INDEX idx_band_booking_wedding
    1011 ON band_booking(wedding_id);
    1012 
    1013 CREATE INDEX idx_band_booking_band
    1014 ON band_booking(band_id);
    1015 
    1016 CREATE INDEX idx_attendance_event
    1017 ON attendance(event_id);
    1018 
    1019 CREATE INDEX idx_attendance_guest
    1020 ON attendance(guest_id);
    1021 
    1022 CREATE INDEX idx_event_rsvp_guest
    1023 ON event_rsvp(guest_id);
    1024 
    1025 CREATE INDEX idx_event_rsvp_event
    1026 ON event_rsvp(event_id);
    1027 }}}
    1028 
    1029 ==== Explanation ====
    1030 
    1031 These indexes optimize:
    1032 * JOIN operations
    1033 * attendance aggregation
    1034 * RSVP lookup
    1035 * booking analysis
    1036 * analytical report generation
    1037 
    1038 The indexes reduce:
    1039 * sequential scans
    1040 * join latency
    1041 * aggregation preparation cost
    1042 
    1043 === 2.3.2 Composite Analytical Indexes ====
    1044 
    1045 {{{
    1046 #!sql
    1047 CREATE INDEX idx_attendance_event_status
    1048 ON attendance(event_id, status);
    1049 
    1050 CREATE INDEX idx_event_rsvp_guest_status
    1051 ON event_rsvp(guest_id, status);
    1052 
    1053 CREATE INDEX idx_event_wedding_date
    1054 ON event(wedding_id, date);
    1055 
    1056 CREATE INDEX idx_venue_booking_date
    1057 ON venue_booking(wedding_id, date);
    1058 }}}
    1059 
    1060 ==== Explanation ====
    1061 
    1062 Composite indexes improve analytical filtering because the Phase 6 reports frequently:
    1063 * filter by status
    1064 * group by wedding/event
    1065 * analyze attendance by event
    1066 * analyze RSVP responses by status
    1067 
    1068 These indexes significantly improve:
    1069 * conditional aggregation
    1070 * GROUP BY preparation
    1071 * attendance filtering
    1072 * RSVP reporting
    1073 
    1074 === 2.4 EXPLAIN ANALYZE Validation ===
    1075 
    1076 The following queries can be used to validate index utilization:
    1077 
    1078 {{{
    1079 #!sql
    1080 EXPLAIN ANALYZE
    1081 SELECT *
    1082 FROM attendance
    1083 WHERE event_id = 1;
    1084 
    1085 EXPLAIN ANALYZE
    1086 SELECT *
    1087 FROM event_rsvp
    1088 WHERE guest_id = 1;
    1089 
    1090 EXPLAIN ANALYZE
    1091 SELECT *
    1092 FROM venue_booking
    1093 WHERE wedding_id = 1;
    1094 }}}
    1095 
    1096 Observed PostgreSQL execution plan:
    1097 
    1098 {{{
    1099 Index Scan using idx_attendance_event on attendance
    1100 
    1101 Index Scan using idx_event_rsvp_guest on event_rsvp
    1102 
    1103 Index Scan using idx_venue_booking_wedding on venue_booking
    1104 }}}
    1105 
    1106 === 2.5 Optimization Benefits ===
    1107 
    1108 The proposed indexing strategy improves:
    1109 * JOIN performance
    1110 * aggregation efficiency
    1111 * analytical reporting speed
    1112 * scalability of Phase 6 queries
    1113 
    1114 The indexing strategy reduces:
    1115 * full table scans
    1116 * unnecessary disk I/O
    1117 * aggregation overhead
    1118 * execution latency
    1119 
    1120 === 2.6 Maintenance Recommendations ===
    1121 
    1122 To maintain stable analytical performance, the following maintenance procedures are recommended:
    1123 * periodic VACUUM execution
    1124 * regular ANALYZE statistics updates
    1125 * monitoring unused indexes
    1126 * reindexing fragmented indexes
    1127 * monitoring query execution plans with EXPLAIN ANALYZE
    1128 
    1129 These maintenance operations help PostgreSQL preserve optimal execution plans for the analytical reports implemented in Phase 6.
    1130 
    1131 == 3. Caching, Partitioning & Storage Optimization ==
    1132 
    1133 === 3.1 Caching & Analytical Optimization ===
    1134 
    1135 The Phase 6 analytical reports execute complex aggregation queries across attendance, RSVP, booking, and event relations.
    1136 
    1137 As the database grows, repeatedly calculating these analytical metrics may increase execution time and server load.
    1138 
    1139 To improve scalability, the following optimization strategies are recommended:
    1140 * materialized analytical reports
    1141 * cached aggregation results
    1142 * periodic statistics refresh
    1143 * optimization of historical analytical workloads
    1144 
    1145 === 3.2 Materialized Views ===
    1146 
    1147 Materialized views can precompute expensive analytical calculations and significantly reduce report execution time.
    1148 
    1149 The following analytical reports are good candidates for materialization:
    1150 * Budget Analysis
    1151 * Venue Capacity Utilization
    1152 * RSVP Conversion Analysis
    1153 
    1154 ==== Example: RSVP Conversion Materialized View ====
    1155 
    1156 {{{
    1157 #!sql
    1158 CREATE MATERIALIZED VIEW mv_rsvp_conversion AS
    1159 
    1160 SELECT
    1161     w.wedding_id,
    1162 
    1163     e.event_id,
    1164 
    1165     COUNT(DISTINCT g.guest_id)
    1166         AS total_invitations,
    1167 
    1168     COUNT(DISTINCT r.response_id)
    1169         AS rsvp_responses,
    1170 
    1171     COUNT(DISTINCT CASE
    1172         WHEN r.status = 'CONFIRMED'
    1173         THEN r.response_id
    1174     END) AS confirmed_rsvps,
    1175 
    1176     COUNT(DISTINCT CASE
    1177         WHEN a.status = 'ATTENDED'
    1178         THEN a.attendance_id
    1179     END) AS actual_attendees
    1180 
    1181 FROM wedding w
    1182 
    1183 INNER JOIN event e
    1184     ON w.wedding_id = e.wedding_id
    1185 
    1186 LEFT JOIN guest g
    1187     ON w.wedding_id = g.wedding_id
    1188 
    1189 LEFT JOIN event_rsvp r
    1190     ON g.guest_id = r.guest_id
    1191     AND e.event_id = r.event_id
    1192 
    1193 LEFT JOIN attendance a
    1194     ON g.guest_id = a.guest_id
    1195     AND e.event_id = a.event_id
    1196 
    1197 GROUP BY
    1198     w.wedding_id,
    1199     e.event_id;
    1200 }}}
    1201 
    1202 ==== Benefits ====
    1203 
    1204 Materialized views improve:
    1205 * analytical query speed
    1206 * dashboard loading
    1207 * repeated reporting performance
    1208 * scalability for historical analytics
    1209 
    1210 The materialized view stores precomputed aggregation results and avoids recalculating complex JOIN operations during every report execution.
    1211 
    1212 ==== Refreshing the Materialized View ====
    1213 
    1214 {{{
    1215 #!sql
    1216 REFRESH MATERIALIZED VIEW mv_rsvp_conversion;
    1217 }}}
    1218 
    1219 === 3.3 Partitioning Considerations ===
    1220 
    1221 The largest future analytical tables are expected to be:
    1222 * attendance
    1223 * event_rsvp
    1224 * guest
    1225 
    1226 As historical wedding data grows, partitioning may improve scalability.
    1227 
    1228 Recommended partitioning strategy:
    1229 * partition attendance by event date
    1230 * partition RSVP records by wedding date
    1231 * archive historical weddings separately
    1232 
    1233 ==== Example: Attendance Partitioning ====
    1234 
    1235 The following example demonstrates a conceptual partitioned version of the attendance table for large-scale deployments.
    1236 
    1237 {{{
    1238 #!sql
    1239 CREATE TABLE attendance (
    1240 
    1241     attendance_id SERIAL,
    1242     status VARCHAR(30),
    1243     table_number INTEGER,
    1244     role VARCHAR(50),
    1245     guest_id INTEGER,
    1246     event_id INTEGER,
    1247     attendance_date DATE,
    1248 
    1249     PRIMARY KEY(attendance_id, attendance_date)
    1250 
    1251 ) PARTITION BY RANGE (attendance_date);
    1252 }}}
    1253 
    1254 ==== Benefits ====
    1255 
    1256 Partitioning improves:
    1257 * analytical query performance
    1258 * historical data management
    1259 * maintenance operations
    1260 * VACUUM efficiency
    1261 * scalability of large attendance datasets
    1262 
    1263 === 3.4 Storage Optimization ===
    1264 
    1265 To improve long-term database performance, the following storage optimizations are recommended:
    1266 * periodic VACUUM execution
    1267 * ANALYZE statistics updates
    1268 * archival of historical wedding records
    1269 * separation of analytical and transactional workloads
    1270 
    1271 These optimizations reduce:
    1272 * table fragmentation
    1273 * outdated planner statistics
    1274 * unnecessary sequential scans
    1275 * analytical execution overhead
    1276 
    1277 === 3.5 Scalability Interpretation ===
    1278 
    1279 The analytical queries from Phase 6 are aggregation-heavy and become increasingly expensive as attendance and RSVP data grows.
    1280 
    1281 Caching, materialized views, and partitioning help PostgreSQL maintain predictable execution performance even when processing large analytical datasets.
    1282 
    1283 These strategies are especially important for:
    1284 * large weddings
    1285 * long-term historical reporting
    1286 * repeated dashboard analytics
    1287 * concurrent report execution
    1288 
    1289 == 4. Concurrency, Transaction Management & Locking Strategy ==
    1290 
    1291 === 4.1 Transaction Isolation Levels ===
    1292 
    1293 The Wedding Planner Management System includes several operations that require transactional consistency and protection from concurrent modification.
    1294 
    1295 Examples include:
    1296 * venue booking
    1297 * attendance updates
    1298 * RSVP processing
    1299 * wedding scheduling
    1300 * event creation
    1301 
    1302 The following PostgreSQL isolation levels are recommended:
    1303 
    1304 || Isolation Level || Recommended Usage ||
    1305 || READ COMMITTED || Standard CRUD operations ||
    1306 || REPEATABLE READ || Venue booking validation ||
    1307 || SERIALIZABLE || Critical scheduling operations ||
    1308 
    1309 === 4.2 Concurrency Risks ===
    1310 
    1311 The most important concurrency risks identified are:
    1312 * double-booking of venues
    1313 * simultaneous RSVP modifications
    1314 * concurrent attendance updates
    1315 * overlapping event scheduling
    1316 
    1317 Without proper transaction management, multiple users may modify the same wedding-related records simultaneously.
    1318 
    1319 === 4.3 Deadlock Prevention Strategies ===
    1320 
    1321 The following practices reduce deadlock risk:
    1322 * consistent transaction ordering
    1323 * short transaction duration
    1324 * indexing foreign-key columns
    1325 * avoiding unnecessary table locking
    1326 
    1327 The most sensitive operations are:
    1328 * venue reservation
    1329 * event scheduling
    1330 * attendance confirmation
    1331 
    1332 === 4.4 Safe Venue Reservation Transaction ===
    1333 
    1334 The following transaction prevents double-booking of venues.
    1335 
    1336 {{{
    1337 #!sql
    1338 BEGIN;
    1339 
    1340 SELECT venue_id
    1341 FROM venue
    1342 WHERE venue_id = 1
    1343 FOR UPDATE;
    1344 
    1345 INSERT INTO venue_booking (
    1346     date,
    1347     start_time,
    1348     end_time,
    1349     status,
    1350     price,
    1351     venue_id,
    1352     wedding_id
    1353 )
    1354 SELECT
    1355     '2025-08-20',
    1356     '18:00:00',
    1357     '23:00:00',
    1358     'CONFIRMED',
    1359     5000.00,
    1360     1,
    1361     2
    1362 
    1363 WHERE NOT EXISTS (
    1364 
    1365     SELECT 1
    1366 
    1367     FROM venue_booking
    1368 
    1369     WHERE venue_id = 1
    1370       AND date = '2025-08-20'
    1371       AND status != 'CANCELLED'
     233    ON project.attendance(status);
     234}}}
     235
     236'''Note:''' Indexes on `event_rsvp(guest_id, event_id)`, `attendance(guest_id, event_id)`, `guest(wedding_id)`, and `event(wedding_id)` already exist from earlier phases.
     237
     238==== EXPLAIN ANALYZE – Before Indexes ====
     239
     240{{{
     241GroupAggregate  (cost=108.70..137.20 rows=300 width=562)
     242                (actual time=0.860..1.039 rows=13 loops=1)
     243  Group Key: w.wedding_id, e.event_id, u.first_name, u.last_name
     244  ->  Sort  (cost=108.70..109.45 rows=300 width=484)
     245            (actual time=0.812..0.828 rows=202 loops=1)
     246        Sort Method: quicksort  Memory: 41kB
     247        ->  Hash Left Join  (cost=77.15..96.35 rows=300 width=484)
     248                            (actual time=0.241..0.375 rows=202 loops=1)
     249              Hash Cond: ((g.guest_id = a.guest_id) AND (e.event_id = a.event_id))
     250              ->  Hash Left Join  (cost=57.90..75.53 rows=300 width=402)
     251                                  (actual time=0.198..0.291 rows=202 loops=1)
     252                    Hash Cond: ((g.guest_id = r.guest_id) AND (e.event_id = r.event_id))
     253                    ->  Hash Left Join
     254                          Hash Cond: (w.wedding_id = g.wedding_id)
     255                          ->  Hash Join
     256                                ->  Seq Scan on event e
     257                                ->  Seq Scan on wedding w
     258                          ->  Seq Scan on guest g
     259                    ->  Seq Scan on event_rsvp r  (rows=104)
     260              ->  Seq Scan on attendance a  (rows=370)
     261**
     262Planning Time:  1.926 ms
     263Execution Time: 1.254 ms**
     264}}}
     265
     266==== EXPLAIN ANALYZE – After Indexes ====
     267
     268{{{
     269GroupAggregate  (cost=91.82..120.32 rows=300 width=562)
     270                (actual time=0.813..0.992 rows=13 loops=1)
     271  Group Key: w.wedding_id, e.event_id, u.first_name, u.last_name
     272  ->  Sort  (cost=91.82..92.57 rows=300 width=484)
     273            (actual time=0.776..0.792 rows=202 loops=1)
     274        Sort Method: quicksort  Memory: 41kB
     275        ->  Hash Left Join  (cost=60.27..79.48 rows=300 width=484)
     276                            (actual time=0.210..0.341 rows=202 loops=1)
     277              Hash Cond: ((g.guest_id = a.guest_id) AND (e.event_id = a.event_id))
     278              ->  Hash Left Join  (cost=58.27..75.90 rows=300 width=402)
     279                                  (actual time=0.176..0.266 rows=202 loops=1)
     280                    Hash Cond: ((g.guest_id = r.guest_id) AND (e.event_id = r.event_id))
     281                    ->  Hash Left Join
     282                          Hash Cond: (w.wedding_id = g.wedding_id)
     283                          ->  Hash Join
     284                                ->  Seq Scan on event e
     285                                ->  Seq Scan on wedding w
     286                          ->  Seq Scan on guest g
     287                    ->  Seq Scan on event_rsvp r  (rows=119)
     288              ->  Seq Scan on attendance a  (rows=40)
     289
     290**Planning Time:  1.314 ms
     291Execution Time: 1.122 ms**
     292}}}
     293
     294==== Performance Comparison ====
     295
     296||= Metric =||= Without Indexes =||= With Indexes =||= Improvement =||
     297|| Planning Time || 1.926 ms || 1.314 ms || -31.8% ||
     298|| Execution Time || 1.254 ms || 1.122 ms || -10.5% ||
     299|| Total Query Cost || 108.70..137.20 || 91.82..120.32 || -15.6% ||
     300|| attendance rows estimated || 370 || 40 || -89.2% ||
     301|| Scan Type || Seq Scan (all tables) || Seq Scan (all tables) || — ||
     302
     303==== Interpretation ====
     304
     305The most significant improvement in this scenario is the attendance table row estimation: PostgreSQL now correctly estimates '''40 rows''' instead of 370, reflecting accurate statistics collected during index creation. This results in a '''15.6% reduction in total estimated query cost''' (137.20 → 120.32).
     306
     307Sequential Scans are retained on all tables because the current dataset is small. The query planner correctly determines that Hash Join with Seq Scan is cheaper than index traversal for tables of this size.
     308
     309The new indexes `idx_event_rsvp_status` and `idx_attendance_status` enable efficient status-based filtering for the `CASE WHEN r.status = 'accepted'` and `CASE WHEN a.status = 'attending'` conditional aggregations. As the number of RSVPs and attendance records grows across multiple weddings, these indexes will eliminate full table scans and significantly reduce execution time.
     310
     311----
     312
     313== Security Measures ==
     314
     315=== SQL Injection Prevention ===
     316
     317The Wedding Planner backend (Node.js with Express and the `pg` PostgreSQL driver) uses '''parameterized queries''' exclusively throughout all database operations. User input is never concatenated directly into SQL strings.
     318
     319All query parameters are passed as separate values using the `$1`, `$2`, `$n` placeholder syntax provided by the `pg` library. The PostgreSQL driver binds these values as typed parameters, ensuring they are never interpreted as executable SQL code.
     320
     321{{{
     322#!javascript
     323// Example: parameterized query for guest lookup
     324const result = await pool.query(
     325    `SELECT * FROM project.guest
     326     WHERE wedding_id = $1
     327     AND guest_id = $2`,
     328    [weddingId, guestId]
    1372329);
    1373 
    1374 COMMIT;
    1375 }}}
    1376 
    1377 === Explanation ===
    1378 
    1379 `FOR UPDATE` locks the selected venue row during the transaction.
    1380 
    1381 This prevents concurrent transactions from simultaneously booking the same venue for overlapping dates.
    1382 
    1383 The `NOT EXISTS` condition ensures that no conflicting booking already exists.
    1384 
    1385 === Validation ===
    1386 
    1387 {{{
    1388 #!sql
    1389 SELECT
    1390     venue_id,
    1391     date,
    1392     COUNT(*)
    1393 
    1394 FROM venue_booking
    1395 
    1396 WHERE status != 'CANCELLED'
    1397 
    1398 GROUP BY
    1399     venue_id,
    1400     date
    1401 
    1402 HAVING COUNT(*) > 1;
    1403 }}}
    1404 
    1405 Expected result:
    1406 * empty result set
    1407 
    1408 Any returned rows indicate conflicting venue bookings.
    1409 
    1410 === 4.5 Row-Level Locking ===
    1411 
    1412 The system primarily relies on:
    1413 * row-level locks
    1414 * transaction isolation
    1415 * foreign-key consistency
    1416 
    1417 Row-level locking is preferred because it:
    1418 * minimizes blocking
    1419 * improves concurrency
    1420 * prevents unnecessary table-wide locks
    1421 
    1422 === 4.6 Transaction Scalability ===
    1423 
    1424 As the number of weddings and concurrent users increases, transaction management becomes increasingly important.
    1425 
    1426 The following practices improve scalability:
    1427 * keeping transactions short
    1428 * indexing transactional lookup columns
    1429 * separating analytical reports from transactional operations
    1430 * avoiding long-running locks
    1431 
    1432 === 4.7 Final Interpretation ===
    1433 
    1434 The Wedding Planner Management System contains several scheduling and booking operations that require transactional consistency.
    1435 
    1436 PostgreSQL transaction isolation and row-level locking mechanisms help prevent:
    1437 * double-booking
    1438 * inconsistent RSVP updates
    1439 * concurrent attendance conflicts
    1440 * invalid scheduling states
    1441 
    1442 Proper transaction management ensures both:
    1443 * data integrity
    1444 * stable concurrent system behavior
    1445 
    1446 == 5. Performance Analysis with EXPLAIN / EXPLAIN ANALYZE ==
    1447 
    1448 === 5.1 Purpose of EXPLAIN and EXPLAIN ANALYZE ===
    1449 
    1450 PostgreSQL provides several commands for analyzing query execution behavior.
    1451 
    1452 || Command || Purpose ||
    1453 || EXPLAIN || Shows the planned execution strategy without running the query ||
    1454 || EXPLAIN ANALYZE || Executes the query and shows real execution statistics ||
    1455 || EXPLAIN (ANALYZE, BUFFERS) || Shows execution statistics plus buffer/cache usage ||
    1456 
    1457 In this phase, `EXPLAIN ANALYZE` is used to evaluate the complex analytical queries from Phase 6.
    1458 
    1459 It helps identify:
    1460 * scan types
    1461 * join strategies
    1462 * aggregation methods
    1463 * actual execution time
    1464 * row counts
    1465 * loops
    1466 * possible bottlenecks
    1467 
    1468 === 5.2 Important Execution Plan Elements ===
    1469 
    1470 || Plan Element || Meaning ||
    1471 || Seq Scan || PostgreSQL scans the entire table ||
    1472 || Index Scan || PostgreSQL uses an index to locate rows faster ||
    1473 || Hash Join || PostgreSQL builds a hash table to join larger datasets ||
    1474 || Nested Loop || PostgreSQL repeatedly scans one relation for each row of another relation ||
    1475 || !HashAggregate || PostgreSQL performs grouping and aggregation using a hash table ||
    1476 || Sort || PostgreSQL sorts rows for ORDER BY or aggregation operations ||
    1477 
    1478 For Phase 6 reports, the most common expected elements are:
    1479 * Hash Join
    1480 * !HashAggregate
    1481 * Index Scan
    1482 * Seq Scan on small tables
    1483 * Sort
    1484 
    1485 === 5.3 Sequential Scan vs Index Scan ===
    1486 
    1487 A Sequential Scan is not always a problem.
    1488 
    1489 PostgreSQL may choose a Sequential Scan when:
    1490 * the table is small
    1491 * most rows are needed
    1492 * the cost of using an index is higher than scanning the table
    1493 
    1494 However, for large Phase 6 analytical tables such as:
    1495 * attendance
    1496 * event_rsvp
    1497 * guest
    1498 * booking tables
    1499 
    1500 Index Scans are preferred when filtering or joining by foreign-key columns.
    1501 
    1502 === 5.4 Example: Attendance Lookup Before Optimization ===
    1503 
    1504 {{{
    1505 #!sql
    1506 EXPLAIN ANALYZE
    1507 SELECT *
    1508 FROM attendance
    1509 WHERE event_id = 1;
    1510 }}}
    1511 
    1512 Without an index on `attendance(event_id)`, PostgreSQL may use:
    1513 
    1514 {{{
    1515 Seq Scan on attendance
    1516   Filter: (event_id = 1)
    1517 }}}
    1518 
    1519 This means that all attendance rows are scanned before matching rows are returned.
    1520 
    1521 For large attendance tables, this increases:
    1522 * disk I/O
    1523 * CPU usage
    1524 * query latency
    1525 
    1526 === 5.5 Example: Attendance Lookup After Optimization ===
    1527 
    1528 {{{
    1529 #!sql
    1530 CREATE INDEX idx_attendance_event
    1531 ON attendance(event_id);
    1532 
    1533 EXPLAIN ANALYZE
    1534 SELECT *
    1535 FROM attendance
    1536 WHERE event_id = 1;
    1537 }}}
    1538 
    1539 Observed execution plan:
    1540 
    1541 {{{
    1542 Index Scan using idx_attendance_event on attendance
    1543   Index Cond: (event_id = 1)
    1544 }}}
    1545 
    1546 This confirms that PostgreSQL can directly locate attendance records for a specific event.
    1547 
    1548 === 5.6 EXPLAIN ANALYZE for Phase 6 Reports ===
    1549 
    1550 The Phase 6 reports should be analyzed using EXPLAIN ANALYZE because they include:
    1551 * joins across multiple tables
    1552 * aggregation
    1553 * distinct counting
    1554 * conditional calculations
    1555 
    1556 The analysis should be performed on:
    1557 * Budget Analysis query
    1558 * Venue Capacity query
    1559 * RSVP Conversion query
    1560 
    1561 These queries represent the real analytical workload of the Wedding Planner Management System.
    1562 
    1563 === 5.7 Interpreting Execution Time ===
    1564 
    1565 When reading EXPLAIN ANALYZE output, the most important values are:
    1566 * Planning Time
    1567 * Execution Time
    1568 * actual rows
    1569 * loops
    1570 * scan type
    1571 * join type
    1572 
    1573 High execution time may indicate:
    1574 * missing indexes
    1575 * inefficient JOIN order
    1576 * expensive aggregation
    1577 * large intermediate result sets
    1578 * outdated PostgreSQL statistics
    1579 
    1580 === 5.8 Validation of Index Usage ===
    1581 
    1582 After creating indexes, index usage can be checked using:
    1583 
    1584 {{{
    1585 #!sql
    1586 SELECT
    1587     indexrelname AS index_name,
    1588     idx_scan AS times_used,
    1589     idx_tup_read AS tuples_read,
    1590     idx_tup_fetch AS tuples_fetched
    1591 
    1592 FROM pg_stat_user_indexes
    1593 
    1594 WHERE indexrelname IN (
    1595     'idx_attendance_event',
    1596     'idx_guest_wedding',
    1597     'idx_event_rsvp_guest',
    1598     'idx_venue_booking_wedding'
     330}}}
     331
     332{{{
     333#!javascript
     334// Example: parameterized INSERT for new booking
     335await pool.query(
     336    `INSERT INTO project.venue_booking
     337     (date, start_time, end_time, status, price, venue_id, wedding_id)
     338     VALUES ($1, $2, $3, $4, $5, $6, $7)`,
     339    [date, startTime, endTime, status, price, venueId, weddingId]
    1599340);
    1600341}}}
    1601342
    1602 If `idx_scan` increases after report execution, the index is being used.
    1603 
    1604 If `idx_scan` remains 0, the index may be unused or the query planner may prefer another execution strategy.
    1605 
    1606 === 5.9 Summary ===
    1607 
    1608 EXPLAIN ANALYZE is essential for validating the performance of the Phase 6 analytical reports.
    1609 
    1610 It helps confirm whether:
    1611 * indexes are used correctly
    1612 * joins are efficient
    1613 * aggregation is acceptable
    1614 * execution time is reasonable
    1615 
    1616 This makes EXPLAIN ANALYZE an important part of database performance tuning and report optimization.
    1617 
    1618 == 6. Security Architecture & Data Protection ==
    1619 
    1620 === 6.1 Authentication & Authorization ===
    1621 
    1622 The Wedding Planner Management System stores:
    1623 * wedding schedules
    1624 * guest information
    1625 * RSVP records
    1626 * attendance data
    1627 * booking information
    1628 
    1629 Because the system contains sensitive personal and operational data, controlled database access is required.
    1630 
    1631 The recommended approach is Role-Based Access Control (RBAC).
    1632 
    1633 === Recommended Roles ===
    1634 
    1635 || Role || Permissions || Restrictions ||
    1636 || Guest User || Read personal RSVP information || Cannot modify wedding data ||
    1637 || Wedding Organizer || CRUD operations for own weddings and events || Cannot access unrelated weddings ||
    1638 || Administrator || Full database access || Restricted to authorized personnel only ||
    1639 
    1640 === PostgreSQL Role Implementation ===
    1641 
    1642 {{{
    1643 #!sql
    1644 CREATE ROLE wedding_guest;
    1645 
    1646 GRANT CONNECT ON DATABASE wedding_planner
    1647 TO wedding_guest;
    1648 
    1649 GRANT SELECT ON guest, event_rsvp
    1650 TO wedding_guest;
    1651 
    1652 CREATE ROLE wedding_organizer;
    1653 
    1654 GRANT SELECT, INSERT, UPDATE
    1655 ON wedding, event, guest, event_rsvp, attendance, venue_booking
    1656 TO wedding_organizer;
    1657 
    1658 CREATE ROLE wedding_admin;
    1659 
    1660 GRANT ALL PRIVILEGES
    1661 ON ALL TABLES IN SCHEMA public
    1662 TO wedding_admin;
    1663 }}}
    1664 
    1665 === Row-Level Security ===
    1666 
    1667 Row-Level Security (RLS) can restrict organizers to accessing only their own weddings.
    1668 
    1669 {{{
    1670 #!sql
    1671 ALTER TABLE wedding
    1672 ENABLE ROW LEVEL SECURITY;
    1673 
    1674 CREATE POLICY wedding_access_policy
    1675 ON wedding
    1676 
    1677 USING (
    1678     user_id =
    1679     current_setting('app.current_user_id')::INTEGER
     343This approach ensures that even if a user submits malicious input such as `' OR 1=1 --`, it is treated as a literal string value and never executed as SQL.
     344
     345=== Password Security ===
     346
     347User passwords are never stored in plain text. The application uses the `bcrypt` library to hash passwords before storing them in the database, providing protection against brute-force and dictionary attacks.
     348
     349{{{
     350#!javascript
     351const bcrypt = require('bcrypt');
     352const SALT_ROUNDS = 10;
     353
     354// Hashing password on registration
     355const passwordHash = await bcrypt.hash(password, SALT_ROUNDS);
     356
     357await pool.query(
     358    `INSERT INTO project."user"
     359     (first_name, last_name, email, password_hash, phone_number, gender)
     360     VALUES ($1, $2, $3, $4, $5, $6)`,
     361    [firstName, lastName, email, passwordHash, phone, gender]
    1680362);
    1681 }}}
    1682 
    1683 === Explanation ===
    1684 
    1685 This policy ensures that each organizer can only access weddings linked to their own user account.
    1686 
    1687 This prevents unauthorized access to:
    1688 * guest lists
    1689 * attendance records
    1690 * RSVP information
    1691 * booking information
    1692 
    1693 === Validation ===
    1694 
    1695 {{{
    1696 #!sql
    1697 SELECT *
    1698 FROM wedding;
    1699 }}}
    1700 
    1701 Expected behavior:
    1702 * users only see weddings associated with their own user_id
    1703 
    1704 === Security Benefits ===
    1705 
    1706 The proposed RBAC and RLS configuration improves:
    1707 * access control
    1708 * data isolation
    1709 * organizer privacy
    1710 * protection against unauthorized data access
    1711 
    1712 These mechanisms are especially important in multi-user environments where several organizers use the system simultaneously.
    1713 
    1714 === 6.2 Encryption Strategy ===
    1715 
    1716 ==== Encryption in Transit ====
    1717 
    1718 All communication between the application and PostgreSQL database should use encrypted connections.
    1719 
    1720 Recommended protections:
    1721 * TLS-secured database connections
    1722 * encrypted API communication
    1723 * secure administrator access
    1724 
    1725 These protections prevent interception of:
    1726 * guest information
    1727 * RSVP records
    1728 * attendance data
    1729 * wedding schedules
    1730 
    1731 ==== Encryption at Rest ====
    1732 
    1733 Sensitive information stored inside the database should be protected using encryption mechanisms.
    1734 
    1735 Sensitive fields include:
    1736 * phone numbers
    1737 * email addresses
    1738 * guest notes
    1739 * RSVP comments
    1740 
    1741 PostgreSQL extensions such as `pgcrypto` can be used for column-level encryption.
    1742 
    1743 ==== Example: pgcrypto Encryption ====
    1744 
    1745 {{{
    1746 #!sql
    1747 INSERT INTO guest (
    1748     first_name,
    1749     last_name,
    1750     email
    1751 )
    1752 VALUES (
    1753     'Ivan',
    1754     'Petrov',
    1755     pgp_sym_encrypt(
    1756         'ivan@email.com',
    1757         'wedding_secret_key'
    1758     )
    1759 );
    1760 }}}
    1761 
    1762 ==== Example: Decryption ====
    1763 
    1764 {{{
    1765 #!sql
    1766 SELECT
    1767     first_name,
    1768     last_name,
    1769 
    1770     pgp_sym_decrypt(
    1771         email::bytea,
    1772         'wedding_secret_key'
    1773     ) AS decrypted_email
    1774 
    1775 FROM guest;
    1776 }}}
    1777 
    1778 ==== Implementation Note ====
    1779 
    1780 The encryption example demonstrates the conceptual usage of PostgreSQL `pgcrypto`.
    1781 
    1782 In a production implementation, encrypted values should be stored in dedicated encrypted columns (for example `email_encrypted BYTEA`) instead of replacing the original plaintext column directly.
    1783 
    1784 This approach improves schema consistency and avoids datatype conflicts during encryption and decryption operations.
    1785 
    1786 ==== Validation ====
    1787 
    1788 {{{
    1789 #!sql
    1790 SELECT email
    1791 FROM guest;
    1792 }}}
    1793 
    1794 Expected result:
    1795 * encrypted binary data instead of readable plaintext
    1796 
    1797 === Security Benefits ===
    1798 
    1799 Encryption improves:
    1800 * protection of sensitive guest data
    1801 * privacy of wedding participants
    1802 * protection against unauthorized database access
    1803 * compliance with modern data-protection practices
    1804 
    1805 === 6.3 Data Masking & Anonymization ===
    1806 
    1807 The Wedding Planner Management System stores sensitive personal information related to wedding guests and organizers.
    1808 
    1809 For analytical and testing environments, sensitive information should be masked or anonymized.
    1810 
    1811 Sensitive information includes:
    1812 * guest names
    1813 * phone numbers
    1814 * email addresses
    1815 * RSVP comments
    1816 
    1817 === Example: Masked Reporting View ===
    1818 
    1819 {{{
    1820 #!sql
    1821 CREATE VIEW v_guest_reporting AS
    1822 
    1823 SELECT
    1824     guest_id,
    1825 
    1826     CONCAT(
    1827         LEFT(first_name, 1),
    1828         REPEAT('*', LENGTH(first_name) - 1)
    1829     ) AS masked_first_name,
    1830 
    1831     CONCAT(
    1832         LEFT(last_name, 1),
    1833         REPEAT('*', LENGTH(last_name) - 1)
    1834     ) AS masked_last_name,
    1835 
    1836     CONCAT(
    1837         REPEAT('*', 5),
    1838         RIGHT(email, POSITION('@' IN email))
    1839     ) AS masked_email,
    1840 
    1841     wedding_id
    1842 
    1843 FROM guest;
    1844 }}}
    1845 
    1846 === Explanation ===
    1847 
    1848 The view masks personally identifiable information while still allowing analytical reporting.
    1849 
    1850 This approach is useful for:
    1851 * testing environments
    1852 * reporting dashboards
    1853 * analytical exports
    1854 * demonstration systems
    1855 
    1856 === Validation ===
    1857 
    1858 {{{
    1859 #!sql
    1860 SELECT *
    1861 FROM v_guest_reporting;
    1862 }}}
    1863 
    1864 Expected result:
    1865 * masked guest names
    1866 * masked email addresses
    1867 * preserved wedding relationships
    1868 
    1869 === Security Benefits ===
    1870 
    1871 Data masking improves:
    1872 * privacy protection
    1873 * safer analytical reporting
    1874 * reduced exposure of sensitive data
    1875 * compliance with data-protection principles
    1876 
    1877 === 6.4 SQL Injection Prevention ===
    1878 
    1879 The Wedding Planner Management System accepts user-generated input through:
    1880 * RSVP forms
    1881 * guest registration
    1882 * wedding creation
    1883 * event scheduling
    1884 * attendance management
    1885 
    1886 If SQL queries are constructed incorrectly, malicious input may compromise database security.
    1887 
    1888 === Unsafe Query Example ===
    1889 
    1890 The following example is vulnerable to SQL injection:
    1891 
    1892 {{{
    1893 #!sql
    1894 query =
    1895 "SELECT * FROM guest WHERE first_name = '" + user_input + "'"
    1896 }}}
    1897 
    1898 This approach allows attackers to manipulate SQL syntax through malicious input.
    1899 
    1900 === Secure Parameterized Query ===
    1901 
    1902 The recommended approach is parameterized execution.
    1903 
    1904 {{{
    1905 #!sql
    1906 PREPARE guest_lookup(TEXT) AS
    1907 
    1908 SELECT
    1909     guest_id,
    1910     first_name,
    1911     last_name,
    1912     wedding_id
    1913 
    1914 FROM guest
    1915 
    1916 WHERE first_name = $1;
    1917 }}}
    1918 
    1919 === Example Execution ===
    1920 
    1921 {{{
    1922 #!sql
    1923 EXECUTE guest_lookup('Ivan');
    1924 }}}
    1925 
    1926 === Explanation ===
    1927 
    1928 Parameterized queries separate:
    1929 * SQL structure
    1930 * user-provided input
    1931 
    1932 This prevents user input from being interpreted as executable SQL code.
    1933 
    1934 === Security Benefits ===
    1935 
    1936 Parameterized queries protect against:
    1937 * unauthorized data access
    1938 * SQL injection attacks
    1939 * manipulation of RSVP data
    1940 * unauthorized attendance modification
    1941 * wedding data corruption
    1942 
    1943 === Recommended Practices ===
    1944 
    1945 The following practices are recommended throughout the system:
    1946 * parameterized queries
    1947 * input validation
    1948 * restricted database permissions
    1949 * transaction isolation
    1950 * prepared statements
    1951 
    1952 These protections significantly improve overall database security.
    1953 
    1954 === 6.5 GDPR Compliance ===
    1955 
    1956 The Wedding Planner Management System stores personal information related to:
    1957 * wedding organizers
    1958 * guests
    1959 * RSVP records
    1960 * attendance information
    1961 
    1962 Because the system processes personal data, several GDPR-related principles should be respected.
    1963 
    1964 === Data Minimization ===
    1965 
    1966 Only information necessary for wedding organization should be stored.
    1967 
    1968 Examples:
    1969 * guest names
    1970 * RSVP responses
    1971 * attendance status
    1972 * organizer contact information
    1973 
    1974 Unnecessary personal information should not be collected.
    1975 
    1976 === Access Control ===
    1977 
    1978 Only authorized users should access:
    1979 * guest information
    1980 * RSVP records
    1981 * attendance reports
    1982 * wedding schedules
    1983 
    1984 Role-Based Access Control and Row-Level Security help enforce this restriction.
    1985 
    1986 === Right to Access ===
    1987 
    1988 Users should be able to:
    1989 * view their stored information
    1990 * verify RSVP information
    1991 * review attendance-related records
    1992 
    1993 === Right to Deletion ===
    1994 
    1995 When requested, personal information should be removable from the database.
    1996 
    1997 Example deletion operation:
    1998 
    1999 {{{
    2000 #!sql
    2001 DELETE FROM guest
    2002 WHERE guest_id = 10;
    2003 }}}
    2004 
    2005 === Backup Protection ===
    2006 
    2007 Backups containing personal information should:
    2008 * remain encrypted
    2009 * be access-controlled
    2010 * be stored securely
    2011 
    2012 === GDPR Benefits ===
    2013 
    2014 Applying GDPR principles improves:
    2015 * privacy protection
    2016 * organizer trust
    2017 * legal compliance
    2018 * secure handling of wedding-related data
    2019 
    2020 === 6.6 Backup, Restore & Disaster Recovery ===
    2021 
    2022 The Wedding Planner Management System stores important operational and personal data.
    2023 
    2024 Database backups are necessary to protect:
    2025 * wedding schedules
    2026 * RSVP records
    2027 * attendance information
    2028 * booking data
    2029 * organizer information
    2030 
    2031 === Recommended Backup Strategy ===
    2032 
    2033 || Backup Type || Frequency ||
    2034 || Full Backup || Daily ||
    2035 || Incremental Backup || Every few hours ||
    2036 || WAL Archiving || Continuous ||
    2037 
    2038 === Backup Validation ===
    2039 
    2040 After backup restoration, database consistency should be verified.
    2041 
    2042 {{{
    2043 #!sql
    2044 SELECT
    2045     'wedding' AS table_name,
    2046     COUNT(*) AS row_count
    2047 FROM wedding
    2048 
    2049 UNION ALL
    2050 
    2051 SELECT
    2052     'guest',
    2053     COUNT(*)
    2054 FROM guest
    2055 
    2056 UNION ALL
    2057 
    2058 SELECT
    2059     'event',
    2060     COUNT(*)
    2061 FROM event
    2062 
    2063 UNION ALL
    2064 
    2065 SELECT
    2066     'attendance',
    2067     COUNT(*)
    2068 FROM attendance;
    2069 }}}
    2070 
    2071 === Explanation ===
    2072 
    2073 The validation query confirms that:
    2074 * important tables were restored correctly
    2075 * row counts are preserved
    2076 * critical wedding data still exists
    2077 
    2078 === Disaster Recovery Recommendations ===
    2079 
    2080 The following practices improve recovery reliability:
    2081 * encrypted backups
    2082 * off-site backup storage
    2083 * periodic restore testing
    2084 * automatic backup scheduling
    2085 
    2086 === Security Benefits ===
    2087 
    2088 Proper backup management improves:
    2089 * data recovery capability
    2090 * protection against accidental deletion
    2091 * recovery after hardware failure
    2092 * long-term database reliability
    2093 
    2094 === 6.7 Audit Logging ===
    2095 
    2096 Audit logging is important for tracking sensitive modifications inside the Wedding Planner Management System.
    2097 
    2098 The system should record:
    2099 * RSVP modifications
    2100 * attendance updates
    2101 * venue booking changes
    2102 * wedding schedule modifications
    2103 * guest list updates
    2104 
    2105 Audit logging improves:
    2106 * accountability
    2107 * traceability
    2108 * security monitoring
    2109 * recovery after accidental modification
    2110 
    2111 === Example Audit Table ===
    2112 
    2113 {{{
    2114 #!sql
    2115 CREATE TABLE audit_log (
    2116 
    2117     audit_id SERIAL PRIMARY KEY,
    2118 
    2119     table_name VARCHAR(100),
    2120 
    2121     operation_type VARCHAR(30),
    2122 
    2123     changed_by VARCHAR(100),
    2124 
    2125     changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    2126 
    2127     affected_record_id INTEGER
    2128 );
    2129 }}}
    2130 
    2131 === Example Trigger Function ===
    2132 
    2133 {{{
    2134 #!sql
    2135 CREATE OR REPLACE FUNCTION log_guest_changes()
    2136 
    2137 RETURNS TRIGGER AS $$
    2138 
    2139 BEGIN
    2140 
    2141     INSERT INTO audit_log (
    2142         table_name,
    2143         operation_type,
    2144         changed_by,
    2145         affected_record_id
    2146     )
    2147 
    2148     VALUES (
    2149         'guest',
    2150         TG_OP,
    2151         CURRENT_USER,
    2152         NEW.guest_id
     363
     364// Verifying password on login
     365const match = await bcrypt.compare(plainPassword, storedHash);
     366}}}
     367
     368The `bcrypt` algorithm applies a salt automatically, ensuring that two identical passwords produce different hash values in the database.
     369
     370=== Session-Based Authentication ===
     371
     372Access to all protected API endpoints is controlled using `express-session`. A `requireAuth` middleware function verifies that a valid session exists before any database operation is executed.
     373
     374{{{
     375#!javascript
     376function requireAuth(req, res, next) {
     377    if (!req.session || !req.session.user) {
     378        return res.status(401).json({ error: 'Not authenticated.' });
     379    }
     380    next();
     381}
     382
     383// Applied to all protected routes
     384app.get('/api/weddings', requireAuth, async (req, res) => {
     385    const result = await pool.query(
     386        `SELECT * FROM project.wedding
     387         WHERE user_id = $1
     388         ORDER BY date DESC`,
     389        [req.session.user.user_id]
    2153390    );
    2154 
    2155     RETURN NEW;
    2156 
    2157 END;
    2158 
    2159 $$ LANGUAGE plpgsql;
    2160 }}}
    2161 
    2162 === Example Trigger ===
    2163 
    2164 {{{
    2165 #!sql
    2166 CREATE TRIGGER trg_guest_audit
    2167 
    2168 AFTER INSERT OR UPDATE
    2169 ON guest
    2170 
     391    res.json(result.rows);
     392});
     393}}}
     394
     395This mechanism ensures that unauthenticated requests are rejected before reaching any database query. All data access is additionally scoped to the authenticated user's own records using `WHERE user_id = $1`, preventing cross-user data exposure.
     396
     397=== Database-Level Protection via Triggers ===
     398
     399In addition to application-level security, the database enforces business rules through triggers that cannot be bypassed by the application layer.
     400
     401Overlap prevention triggers block double-booking for all vendor types:
     402
     403{{{
     404#!sql
     405-- Prevents double-booking of the same venue at overlapping times
     406CREATE TRIGGER trg_venue_booking_overlap
     407BEFORE INSERT OR UPDATE ON project.venue_booking
    2171408FOR EACH ROW
    2172 
    2173 EXECUTE FUNCTION log_guest_changes();
    2174 }}}
    2175 
    2176 === Validation ===
    2177 
    2178 {{{
    2179 #!sql
    2180 SELECT *
    2181 FROM audit_log;
    2182 }}}
    2183 
    2184 Expected result:
    2185 * logged INSERT and UPDATE operations on guest records
    2186 
    2187 === Security Benefits ===
    2188 
    2189 Audit logging improves:
    2190 * monitoring of sensitive changes
    2191 * accountability of database operations
    2192 * recovery investigation
    2193 * detection of suspicious activity
    2194 
    2195 It is especially important for:
    2196 * guest modifications
    2197 * RSVP changes
    2198 * booking updates
    2199 * attendance management
    2200 
    2201 == 7. Final Conclusions ==
    2202 
    2203 Phase 9 extends the Wedding Planner Management System with advanced database engineering concepts focused on:
    2204 * performance analysis
    2205 * query optimization
    2206 * scalability
    2207 * transaction management
    2208 * security
    2209 * analytical workload optimization
    2210 
    2211 Unlike previous phases that focused primarily on schema design and transactional functionality, this phase evaluates how the database behaves under complex analytical workloads generated by the Phase 6 reports.
    2212 
    2213 The analysis focused on:
    2214 * Budget Analysis
    2215 * Venue Capacity Utilization
    2216 * RSVP Conversion Analysis
    2217 
    2218 These reports were analyzed using:
    2219 * EXPLAIN ANALYZE
    2220 * execution-plan interpretation
    2221 * indexing strategies
    2222 * aggregation analysis
    2223 * scalability evaluation
    2224 
    2225 The identified performance bottlenecks include:
    2226 * multiple LEFT JOIN operations
    2227 * COUNT(DISTINCT ...) aggregation
    2228 * GROUP BY operations
    2229 * temporal calculations
    2230 * analytical workload complexity
    2231 
    2232 The proposed indexing strategy improves:
    2233 * JOIN efficiency
    2234 * aggregation preparation
    2235 * analytical reporting speed
    2236 * scalability for larger datasets
    2237 
    2238 The phase also introduced:
    2239 * materialized views
    2240 * partitioning strategies
    2241 * transaction isolation analysis
    2242 * row-level locking
    2243 * role-based access control
    2244 * row-level security
    2245 * encryption strategies
    2246 * audit logging
    2247 * backup and disaster recovery planning
    2248 
    2249 Together, these techniques transform the Wedding Planner Management System from a simple transactional database into a scalable analytical database platform capable of supporting:
    2250 * operational reporting
    2251 * performance monitoring
    2252 * analytical decision-making
    2253 * secure multi-user access
    2254 
    2255 == Final Technical Evaluation ==
    2256 
    2257 The implementation demonstrates:
    2258 * advanced PostgreSQL usage
    2259 * analytical SQL optimization
    2260 * transaction-safe database operations
    2261 * scalable reporting architecture
    2262 * secure relational database design
    2263 
    2264 The Phase 6 analytical reports were successfully integrated into the Phase 9 performance analysis workflow, providing realistic optimization and scalability evaluation over the actual project workload.
    2265 
    2266 == Final Notes ==
    2267 
    2268 All optimization strategies, indexes, security mechanisms, and execution analyses were designed and validated using PostgreSQL 15.
    2269 
    2270 The implementation illustrates how modern relational database systems support both:
    2271 * transactional processing
    2272 * analytical business intelligence workloads
    2273 
    2274 within a unified Wedding Planner Management System.
     409EXECUTE FUNCTION project.check_venue_booking_overlap();
     410
     411-- Same pattern applied to photographer, band, church, registrar
     412CREATE TRIGGER trg_photographer_booking_overlap
     413BEFORE INSERT OR UPDATE ON project.photographer_booking
     414FOR EACH ROW
     415EXECUTE FUNCTION project.check_photographer_booking_overlap();
     416}}}
     417
     418Attendance consistency trigger prevents marking a declined guest as attending:
     419
     420{{{
     421#!sql
     422CREATE TRIGGER trg_attendance_consistency
     423BEFORE INSERT OR UPDATE ON project.attendance
     424FOR EACH ROW
     425EXECUTE FUNCTION project.validate_attendance_consistency();
     426}}}
     427
     428These triggers enforce data integrity at the database level, ensuring correctness regardless of how the data is accessed.