| 191 | | Therefore, K+ determines all attributes of R, so K is a superkey. It is minimal because removing any identifier loses one independent component of the universal relation: notification, application, listing, health record, review, favorite pair, or unavailable slot. |
| | 191 | Therefore, K+ determines all attributes of R, so K is a superkey. |
| | 192 | |
| | 193 | === Proof that there are no other candidate keys |
| | 194 | |
| | 195 | To promote K from only "a candidate key" to the chosen primary key, we also need to show that no other candidate key exists. |
| | 196 | |
| | 197 | In the given set of functional dependencies, the following identifiers represent independent components of the universal relation: |
| | 198 | {{{ |
| | 199 | notification_id, |
| | 200 | application_id, |
| | 201 | listing_id, |
| | 202 | healthrecord_id, |
| | 203 | review_id, |
| | 204 | favorite_user_id, |
| | 205 | favorite_listing_id, |
| | 206 | slot_id |
| | 207 | }}} |
| | 208 | |
| | 209 | None of these attributes is functionally determined by another determinant in the given FD set: |
| | 210 | * notification_id does not appear on the right-hand side of any FD, so it cannot be derived. |
| | 211 | * application_id does not appear on the right-hand side of any FD, so it cannot be derived. |
| | 212 | * listing_id does not appear on the right-hand side of any FD, so it cannot be derived. |
| | 213 | * healthrecord_id does not appear on the right-hand side of any FD, so it cannot be derived. |
| | 214 | * review_id does not appear on the right-hand side of any FD, so it cannot be derived. |
| | 215 | * favorite_user_id and favorite_listing_id identify the saved_by relationship. Since FavoriteListing has no non-trivial FD that derives either component, both are required. |
| | 216 | * slot_id does not appear on the right-hand side of any FD, so it cannot be derived. |
| | 217 | |
| | 218 | Therefore every superkey of R must contain all attributes in: |
| | 219 | {{{ |
| | 220 | E = {notification_id, application_id, listing_id, healthrecord_id, review_id, |
| | 221 | favorite_user_id, favorite_listing_id, slot_id} |
| | 222 | }}} |
| | 223 | |
| | 224 | Since K = E and K+ = R, K is a superkey. Since every superkey must contain E, no smaller key can exist. Also, no different candidate key can be formed by replacing one of these attributes with another attribute, because none of the required attributes in E can be derived from any other FD determinant. |
| | 225 | |
| | 226 | Thus K is the only candidate key of the initial de-normalized relation under the stated functional dependencies. |
| 255 | | The decomposition is lossless because each decomposed relation is formed around a determinant that becomes the key of that relation. Foreign keys preserve the relationships between the decomposed relations. |
| | 290 | We use the binary lossless decomposition criterion: |
| | 291 | For a decomposition of relation R into R1 and R2, the decomposition is lossless if: |
| | 292 | {{{ |
| | 293 | (R1 ∩ R2) -> R1 |
| | 294 | or |
| | 295 | (R1 ∩ R2) -> R2 |
| | 296 | }}} |
| | 297 | |
| | 298 | The 2NF decomposition can be seen as repeated binary decompositions. At each step, a relation is split off using a determinant X, and X remains in the remaining relation as the join attribute. Since X functionally determines all attributes in the new relation, the join is lossless. |
| | 299 | |
| | 300 | Step-by-step: |
| | 301 | {{{ |
| | 302 | 1. Split Users(user_id, username, email, name, surname, password_hash, created_at) |
| | 303 | Intersection with remaining relation: user_id |
| | 304 | FD1 gives user_id -> Users |
| | 305 | Therefore the join is lossless. |
| | 306 | |
| | 307 | 2. Split Admins(user_id) |
| | 308 | Intersection with remaining relation: user_id |
| | 309 | user_id is the key of Admins |
| | 310 | Therefore the join is lossless. |
| | 311 | |
| | 312 | 3. Split Clients(user_id, is_blocked, blocked_at, blocked_reason, blocked_by) |
| | 313 | Intersection with remaining relation: user_id |
| | 314 | FD3 gives client_user_id -> client attributes, and in the final schema client_user_id is represented as user_id |
| | 315 | Therefore the join is lossless. |
| | 316 | |
| | 317 | 4. Split Owners(user_id) |
| | 318 | Intersection with remaining relation: user_id |
| | 319 | user_id is the key of Owners |
| | 320 | Therefore the join is lossless. |
| | 321 | |
| | 322 | 5. Split VetClinics(clinic_id, user_id, name, email, phone, location, city, address, work_days, start_time, end_time) |
| | 323 | Intersection with remaining relation: clinic_id |
| | 324 | FD5 gives clinic_id -> VetClinics |
| | 325 | Therefore the join is lossless. |
| | 326 | |
| | 327 | 6. Split Notifications(notification_id, user_id, type, message, is_read, created_at) |
| | 328 | Intersection with remaining relation: notification_id |
| | 329 | FD6 gives notification_id -> Notifications |
| | 330 | Therefore the join is lossless. |
| | 331 | |
| | 332 | 7. Split VetClinicApplications(application_id, clinic_id, name, email, phone, city, address, submitted_at, status, reviewed_at, reviewed_by, denial_reason) |
| | 333 | Intersection with remaining relation: application_id |
| | 334 | FD7 gives application_id -> VetClinicApplications |
| | 335 | Therefore the join is lossless. |
| | 336 | |
| | 337 | 8. Split Animals(animal_id, owner_id, name, located_name, species, type, breed, sex, date_of_birth, photo_url) |
| | 338 | Intersection with remaining relation: animal_id |
| | 339 | FD8 gives animal_id -> Animals |
| | 340 | Therefore the join is lossless. |
| | 341 | |
| | 342 | 9. Split Listings(listing_id, owner_id, animal_id, status, created_at, price, description) |
| | 343 | Intersection with remaining relation: listing_id |
| | 344 | FD9 gives listing_id -> Listings |
| | 345 | Therefore the join is lossless. |
| | 346 | |
| | 347 | 10. Split FavoriteListings(user_id, listing_id) |
| | 348 | Intersection with remaining relation: user_id, listing_id |
| | 349 | (user_id, listing_id) is the composite key of FavoriteListings |
| | 350 | Therefore the join is lossless. |
| | 351 | |
| | 352 | 11. Split Appointments(appointment_id, clinic_id, animal_id, responsible_owner_id, date_time, status, notes) |
| | 353 | Intersection with remaining relation: appointment_id |
| | 354 | FD10 gives appointment_id -> Appointments |
| | 355 | Therefore the join is lossless. |
| | 356 | |
| | 357 | 12. Split HealthRecords(healthrecord_id, animal_id, appointment_id, type, description, date) |
| | 358 | Intersection with remaining relation: healthrecord_id |
| | 359 | FD11 gives healthrecord_id -> HealthRecords |
| | 360 | Therefore the join is lossless. |
| | 361 | |
| | 362 | 13. Split Reviews(review_id, reviewer_id, rating, comment, created_at, updated_at, is_deleted) |
| | 363 | Intersection with remaining relation: review_id |
| | 364 | FD12 gives review_id -> Reviews |
| | 365 | Therefore the join is lossless. |
| | 366 | |
| | 367 | 14. Split UserReviews(review_id, target_user_id, interaction_type) |
| | 368 | Intersection with Reviews: review_id |
| | 369 | review_id is the key of UserReviews and also references Reviews(review_id) |
| | 370 | Therefore joining Reviews with UserReviews is lossless for user-review tuples. |
| | 371 | |
| | 372 | 15. Split ClinicReviews(review_id, target_clinic_id, appointment_id) |
| | 373 | Intersection with Reviews: review_id |
| | 374 | review_id is the key of ClinicReviews and also references Reviews(review_id) |
| | 375 | Therefore joining Reviews with ClinicReviews is lossless for clinic-review tuples. |
| | 376 | |
| | 377 | 16. Split ClinicUnavailableSlots(slot_id, clinic_id, date_time, reason, created_at) |
| | 378 | Intersection with remaining relation: slot_id |
| | 379 | FD15 gives slot_id -> ClinicUnavailableSlots |
| | 380 | Therefore the join is lossless. |
| | 381 | }}} |
| | 382 | |
| | 383 | Because every decomposition step satisfies the binary lossless decomposition criterion, the whole decomposition is lossless by induction: after each split, the remaining relation can be joined back with the new relation without producing spurious tuples or losing tuples. |