| 26 | | user_id → first_name, last_name, email, phone_number, gender, birthday |
| 27 | | wedding_id → date, budget, notes, user_id |
| 28 | | event_id → event_type, date, start_time, end_time, status, wedding_id |
| 29 | | guest_id → guest_first_name, guest_last_name, email, wedding_id |
| 30 | | response_id → status, response_date, guest_id, event_id |
| 31 | | attendance_id → status, table_number, role, guest_id, event_id |
| 32 | | venue_booking_id → vb_date, vb_start, vb_end, vb_status, vb_price, venue_id, wedding_id |
| 33 | | venue_id → venue_name, location, city, address, capacity, menu, phone_number, price_per_guest, type_id |
| 34 | | type_id → type_name |
| 35 | | photographer_booking_id → date, start_time, end_time, status, photographer_id, wedding_id |
| 36 | | photographer_id → photographer_name, email, phone_number, price_per_hour |
| 37 | | band_booking_id → date, start_time, end_time, status, band_id, wedding_id |
| 38 | | band_id → band_name, band_genre, equipment, phone_number, price_per_hour |
| 39 | | registrar_booking_id → start_time, end_time, price, status, registrar_id, wedding_id |
| 40 | | registrar_id → registrar_name, contact, location, working_hours |
| 41 | | church_id → church_name, location, contact, wedding_id |
| 42 | | priest_id → priest_name, contact, church_id |
| 43 | | |
| 44 | | === Additional Business Constraints === |
| 45 | | |
| 46 | | The following additional dependencies also hold: |
| 47 | | |
| 48 | | {guest_id, event_id} → {response_id, rsvp_status, response_date} |
| 49 | | {guest_id, event_id} → {attendance_id, attendance_status, table_number} |
| 50 | | {venue_id, vb_date, vb_start} → {venue_booking_id} |
| 51 | | {wedding_id} → {venue_booking_id, photographer_booking_id, band_booking_id, registrar_booking_id, church_id} |
| | 26 | * user_id → first_name, last_name, email, phone_number, gender, birthday |
| | 27 | * wedding_id → date, budget, notes, user_id |
| | 28 | * event_id → event_type, date, start_time, end_time, status, wedding_id |
| | 29 | * guest_id → guest_first_name, guest_last_name, email, wedding_id |
| | 30 | * response_id → status, response_date, guest_id, event_id |
| | 31 | * attendance_id → status, table_number, role, guest_id, event_id |
| | 32 | * venue_booking_id → vb_date, vb_start, vb_end, vb_status, vb_price, venue_id, wedding_id |
| | 33 | * venue_id → venue_name, location, city, address, capacity, menu, phone_number, price_per_guest, type_id |
| | 34 | * type_id → type_name |
| | 35 | * photographer_booking_id → date, start_time, end_time, status, photographer_id, wedding_id |
| | 36 | * photographer_id → photographer_name, email, phone_number, price_per_hour |
| | 37 | * band_booking_id → date, start_time, end_time, status, band_id, wedding_id |
| | 38 | * band_id → band_name, band_genre, equipment, phone_number, price_per_hour |
| | 39 | * registrar_booking_id → start_time, end_time, price, status, registrar_id, wedding_id |
| | 40 | * registrar_id → registrar_name, contact, location, working_hours |
| | 41 | * church_id → church_name, location, contact, wedding_id |
| | 42 | * priest_id → priest_name, contact, church_id |
| | 43 | |
| | 44 | === Additional Functional Dependencies === |
| | 45 | |
| | 46 | The following additional functional dependencies also hold in R due to business constraints: |
| | 47 | |
| | 48 | * {guest_id, event_id} → {response_id, rsvp_status, response_date} |
| | 49 | * {guest_id, event_id} → {attendance_id, attendance_status, table_number} |
| | 50 | * {venue_id, vb_date, vb_start} → {venue_booking_id} |
| | 51 | * {wedding_id} → {venue_booking_id, photographer_booking_id, band_booking_id, registrar_booking_id, church_id} |
| 143 | | The original relation violates 2NF because: |
| 144 | | |
| 145 | | user_id → user attributes |
| 146 | | wedding_id → wedding attributes |
| 147 | | venue_id → venue attributes |
| 148 | | |
| 149 | | These dependencies rely only on part of the composite key. |
| 150 | | |
| 151 | | === Decomposition into 2NF Relations === |
| 152 | | |
| 153 | | || Relation || Primary Key || |
| 154 | | || USER || user_id || |
| 155 | | || WEDDING || wedding_id || |
| 156 | | || VENUE_TYPE || type_id || |
| 157 | | || VENUE || venue_id || |
| 158 | | || VENUE_BOOKING || booking_id || |
| 159 | | || PHOTOGRAPHER || photographer_id || |
| 160 | | || PHOTOGRAPHER_BOOKING || booking_id || |
| 161 | | || BAND || band_id || |
| 162 | | || BAND_BOOKING || booking_id || |
| 163 | | || REGISTRAR || registrar_id || |
| 164 | | || REGISTRAR_BOOKING || booking_id || |
| 165 | | || CHURCH || church_id || |
| 166 | | || PRIEST || priest_id || |
| 167 | | || EVENT || event_id || |
| 168 | | || GUEST || guest_id || |
| 169 | | || EVENT_RSVP || response_id || |
| 170 | | || ATTENDANCE || attendance_id || |
| 171 | | |
| 172 | | Each resulting relation contains attributes fully dependent on its primary key. |
| 173 | | |
| 174 | | === Lossless Join Verification === |
| 175 | | |
| 176 | | Every decomposition satisfies the lossless join condition: |
| 177 | | |
| 178 | | (Ri ∩ Rj) → Ri OR (Ri ∩ Rj) → Rj |
| 179 | | |
| 180 | | Examples: |
| 181 | | |
| 182 | | || Join || Common Attribute || Proof || |
| 183 | | || USER ⋈ WEDDING || user_id || user_id is PK in USER || |
| 184 | | || VENUE_BOOKING ⋈ VENUE || venue_id || venue_id is PK in VENUE || |
| 185 | | || EVENT_RSVP ⋈ EVENT || event_id || event_id is PK in EVENT || |
| 186 | | |
| 187 | | Therefore all decompositions are lossless. |
| 188 | | |
| 189 | | == Third Normal Form (3NF) == |
| 190 | | |
| 191 | | === Definition === |
| 192 | | |
| 193 | | A relation is in 3NF if: |
| 194 | | |
| 195 | | It is already in 2NF |
| 196 | | No transitive dependencies exist |
| 197 | | Every non-key attribute depends only on the primary key |
| 198 | | |
| 199 | | === Transitive Dependency Example === |
| 200 | | |
| 201 | | venue_id → type_id → type_name |
| 202 | | |
| 203 | | Here, type_name depends transitively on venue_id through type_id. |
| 204 | | |
| 205 | | === Resolution === |
| 206 | | |
| 207 | | To eliminate the transitive dependency: |
| 208 | | |
| 209 | | VENUE_TYPE(type_id, type_name) is created |
| 210 | | VENUE stores type_id as a foreign key |
| 211 | | |
| 212 | | This removes the transitive dependency while preserving all functional dependencies. |
| 213 | | |
| 214 | | === Final 3NF Relations === |
| 215 | | |
| 216 | | || Relation || Primary Key || Foreign Keys || |
| 217 | | || USER || user_id || — || |
| 218 | | || WEDDING || wedding_id || user_id → USER || |
| 219 | | || VENUE_TYPE || type_id || — || |
| 220 | | || VENUE || venue_id || type_id → VENUE_TYPE || |
| 221 | | || VENUE_BOOKING || booking_id || venue_id → VENUE, wedding_id → WEDDING || |
| 222 | | || PHOTOGRAPHER || photographer_id || — || |
| 223 | | || PHOTOGRAPHER_BOOKING || booking_id || photographer_id → PHOTOGRAPHER, wedding_id → WEDDING || |
| 224 | | || BAND || band_id || — || |
| 225 | | || BAND_BOOKING || booking_id || band_id → BAND, wedding_id → WEDDING || |
| 226 | | || REGISTRAR || registrar_id || — || |
| 227 | | || REGISTRAR_BOOKING || booking_id || registrar_id → REGISTRAR, wedding_id → WEDDING || |
| 228 | | || CHURCH || church_id || wedding_id → WEDDING || |
| 229 | | || PRIEST || priest_id || church_id → CHURCH || |
| 230 | | || EVENT || event_id || wedding_id → WEDDING || |
| 231 | | || GUEST || guest_id || wedding_id → WEDDING || |
| 232 | | || EVENT_RSVP || response_id || guest_id → GUEST, event_id → EVENT || |
| 233 | | || ATTENDANCE || attendance_id || guest_id → GUEST, event_id → EVENT || |
| 234 | | |
| 235 | | == BCNF Verification == |
| 236 | | |
| 237 | | A relation is in BCNF if for every non-trivial dependency: |
| 238 | | |
| 239 | | X → Y |
| 240 | | |
| 241 | | X is a superkey. |
| 242 | | |
| 243 | | All final relations satisfy this condition because every determinant is a candidate key or superkey. |
| 244 | | |
| 245 | | == Final Conclusion == |
| 246 | | |
| 247 | | The Wedding Planner database schema has been formally normalized to Third Normal Form (3NF) and BCNF. |
| 248 | | |
| 249 | | The normalization process: |
| 250 | | |
| 251 | | Eliminates redundancy |
| 252 | | Prevents update anomalies |
| 253 | | Prevents insertion anomalies |
| 254 | | Prevents deletion anomalies |
| 255 | | Preserves functional dependencies |
| 256 | | Guarantees lossless joins |
| 257 | | Ensures entity integrity through verified primary keys |
| 258 | | |
| 259 | | The final schema consists of 17 fully normalized relations. |
| | 177 | Examples of partial dependencies in the original relation: |
| | 178 | |
| | 179 | * user_id → user attributes |
| | 180 | * wedding_id → wedding attributes |
| | 181 | * venue_id → venue attributes |
| | 182 | |
| | 183 | Therefore the original relation violates 2NF. |
| | 184 | |
| | 185 | == 2NF Decomposition == |
| | 186 | |
| | 187 | === R1 – USER === |
| | 188 | |
| | 189 | {user_id} → {first_name, last_name, email, phone_number, gender, birthday} |
| | 190 | |
| | 191 | === Candidate Key Verification === |
| | 192 | |
| | 193 | user_id is the surrogate primary key. |
| | 194 | |
| | 195 | No partial dependencies exist. |
| | 196 | |
| | 197 | === Sample Data === |
| | 198 | |
| | 199 | || user_id || first_name || last_name || email || phone_number || gender || birthday || |
| | 200 | || 1 || Ana || Trajkovska || ana.trajkovska@gmail.com || +38970111222 || Female || 2004-07-12 || |
| | 201 | || 2 || Stefan || Petrovski || stefan.petrovski@gmail.com || +38970333444 || Male || 2003-11-20 || |
| | 202 | |
| | 203 | === Lossless Join Test === |
| | 204 | |
| | 205 | R ∩ R1 = {user_id, first_name, last_name, email, phone_number, gender, birthday} |
| | 206 | |
| | 207 | Since: |
| | 208 | |
| | 209 | user_id → R1 |
| | 210 | |
| | 211 | The decomposition is lossless. |
| | 212 | |
| | 213 | ---- |
| | 214 | |
| | 215 | === R2 – WEDDING === |
| | 216 | |
| | 217 | {wedding_id} → {date, budget, notes, user_id} |
| | 218 | |
| | 219 | === Candidate Key Verification === |
| | 220 | |
| | 221 | wedding_id is the surrogate primary key. |
| | 222 | |
| | 223 | === Sample Data === |
| | 224 | |
| | 225 | || wedding_id || date || budget || notes || user_id || |
| | 226 | || 1 || 2026-06-20 || 8500.00 || Summer wedding || 1 || |
| | 227 | || 2 || 2026-09-05 || 12000.00 || Elegant indoor wedding || 2 || |
| | 228 | |
| | 229 | === Lossless Join Test === |
| | 230 | |
| | 231 | R1.1 ∩ R2 = {wedding_id, date, budget, notes, user_id} |
| | 232 | |
| | 233 | Since: |
| | 234 | |
| | 235 | wedding_id → R2 |
| | 236 | |
| | 237 | The decomposition is lossless. |
| | 238 | |
| | 239 | ---- |
| | 240 | |
| | 241 | === R3 – VENUE_TYPE === |
| | 242 | |
| | 243 | {type_id} → {type_name} |
| | 244 | |
| | 245 | === Candidate Key Verification === |
| | 246 | |
| | 247 | type_id is the surrogate primary key. |
| | 248 | |
| | 249 | === Sample Data === |
| | 250 | |
| | 251 | || type_id || type_name || |
| | 252 | || 1 || Restaurant || |
| | 253 | || 2 || Wedding Hall || |
| | 254 | || 3 || Outdoor Garden || |
| | 255 | |
| | 256 | === Lossless Join Test === |
| | 257 | |
| | 258 | R2.1 ∩ R3 = {type_id, type_name} |
| | 259 | |
| | 260 | Since: |
| | 261 | |
| | 262 | type_id → type_name |
| | 263 | |
| | 264 | The decomposition is lossless. |
| | 265 | |
| | 266 | ---- |
| | 267 | |
| | 268 | === R4 – VENUE === |
| | 269 | |
| | 270 | VENUE( |
| | 271 | venue_id, |
| | 272 | name, |
| | 273 | location, |
| | 274 | city, |
| | 275 | address, |
| | 276 | capacity, |
| | 277 | menu, |
| | 278 | phone_number, |
| | 279 | price_per_guest, |
| | 280 | type_id |
| | 281 | ) |
| | 282 | |
| | 283 | === Candidate Key Verification === |
| | 284 | |
| | 285 | venue_id is the surrogate primary key. |
| | 286 | |
| | 287 | === Sample Data === |
| | 288 | |
| | 289 | || venue_id || name || location || city || address || capacity || menu || phone_number || price_per_guest || type_id || |
| | 290 | || 1 || Lakeside Garden || Matka || Skopje || Matka 12 || 200 || Garden menu || +38971123456 || 35 || 3 || |
| | 291 | || 2 || Royal Hall || Centar || Skopje || Main St 5 || 350 || Full menu || +38972234567 || 45 || 2 || |
| | 292 | |
| | 293 | === Lossless Join Test === |
| | 294 | |
| | 295 | R3.1 ∩ R4 = {venue_id, name, location, city, address, capacity, menu, phone_number, price_per_guest, type_id} |
| | 296 | |
| | 297 | Since: |
| | 298 | |
| | 299 | venue_id → R4 |
| | 300 | |
| | 301 | The decomposition is lossless. |
| | 302 | |
| | 303 | ---- |
| | 304 | |
| | 305 | === R5 – VENUE_BOOKING === |
| | 306 | |
| | 307 | {booking_id} → {date, start_time, end_time, status, price, venue_id, wedding_id} |
| | 308 | |
| | 309 | === Candidate Key Verification === |
| | 310 | |
| | 311 | booking_id is the surrogate primary key. |
| | 312 | |
| | 313 | === Sample Data === |
| | 314 | |
| | 315 | || booking_id || date || start_time || end_time || status || price || venue_id || wedding_id || |
| | 316 | || 1 || 2026-06-20 || 16:00 || 23:00 || confirmed || 7000 || 1 || 1 || |
| | 317 | || 2 || 2026-09-05 || 17:00 || 23:30 || confirmed || 9500 || 2 || 2 || |
| | 318 | |
| | 319 | === Lossless Join Test === |
| | 320 | |
| | 321 | R4.1 ∩ R5 = {venue_booking_id, date, start_time, end_time, status, price, venue_id, wedding_id} |
| | 322 | |
| | 323 | Since: |
| | 324 | |
| | 325 | venue_booking_id → R5 |
| | 326 | |
| | 327 | The decomposition is lossless. |
| | 328 | |
| | 329 | ---- |
| | 330 | |
| | 331 | === R6 – PHOTOGRAPHER === |
| | 332 | |
| | 333 | {photographer_id} → {name, email, phone_number, price_per_hour} |
| | 334 | |
| | 335 | === Candidate Key Verification === |
| | 336 | |
| | 337 | photographer_id is the surrogate primary key. |
| | 338 | |
| | 339 | === Sample Data === |
| | 340 | |
| | 341 | || photographer_id || name || email || phone_number || price_per_hour || |
| | 342 | || 1 || Luna Studio || luna@studio.mk || +38970101010 || 55 || |
| | 343 | || 2 || Golden Frame || golden@frame.mk || +38970202020 || 65 || |
| | 344 | |
| | 345 | === Lossless Join Test === |
| | 346 | |
| | 347 | R5.1 ∩ R6 = {photographer_id, name, email, phone_number, price_per_hour} |
| | 348 | |
| | 349 | Since: |
| | 350 | |
| | 351 | photographer_id → R6 |
| | 352 | |
| | 353 | The decomposition is lossless. |
| | 354 | |
| | 355 | ---- |
| | 356 | |
| | 357 | === R7 – PHOTOGRAPHER_BOOKING === |
| | 358 | |
| | 359 | {booking_id} → {date, start_time, end_time, status, photographer_id, wedding_id} |
| | 360 | |
| | 361 | === Candidate Key Verification === |
| | 362 | |
| | 363 | booking_id is the surrogate primary key. |
| | 364 | |
| | 365 | === Sample Data === |
| | 366 | |
| | 367 | || booking_id || date || start_time || end_time || status || photographer_id || wedding_id || |
| | 368 | || 1 || 2026-06-20 || 14:00 || 22:00 || confirmed || 1 || 1 || |
| | 369 | || 2 || 2026-09-05 || 12:00 || 21:00 || pending || 2 || 2 || |
| | 370 | |
| | 371 | === Lossless Join Test === |
| | 372 | |
| | 373 | R6.1 ∩ R7 = {photographer_booking_id, date, start_time, end_time, status, photographer_id, wedding_id} |
| | 374 | |
| | 375 | Since: |
| | 376 | |
| | 377 | photographer_booking_id → R7 |
| | 378 | |
| | 379 | The decomposition is lossless. |
| | 380 | |
| | 381 | ---- |
| | 382 | |
| | 383 | === R8 – BAND === |
| | 384 | |
| | 385 | {band_id} → {band_name, genre, equipment, phone_number, price_per_hour} |
| | 386 | |
| | 387 | === Candidate Key Verification === |
| | 388 | |
| | 389 | band_id is the surrogate primary key. |
| | 390 | |
| | 391 | === Sample Data === |
| | 392 | |
| | 393 | || band_id || band_name || genre || equipment || phone_number || price_per_hour || |
| | 394 | || 1 || Wedding Vibes || Pop || Sound + lights || +38970909090 || 80 || |
| | 395 | || 2 || Balkan Groove || Traditional || Full instruments || +38970707070 || 95 || |
| | 396 | |
| | 397 | === Lossless Join Test === |
| | 398 | |
| | 399 | R7.1 ∩ R8 = {band_id, band_name, genre, equipment, phone_number, price_per_hour} |
| | 400 | |
| | 401 | Since: |
| | 402 | |
| | 403 | band_id → R8 |
| | 404 | |
| | 405 | The decomposition is lossless. |
| | 406 | |
| | 407 | ---- |
| | 408 | |
| | 409 | === R9 – BAND_BOOKING === |
| | 410 | |
| | 411 | {booking_id} → {date, start_time, end_time, status, band_id, wedding_id} |
| | 412 | |
| | 413 | === Candidate Key Verification === |
| | 414 | |
| | 415 | booking_id is the surrogate primary key. |
| | 416 | |
| | 417 | === Sample Data === |
| | 418 | |
| | 419 | || booking_id || date || start_time || end_time || status || band_id || wedding_id || |
| | 420 | || 1 || 2026-06-20 || 18:00 || 23:00 || confirmed || 1 || 1 || |
| | 421 | || 2 || 2026-09-05 || 19:00 || 23:30 || confirmed || 2 || 2 || |
| | 422 | |
| | 423 | === Lossless Join Test === |
| | 424 | |
| | 425 | R8.1 ∩ R9 = {band_booking_id, date, start_time, end_time, status, band_id, wedding_id} |
| | 426 | |
| | 427 | Since: |
| | 428 | |
| | 429 | band_booking_id → R9 |
| | 430 | |
| | 431 | The decomposition is lossless. |
| | 432 | |
| | 433 | ---- |