Changeset ef1c1c7 for docs/P5-Normalization/Normalization.md
- Timestamp:
- 09/24/26 17:43:19 (5 days ago)
- Branches:
- main
- Children:
- 0cee8ec
- Parents:
- a531b45
- File:
-
- 1 edited
-
docs/P5-Normalization/Normalization.md (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
docs/P5-Normalization/Normalization.md
ra531b45 ref1c1c7 257 257 again: nothing was lost. 258 258 259 **Lossless join.** For every pair (referencing relation, referenced relation) connected by a 260 foreign key — `R_MARKETS.M_CRYPTO_ID → R_CRYPTO.C_ID`, `R_HOLDINGS.H_USER_ID → R_USERS.U_ID` / 261 `R_HOLDINGS.H_CRYPTO_ID → R_CRYPTO.C_ID`, `R_ORDERS.O_USER_ID → R_USERS.U_ID` / 262 `R_ORDERS.O_MARKET_ID → R_MARKETS.M_ID`, `R_TRANSACTIONS.T_USER_ID → R_USERS.U_ID` / 263 `R_TRANSACTIONS.T_RELATED_ORDER → R_ORDERS.O_ID`, `R_MARKET_TRADES.MT_MARKET_ID → 264 R_MARKETS.M_ID`, `R_MARKET_CANDLES.MC_MARKET_ID → R_MARKETS.M_ID`, 265 `R_WATCHLISTS.W_USER_ID → R_USERS.U_ID`, `R_WATCHLIST_ITEMS.WI_WATCHLIST_ID → 266 R_WATCHLISTS.W_ID` / `R_WATCHLIST_ITEMS.WI_CRYPTO_ID → R_CRYPTO.C_ID` — the join attribute on 267 the "one" side is that relation's own primary key (`U_ID`, `C_ID`, `M_ID`, `O_ID`, `W_ID`). 268 A join on a foreign key equated to the primary key it references is the textbook sufficient 269 condition for a lossless decomposition (`Ri ∩ Rj` is a key of `Rj`), so re-joining all ten 270 relations on their foreign-key/primary-key pairs reconstructs `R_EDUBERZA` exactly, with no 271 spurious rows and none missing. 259 **Lossless join — chase test.** 260 261 > *Note: the chase algorithm is not part of the course material. I was curious about a 262 > stricter way to test lossless join than the usual "the common attributes are a key of one 263 > side" argument, so I applied it here.* 264 265 The chase decides whether a decomposition `R = R1 ∪ … ∪ Rn` is lossless under a set of 266 functional dependencies. Build a tableau with one column per attribute of `R` and one row per 267 relation `Ri`. In row `i`, put a distinguished symbol `a` in every column of `Ri` and a unique 268 symbol `b_i` in every other column. Then repeat, until nothing changes: for each FD `X → Y`, 269 whenever two rows agree on all of `X`, make them agree on `Y`. If they disagree, an `a` wins, 270 otherwise one `b` replaces the other. **The decomposition is lossless exactly when some row 271 ends up with `a` in every column.** 272 273 All attributes of one cluster (`U_*`, `C_*`, `M_*`, …) always appear together, and FD1–FD17 274 never mix clusters. So each cluster is one column group below: `a` means every column of the 275 group holds a distinguished symbol, and `b` means none of them does. The foreign-key 276 attributes (`H_USER_ID`, `O_MARKET_ID`, …) belong to their own cluster (`H_*`, `O_*`, …), not 277 to the cluster they reference. 278 279 **Step 1 — the ten relations from the table above.** 280 281 ``` 282 U* C* M* H* O* T* MT* MC* W* WI* 283 R_USERS a b b b b b b b b b 284 R_CRYPTO b a b b b b b b b b 285 R_MARKETS b b a b b b b b b b 286 R_HOLDINGS b b b a b b b b b b 287 R_ORDERS b b b b a b b b b b 288 R_TRANSACTIONS b b b b b a b b b b 289 R_MARKET_TR. b b b b b b a b b b 290 R_MARKET_CA. b b b b b b b a b b 291 R_WATCHLISTS b b b b b b b b a b 292 R_WATCHLIST_I. b b b b b b b b b a 293 ``` 294 295 Every FD has its left side inside one cluster, for example `U_ID → U_*` or 296 `H_USER_ID, H_CRYPTO_ID → H_ID`. For such an FD to fire, two rows would have to agree on that 297 left side. But only one row has `a`s in that cluster, and the `b`s of different rows are 298 all different, so no two rows ever agree on any left side. **The chase changes nothing, and 299 no row becomes all `a`.** Under FD1–FD17 alone, the ten relations are *not* guaranteed to 300 join back to `R_EDUBERZA`. This is not an accident of this model. It is exactly why 301 Bernstein's synthesis algorithm has a final step: *if no synthesised relation contains a 302 candidate key of `R`, add one that does.* None of the ten contains the ten-attribute key. 303 304 **Step 2 — add the key relation** `R_KEY(U_ID, C_ID, M_ID, H_ID, O_ID, T_ID, MT_ID, MC_ID, 305 W_ID, WI_ID)`. Its row has `a` only in the ten ID columns, written `a·` for "`a` in the ID, 306 `b` in the rest of the group": 307 308 ``` 309 U* C* M* H* O* T* MT* MC* W* WI* 310 R_KEY a· a· a· a· a· a· a· a· a· a· 311 (the ten rows of step 1 unchanged) 312 ``` 313 314 Now FD1 `U_ID → U_*` fires: row `R_KEY` and row `R_USERS` both have `a` in `U_ID`, so they 315 must agree on the rest of `U_*`, and `R_USERS` has `a` there. `R_KEY` becomes `a` in the whole 316 `U*` group. The same happens with FD4 (`C*`), FD6 (`M*`), FD8 (`H*`), FD10 (`O*`), FD11 (`T*`), 317 FD12 (`MT*`), FD13 (`MC*`), FD15 (`W*`) and FD16 (`WI*`): 318 319 ``` 320 U* C* M* H* O* T* MT* MC* W* WI* 321 R_KEY a a a a a a a a a a <- all distinguished 322 ``` 323 324 **Row `R_KEY` is all `a`, so the decomposition into the ten relations plus `R_KEY` is 325 lossless.** 326 327 **Why `R_KEY` is not kept in the final schema.** An instance of `R_KEY` would only record 328 which ID of one cluster appears together with which ID of every other cluster. As shown under 329 *Candidate keys and primary key*, the ten clusters are independent record types, and 330 `R_EDUBERZA` pairs every row of one with every row of the others. So `R_KEY` would be just the 331 cross product of the ten ID sets and would carry no information. The same independence means 332 the join dependency `⋈[R_USERS, …, R_WATCHLIST_ITEMS]` holds on `R_EDUBERZA` by construction. 333 Under that dependency the ten relations alone already reconstruct it: their natural join, with 334 no common attributes, is exactly that cross product. The chase makes this reasoning explicit. 335 FDs by themselves cannot prove the join lossless; you need either the key relation or the 336 independence of the clusters. That was hidden in the earlier "foreign key equals primary key" 337 argument, which described the equi-joins the application runs, not the natural join the 338 lossless-join property is about. 272 339 273 340 ## 3NF decomposition
Note:
See TracChangeset
for help on using the changeset viewer.
