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