| 1 | # Normalization
|
|---|
| 2 |
|
|---|
| 3 | This phase does not use the relations of
|
|---|
| 4 | [RelationalDesign](../P2-RelationalDesign/RelationalDesign.md) (P2) as a starting point.
|
|---|
| 5 | It starts from the attributes of [ERModel](../P1-ConceptualModel/ERModel.md) **v05** (P1),
|
|---|
| 6 | put into one de-normalized relation. It states the functional dependencies that the
|
|---|
| 7 | model's rules impose on those attributes, **computes** the keys of that relation from the
|
|---|
| 8 | dependencies, and then decomposes it step by step through 2NF, 3NF and BCNF. Every step is
|
|---|
| 9 | checked for a lossless join and for dependency preservation. The
|
|---|
| 10 | [final section](#final-result-and-discussion) compares the result with P2.
|
|---|
| 11 |
|
|---|
| 12 | ## De-normalized database form
|
|---|
| 13 |
|
|---|
| 14 | ### Which attributes go into the relation
|
|---|
| 15 |
|
|---|
| 16 | The relation contains **the attributes of the ER model and nothing else**. In v05 all
|
|---|
| 17 | attributes belong to the 11 entity sets. None of the 15 relationships has attributes of its
|
|---|
| 18 | own.
|
|---|
| 19 |
|
|---|
| 20 | A relationship adds **no column**. Foreign-key columns such as `crypto_id` or `watchlist_id`
|
|---|
| 21 | belong to the relational model of P2, not to the ER model, so they do not appear here. What a
|
|---|
| 22 | relationship contributes is a **functional dependency** between attributes that are already
|
|---|
| 23 | in the relation. For example, `Contains` (Watchlists 1 : N WatchlistItems) says that every
|
|---|
| 24 | watchlist item is on exactly one watchlist, which is the dependency `WI_ID → W_ID` in the
|
|---|
| 25 | next section. It is not a column `WI_WATCHLIST_ID`.
|
|---|
| 26 |
|
|---|
| 27 | Attribute names are prefixed with the entity set they come from, because several names repeat
|
|---|
| 28 | across the model (`id`, `created_at`, `quantity`, `type`, `name`, `price`, `side`), and one
|
|---|
| 29 | relation cannot contain the same name twice.
|
|---|
| 30 |
|
|---|
| 31 | | Prefix | Entity set (P1) | Attributes |
|
|---|
| 32 | |---|---|---|
|
|---|
| 33 | | `U_` | Users | `U_ID, U_USERNAME, U_EMAIL, U_FULL_NAME, U_PASSWORD_HASH, U_AVAILABLE_BALANCE, U_INVESTED_BALANCE, U_RESERVED_BALANCE, U_CREATED_AT, U_UPDATED_AT` |
|
|---|
| 34 | | `C_` | Cryptos | `C_ID, C_SYMBOL, C_NAME, C_CREATED_AT` |
|
|---|
| 35 | | `M_` | Markets | `M_ID, M_QUOTE_CURRENCY, M_IS_ACTIVE, M_CREATED_AT` |
|
|---|
| 36 | | `H_` | Holdings | `H_ID, H_QUANTITY, H_RESERVED_QUANTITY, H_AVG_PRICE, H_CREATED_AT, H_UPDATED_AT` |
|
|---|
| 37 | | `O_` | Orders | `O_ID, O_SIDE, O_TYPE, O_STATUS, O_QUANTITY, O_FILLED_QUANTITY, O_PRICE, O_PLACED_AT, O_EXECUTED_AT` |
|
|---|
| 38 | | `T_` | Transactions | `T_ID, T_TYPE, T_AMOUNT, T_CURRENCY, T_CREATED_AT, T_DESCRIPTION` |
|
|---|
| 39 | | `MT_` | MarketTrades | `MT_ID, MT_EXECUTED_AT, MT_PRICE, MT_QUANTITY, MT_SIDE, MT_SOURCE` |
|
|---|
| 40 | | `OE_` | OrderEvents | `OE_ID, OE_EVENT_TYPE, OE_QUANTITY, OE_PRICE, OE_STATUS_AFTER, OE_CREATED_AT` |
|
|---|
| 41 | | `MC_` | MarketCandles | `MC_ID, MC_TIMEFRAME, MC_OPEN, MC_HIGH, MC_LOW, MC_CLOSE, MC_VOLUME, MC_CANDLE_TIME` |
|
|---|
| 42 | | `W_` | Watchlists | `W_ID, W_NAME, W_CREATED_AT` |
|
|---|
| 43 | | `WI_` | WatchlistItems | `WI_ID, WI_ADDED_AT` |
|
|---|
| 44 |
|
|---|
| 45 | That is 64 attributes. **One case needs two more.** `FillsBuy` and `FillsSell` are two
|
|---|
| 46 | different relationships between the same two entity sets, Orders and MarketTrades. A trade
|
|---|
| 47 | can fill one buy order *and* one sell order, which are two different orders. One relation
|
|---|
| 48 | has only one `O_ID` column, and one column cannot hold two different orders in the same
|
|---|
| 49 | tuple. So the order's identifier appears once per **role**, named after the relationship
|
|---|
| 50 | that gives the role:
|
|---|
| 51 |
|
|---|
| 52 | | Attribute | Meaning |
|
|---|
| 53 | |---|---|
|
|---|
| 54 | | `O_ID_FILLSBUY` | the `id` of Orders, in its role in `FillsBuy` (the buy order a trade filled) |
|
|---|
| 55 | | `O_ID_FILLSSELL` | the `id` of Orders, in its role in `FillsSell` (the sell order a trade filled) |
|
|---|
| 56 |
|
|---|
| 57 | These are not foreign keys copied from P2. They are the ER attribute `Orders.id` itself, once
|
|---|
| 58 | for each of the two relationships. [ERModel](../P1-ConceptualModel/ERModel.md) names these two
|
|---|
| 59 | roles of `Orders` explicitly: *the buy order* of a trade in `FillsBuy`, and *the sell order* in
|
|---|
| 60 | `FillsSell`. This is the only place where the model has two
|
|---|
| 61 | relationships between the same pair of entity sets. Every other relationship is expressed with
|
|---|
| 62 | the attributes above, without renaming.
|
|---|
| 63 |
|
|---|
| 64 | This gives **one relation, `R_EDUBERZA`, of 66 attributes:**
|
|---|
| 65 |
|
|---|
| 66 | ```
|
|---|
| 67 | R_EDUBERZA(
|
|---|
| 68 | U_ID, U_USERNAME, U_EMAIL, U_FULL_NAME, U_PASSWORD_HASH, U_AVAILABLE_BALANCE,
|
|---|
| 69 | U_INVESTED_BALANCE, U_RESERVED_BALANCE, U_CREATED_AT, U_UPDATED_AT,
|
|---|
| 70 | C_ID, C_SYMBOL, C_NAME, C_CREATED_AT,
|
|---|
| 71 | M_ID, M_QUOTE_CURRENCY, M_IS_ACTIVE, M_CREATED_AT,
|
|---|
| 72 | H_ID, H_QUANTITY, H_RESERVED_QUANTITY, H_AVG_PRICE, H_CREATED_AT, H_UPDATED_AT,
|
|---|
| 73 | O_ID, O_SIDE, O_TYPE, O_STATUS, O_QUANTITY, O_FILLED_QUANTITY, O_PRICE,
|
|---|
| 74 | O_PLACED_AT, O_EXECUTED_AT,
|
|---|
| 75 | T_ID, T_TYPE, T_AMOUNT, T_CURRENCY, T_CREATED_AT, T_DESCRIPTION,
|
|---|
| 76 | MT_ID, MT_EXECUTED_AT, MT_PRICE, MT_QUANTITY, MT_SIDE, MT_SOURCE,
|
|---|
| 77 | O_ID_FILLSBUY, O_ID_FILLSSELL,
|
|---|
| 78 | OE_ID, OE_EVENT_TYPE, OE_QUANTITY, OE_PRICE, OE_STATUS_AFTER, OE_CREATED_AT,
|
|---|
| 79 | MC_ID, MC_TIMEFRAME, MC_OPEN, MC_HIGH, MC_LOW, MC_CLOSE, MC_VOLUME, MC_CANDLE_TIME,
|
|---|
| 80 | W_ID, W_NAME, W_CREATED_AT,
|
|---|
| 81 | WI_ID, WI_ADDED_AT
|
|---|
| 82 | )
|
|---|
| 83 | ```
|
|---|
| 84 |
|
|---|
| 85 | To keep the tables below readable, **`X_*`** means the non-identifier attributes of prefix
|
|---|
| 86 | `X_`. For example, `U_*` = `U_USERNAME … U_UPDATED_AT` (9 attributes), and `O_*` =
|
|---|
| 87 | `O_SIDE … O_EXECUTED_AT` (8 attributes). `U_ID`, `O_ID`, … are always written out.
|
|---|
| 88 |
|
|---|
| 89 | Every attribute is single-valued and atomic (a balance, a timestamp, a symbol, an amount —
|
|---|
| 90 | nothing here is a list or a nested record), so `R_EDUBERZA` satisfies 1NF as soon as it is
|
|---|
| 91 | written down.
|
|---|
| 92 |
|
|---|
| 93 | ## Functional dependencies
|
|---|
| 94 |
|
|---|
| 95 | At this point `R_EDUBERZA` is just a set of attributes. It has **no keys yet**. `U_ID`,
|
|---|
| 96 | `O_ID`, … are ordinary attributes of this relation, and which attribute sets are keys of
|
|---|
| 97 | `R_EDUBERZA` is computed in the [next section](#candidate-keys-and-primary-key), from the
|
|---|
| 98 | dependencies below. Each dependency is justified by a rule of the domain, as described in
|
|---|
| 99 | the data requirements of [ERModel](../P1-ConceptualModel/ERModel.md). The rules are of four
|
|---|
| 100 | kinds:
|
|---|
| 101 |
|
|---|
| 102 | - **(I) Identification.** Every value of an identifier (`U_ID`, `C_ID`, …) is given to
|
|---|
| 103 | exactly one real object: one user, one crypto, one order. That object has exactly one
|
|---|
| 104 | username, one balance, one price, and so on. So the identifier's value fixes those values.
|
|---|
| 105 | - **(R) 1:N relationship.** In a 1:N relationship, each object on the N side is linked to
|
|---|
| 106 | exactly one object on the 1 side. So the N side's identifier fixes the 1 side's
|
|---|
| 107 | identifier. Example: an order is placed by exactly one user (`Places`), so `O_ID → U_ID`.
|
|---|
| 108 | The opposite direction does not hold: a user places many orders, so `U_ID ↛ O_ID`.
|
|---|
| 109 | - **(U) Uniqueness rule.** A rule of the form "at most one X per Y and Z" gives
|
|---|
| 110 | `Y, Z → X`.
|
|---|
| 111 | - **(N) Unique natural attribute.** No two users share a username or an email, and no two
|
|---|
| 112 | cryptos share a symbol.
|
|---|
| 113 |
|
|---|
| 114 | **Only rules of the ER model are used.** The dependencies below come from the rules stated in
|
|---|
| 115 | [ERModel](../P1-ConceptualModel/ERModel.md) v05 and nothing else. The analysis uses the
|
|---|
| 116 | classical definitions (Armstrong's axioms), with no special treatment of `NULL`. Partial
|
|---|
| 117 | relationships (`Settles`, `FillsBuy`, `FillsSell`) are discussed where they matter:
|
|---|
| 118 | under [Canonical cover](#canonical-cover) and in the [discussion](#discussion).
|
|---|
| 119 |
|
|---|
| 120 | | # | Functional dependency | Rule | Why it holds |
|
|---|
| 121 | |---|---|---|---|
|
|---|
| 122 | | FD1 | `U_ID → U_USERNAME, U_EMAIL, U_FULL_NAME, U_PASSWORD_HASH, U_AVAILABLE_BALANCE, U_INVESTED_BALANCE, U_RESERVED_BALANCE, U_CREATED_AT, U_UPDATED_AT` | I | one user, one value of each |
|
|---|
| 123 | | FD2 | `U_USERNAME → U_ID` | N | usernames are unique |
|
|---|
| 124 | | FD3 | `U_EMAIL → U_ID` | N | emails are unique |
|
|---|
| 125 | | FD4 | `C_ID → C_SYMBOL, C_NAME, C_CREATED_AT` | I | one crypto, one value of each |
|
|---|
| 126 | | FD5 | `C_SYMBOL → C_ID` | N | symbols are unique |
|
|---|
| 127 | | FD6 | `M_ID → M_QUOTE_CURRENCY, M_IS_ACTIVE, M_CREATED_AT, C_ID` | I, R | …and a market is `QuotedOn` exactly one crypto |
|
|---|
| 128 | | FD7 | `C_ID, M_QUOTE_CURRENCY → M_ID` | U | a crypto is quoted at most once per currency |
|
|---|
| 129 | | FD8 | `H_ID → H_QUANTITY, H_RESERVED_QUANTITY, H_AVG_PRICE, H_CREATED_AT, H_UPDATED_AT, U_ID, C_ID` | I, R | …and a holding belongs to one user (`Holds`) and is a position in one crypto (`PositionIn`) |
|
|---|
| 130 | | FD9 | `U_ID, C_ID → H_ID` | U | at most one holding per user and crypto |
|
|---|
| 131 | | FD10 | `O_ID → O_SIDE, O_TYPE, O_STATUS, O_QUANTITY, O_FILLED_QUANTITY, O_PRICE, O_PLACED_AT, O_EXECUTED_AT, U_ID, M_ID` | I, R | …and an order is placed by one user (`Places`) on one market (`PlacedOn`) |
|
|---|
| 132 | | FD11 | `T_ID → T_TYPE, T_AMOUNT, T_CURRENCY, T_CREATED_AT, T_DESCRIPTION, U_ID, O_ID` | I, R | …and a ledger entry belongs to one user (`Records`) and to at most one order (`Settles`) |
|
|---|
| 133 | | FD12 | `MT_ID → MT_EXECUTED_AT, MT_PRICE, MT_QUANTITY, MT_SIDE, MT_SOURCE, M_ID, O_ID_FILLSBUY, O_ID_FILLSSELL` | I, R | …and a trade happened on one market (`Fills`) and filled at most one buy order (`FillsBuy`) and at most one sell order (`FillsSell`) |
|
|---|
| 134 | | FD13 | `OE_ID → OE_EVENT_TYPE, OE_QUANTITY, OE_PRICE, OE_STATUS_AFTER, OE_CREATED_AT, O_ID` | I, R | …and an event belongs to one order (`Logs`) |
|
|---|
| 135 | | FD14 | `MC_ID → MC_TIMEFRAME, MC_OPEN, MC_HIGH, MC_LOW, MC_CLOSE, MC_VOLUME, MC_CANDLE_TIME, M_ID` | I, R | …and a candle summarises one market (`Aggregates`) |
|
|---|
| 136 | | FD15 | `M_ID, MC_TIMEFRAME, MC_CANDLE_TIME → MC_ID` | U | one candle per market, timeframe and bucket |
|
|---|
| 137 | | FD16 | `W_ID → W_NAME, W_CREATED_AT, U_ID` | I, R | …and a watchlist is owned by one user (`Owns`) |
|
|---|
| 138 | | FD17 | `WI_ID → WI_ADDED_AT, W_ID, C_ID` | I, R | …and an item is on one watchlist (`Contains`) and names one crypto (`Lists`) |
|
|---|
| 139 | | FD18 | `W_ID, C_ID → WI_ID` | U | an asset appears at most once per watchlist |
|
|---|
| 140 |
|
|---|
| 141 | **Dependencies that do *not* hold** are as important, because they are why some attributes
|
|---|
| 142 | must be combined in the key later:
|
|---|
| 143 |
|
|---|
| 144 | - The reverse of every (R) dependency, e.g. `U_ID ↛ O_ID`, `M_ID ↛ MT_ID`, `W_ID ↛ WI_ID`.
|
|---|
| 145 | These are 1:N, not 1:1.
|
|---|
| 146 | - `M_ID, MT_EXECUTED_AT ↛ MT_ID`. Two trades on a market can share a timestamp.
|
|---|
| 147 | - `U_ID, W_NAME ↛ W_ID`. The model does not require list names to be unique per user.
|
|---|
| 148 | - `O_ID_FILLSBUY` and `O_ID_FILLSSELL` determine no other attribute of `R_EDUBERZA` **by any
|
|---|
| 149 | rule of the ER model**. The order data (`O_SIDE`, `O_PRICE`, …) describes the order in the
|
|---|
| 150 | `O_ID` column, not the order in a role column. (The database also has a rule that a trade
|
|---|
| 151 | and the orders it fills are on the same market. That rule is a trigger in P7 relating
|
|---|
| 152 | several entity sets, not a rule of the ER model, so it is not used here.)
|
|---|
| 153 |
|
|---|
| 154 | ### Canonical cover
|
|---|
| 155 |
|
|---|
| 156 | A canonical (minimal) cover is obtained in three steps.
|
|---|
| 157 |
|
|---|
| 158 | **Step 1 — single attribute on the right.** Each FD above is read as one dependency per
|
|---|
| 159 | right-side attribute, e.g. FD6 is `M_ID → M_QUOTE_CURRENCY`, `M_ID → M_IS_ACTIVE`,
|
|---|
| 160 | `M_ID → M_CREATED_AT`, `M_ID → C_ID`.
|
|---|
| 161 |
|
|---|
| 162 | **Step 2 — no extraneous attribute on the left.** Only FD7, FD9, FD15 and FD18 have more than one
|
|---|
| 163 | attribute on the left. For each one, dropping any attribute makes the rule false:
|
|---|
| 164 |
|
|---|
| 165 | | FD | Drop | Counter-example (the smaller left side does not determine the right side) |
|
|---|
| 166 | |---|---|---|
|
|---|
| 167 | | FD7 | `M_QUOTE_CURRENCY` | BTC is quoted in USD *and* in EUR: one `C_ID`, two markets |
|
|---|
| 168 | | | `C_ID` | USD is the quote currency of many markets |
|
|---|
| 169 | | FD9 | `C_ID` | one user holds several cryptos |
|
|---|
| 170 | | | `U_ID` | one crypto is held by several users |
|
|---|
| 171 | | FD15 | `M_ID` | every market has a `1h` candle starting at 10:00 |
|
|---|
| 172 | | | `MC_TIMEFRAME` | a market has a `1m` and a `1h` candle both starting at 10:00 |
|
|---|
| 173 | | | `MC_CANDLE_TIME` | a market has many `1h` candles |
|
|---|
| 174 | | FD18 | `C_ID` | a watchlist has several items |
|
|---|
| 175 | | | `W_ID` | a crypto is on several watchlists |
|
|---|
| 176 |
|
|---|
| 177 | **Step 3 — no redundant dependency.** A dependency is redundant if it follows from the others. For
|
|---|
| 178 | almost every dependency, its right-side attribute appears on the right of no other
|
|---|
| 179 | dependency with a different left side (e.g. nothing but `U_ID` determines
|
|---|
| 180 | `U_AVAILABLE_BALANCE`), so it cannot be derived. The candidates worth checking are the
|
|---|
| 181 | identifiers that are reached from several places:
|
|---|
| 182 |
|
|---|
| 183 | - **`T_ID → U_ID` is redundant.** It follows by transitivity from `T_ID → O_ID` (FD11) and
|
|---|
| 184 | `O_ID → U_ID` (FD10): a ledger entry's user is the user of the order it settles. It is
|
|---|
| 185 | therefore **removed** from FD11. The derivation is valid only for an entry that has an
|
|---|
| 186 | order. Every tuple of `R_EDUBERZA` does have one (see the
|
|---|
| 187 | [discussion](#discussion)), so in the de-normalized relation the removal is correct. The
|
|---|
| 188 | consequence for deposits, which have no order, is taken up in the discussion.
|
|---|
| 189 | - `H_ID → U_ID`, `H_ID → C_ID`, `WI_ID → W_ID`, `WI_ID → C_ID`, `M_ID → C_ID`, `MT_ID → M_ID`,
|
|---|
| 190 | `MC_ID → M_ID`, `OE_ID → O_ID`, `W_ID → U_ID` and `O_ID → U_ID`, `O_ID → M_ID`: for
|
|---|
| 191 | each, no other dependency with a different left side has that attribute on its right
|
|---|
| 192 | side and a left side reachable from this one, so none can be derived.
|
|---|
| 193 | - The four (U) and three (N) dependencies go "backwards" from a non-identifier to an
|
|---|
| 194 | identifier. Nothing else produces an identifier from those attributes, so they are not
|
|---|
| 195 | derivable either.
|
|---|
| 196 |
|
|---|
| 197 | Grouping the single-attribute dependencies back by left side gives FD1–FD18 as listed,
|
|---|
| 198 | except that FD11 loses `U_ID`:
|
|---|
| 199 |
|
|---|
| 200 | | # | Functional dependency (canonical cover) |
|
|---|
| 201 | |---|---|
|
|---|
| 202 | | FD11 | `T_ID → T_TYPE, T_AMOUNT, T_CURRENCY, T_CREATED_AT, T_DESCRIPTION, O_ID` |
|
|---|
| 203 |
|
|---|
| 204 | **FD1–FD18, with this FD11, is the canonical cover.** From here on, "FD11" means this reduced
|
|---|
| 205 | form.
|
|---|
| 206 |
|
|---|
| 207 | ## Candidate keys and primary key
|
|---|
| 208 |
|
|---|
| 209 | A candidate key is a minimal set of attributes whose closure under FD1–FD18 is all 66
|
|---|
| 210 | attributes.
|
|---|
| 211 |
|
|---|
| 212 | **Attributes that must be in every key.** `T_ID`, `OE_ID` and `MT_ID` appear on the right side
|
|---|
| 213 | of no dependency. Nothing determines them, so every key must contain them.
|
|---|
| 214 |
|
|---|
| 215 | **Closure of `{T_ID, OE_ID, MT_ID}`:**
|
|---|
| 216 |
|
|---|
| 217 | | Step | Added | Using |
|
|---|
| 218 | |---|---|---|
|
|---|
| 219 | | start | `T_ID, OE_ID, MT_ID` | — |
|
|---|
| 220 | | 1 | `T_*`, `O_ID` | FD11 |
|
|---|
| 221 | | 2 | `OE_*` | FD13 |
|
|---|
| 222 | | 3 | `MT_*`, `M_ID`, `O_ID_FILLSBUY`, `O_ID_FILLSSELL` | FD12 |
|
|---|
| 223 | | 4 | `O_*`, `U_ID` | FD10 |
|
|---|
| 224 | | 5 | `U_*` | FD1 |
|
|---|
| 225 | | 6 | `M_*`, `C_ID` | FD6 |
|
|---|
| 226 | | 7 | `C_*` | FD4 |
|
|---|
| 227 | | 8 | `H_ID` | FD9 (`U_ID` and `C_ID` are both present) |
|
|---|
| 228 | | 9 | `H_*` | FD8 |
|
|---|
| 229 |
|
|---|
| 230 | That is 53 attributes. Still missing are all 8 `MC_` attributes, the 3 `W_` attributes and
|
|---|
| 231 | the 2 `WI_` attributes:
|
|---|
| 232 |
|
|---|
| 233 | - **`MC_`:** only `MC_ID` determines them (FD14), and `MC_ID` is reached only by FD15, which
|
|---|
| 234 | needs `M_ID` (already present), `MC_TIMEFRAME` and `MC_CANDLE_TIME`. So the key must add
|
|---|
| 235 | either `MC_ID` or both `MC_TIMEFRAME` and `MC_CANDLE_TIME`. Neither of those two alone is
|
|---|
| 236 | enough.
|
|---|
| 237 | - **`W_` and `WI_`:** `WI_ID` gives `W_ID` (FD17), and `W_ID` gives `WI_ID` together with
|
|---|
| 238 | `C_ID`, which is already present (FD18). So adding either `WI_ID` or `W_ID` gives all five.
|
|---|
| 239 |
|
|---|
| 240 | **Candidate keys** (each one's closure is all 66 attributes, and removing any member breaks
|
|---|
| 241 | that, by the argument above):
|
|---|
| 242 |
|
|---|
| 243 | | Key | Attributes |
|
|---|
| 244 | |---|---|
|
|---|
| 245 | | **K1** | `T_ID, OE_ID, MT_ID, MC_ID, WI_ID` |
|
|---|
| 246 | | K2 | `T_ID, OE_ID, MT_ID, MC_ID, W_ID` |
|
|---|
| 247 | | K3 | `T_ID, OE_ID, MT_ID, MC_TIMEFRAME, MC_CANDLE_TIME, WI_ID` |
|
|---|
| 248 | | K4 | `T_ID, OE_ID, MT_ID, MC_TIMEFRAME, MC_CANDLE_TIME, W_ID` |
|
|---|
| 249 |
|
|---|
| 250 | **Primary key: K1.** It consists only of identifiers, and it is the key that remains at the
|
|---|
| 251 | end of the decomposition below.
|
|---|
| 252 |
|
|---|
| 253 | **Prime attributes** (in at least one candidate key): `T_ID, OE_ID, MT_ID, MC_ID,
|
|---|
| 254 | MC_TIMEFRAME, MC_CANDLE_TIME, W_ID, WI_ID`. The other 58 attributes are **non-prime**. The
|
|---|
| 255 | difference matters: 2NF and 3NF only restrict dependencies of non-prime attributes, and BCNF
|
|---|
| 256 | restricts all of them.
|
|---|
| 257 |
|
|---|
| 258 | In words, a tuple of `R_EDUBERZA` puts together one ledger entry, one order event, one
|
|---|
| 259 | trade, one candle and one watchlist item. Everything else in the tuple (the user, the order,
|
|---|
| 260 | the market, the crypto, the holding, the watchlist) follows from those five.
|
|---|
| 261 |
|
|---|
| 262 | **Normal form of `R_EDUBERZA`:** 1NF only. It is not in 2NF, because, for example, `T_AMOUNT`
|
|---|
| 263 | depends on `T_ID` alone, a proper part of K1.
|
|---|
| 264 |
|
|---|
| 265 | ## 1NF decomposition
|
|---|
| 266 |
|
|---|
| 267 | No decomposition is needed. Every attribute of `R_EDUBERZA` is atomic and single-valued, and
|
|---|
| 268 | the relation has no repeating groups (see
|
|---|
| 269 | [De-normalized database form](#de-normalized-database-form)).
|
|---|
| 270 |
|
|---|
| 271 | ## 2NF decomposition
|
|---|
| 272 |
|
|---|
| 273 | ### How every step is described and checked
|
|---|
| 274 |
|
|---|
| 275 | Each step of 2NF, 3NF and BCNF below lists, in this order: the relation analyzed, its
|
|---|
| 276 | dependencies, its candidate keys and primary key, and its normal form; the dependency that
|
|---|
| 277 | violates the next normal form and is used for the split; the two resulting relations, each
|
|---|
| 278 | with its dependencies, keys and normal form; and the dependency-preservation and lossless-join
|
|---|
| 279 | checks.
|
|---|
| 280 |
|
|---|
| 281 | Every step splits one relation `R` into two: the **extracted** relation `Ri` and the
|
|---|
| 282 | **residual** relation `R'` (what is left of `R`). The same two checks are made each time:
|
|---|
| 283 |
|
|---|
| 284 | - **Lossless join.** The split of `R` into `Ri` and `R'` is lossless if the common attributes
|
|---|
| 285 | determine one of the two sides: `(Ri ∩ R') → Ri` or `(Ri ∩ R') → R'`. Every step below
|
|---|
| 286 | extracts `Ri = X ∪ (what X determines)` for some determinant `X` that stays in `R'`. So
|
|---|
| 287 | `X ⊆ Ri ∩ R'` and `X → Ri`, and the first condition holds.
|
|---|
| 288 | - **Dependency preservation.** Every dependency of the canonical cover must end up with all
|
|---|
| 289 | its attributes inside one relation. So an attribute is removed from the residual only when
|
|---|
| 290 | no dependency still waiting in the residual needs it. Otherwise it is extracted **and**
|
|---|
| 291 | kept.
|
|---|
| 292 |
|
|---|
| 293 | **Relation analyzed first:** `R_EDUBERZA` (66 attributes), dependencies FD1–FD18, candidate
|
|---|
| 294 | keys K1–K4, primary key K1. **Normal form:** 1NF.
|
|---|
| 295 |
|
|---|
| 296 | **Dependencies that violate 2NF.** 2NF forbids a non-prime attribute from depending on a proper
|
|---|
| 297 | part of a candidate key. There are six such partial dependencies:
|
|---|
| 298 |
|
|---|
| 299 | | Part of a key | Non-prime attributes that depend on it | Through |
|
|---|
| 300 | |---|---|---|
|
|---|
| 301 | | `T_ID` (K1–K4) | `T_*`, `O_ID`, and through them `O_*`, `U_ID`, `U_*`, `M_ID`, `M_*`, `C_ID`, `C_*`, `H_ID`, `H_*` | FD11, then FD10, FD1, FD6, FD4, FD9, FD8 |
|
|---|
| 302 | | `OE_ID` (K1–K4) | `OE_*`, `O_ID` | FD13 |
|
|---|
| 303 | | `MT_ID` (K1–K4) | `MT_*`, `M_ID`, `O_ID_FILLSBUY`, `O_ID_FILLSSELL` | FD12 |
|
|---|
| 304 | | `MC_ID` (K1, K2) | `MC_OPEN, MC_HIGH, MC_LOW, MC_CLOSE, MC_VOLUME`, `M_ID` | FD14 |
|
|---|
| 305 | | `W_ID` (K2, K4) | `W_*`, `U_ID` | FD16 |
|
|---|
| 306 | | `WI_ID` (K1, K3) | `WI_ADDED_AT`, `C_ID` | FD17 |
|
|---|
| 307 |
|
|---|
| 308 | The table lists the part of a key that each group depends on most directly. It is not the
|
|---|
| 309 | only one: under K3/K4, for example, `MC_OPEN … MC_VOLUME` also depend on
|
|---|
| 310 | `{MT_ID, MC_TIMEFRAME, MC_CANDLE_TIME}`, and under K1/K3 `W_*` depend on `WI_ID` through
|
|---|
| 311 | `W_ID`. These lead to the same relations, so they need no extra steps. `MC_TIMEFRAME`,
|
|---|
| 312 | `MC_CANDLE_TIME` and `W_ID` also depend on parts of keys, but they are prime, so 2NF does not
|
|---|
| 313 | restrict them. They are handled under BCNF.
|
|---|
| 314 |
|
|---|
| 315 | Each step below removes one row of this table, splitting the current relation into two. The
|
|---|
| 316 | **order** is chosen so that no dependency is lost. `T_ID` goes first, because its group is the
|
|---|
| 317 | largest and carries FD1–FD11 with it. Each later step handles a group whose determinant is
|
|---|
| 318 | still in the residual relation.
|
|---|
| 319 |
|
|---|
| 320 | ### Step 2NF-1 — partial dependency on `T_ID`
|
|---|
| 321 |
|
|---|
| 322 | - **Relation analyzed:** `R_EDUBERZA` (66 attributes).
|
|---|
| 323 | - **Dependencies:** FD1–FD18. **Candidate keys:** K1–K4. **Primary key:** K1.
|
|---|
| 324 | **Normal form:** 1NF.
|
|---|
| 325 | - **2NF violations:** all six rows of the table above. **Split first on `T_ID`**, the
|
|---|
| 326 | largest group (see the order explained above).
|
|---|
| 327 | - **Decomposition dependency:** `T_ID → T_*, O_ID` (FD11), together with everything it
|
|---|
| 328 | determines transitively (FD10, FD1, FD6, FD4, FD9, FD8). `T_ID` is a proper part of K1, and
|
|---|
| 329 | `T_AMOUNT`, for example, is non-prime, so this violates 2NF.
|
|---|
| 330 | - **New relation `R_A`** = `{ T_ID, T_*, O_ID, O_*, U_ID, U_*, M_ID, M_*, C_ID, C_*, H_ID, H_* }`
|
|---|
| 331 | (39 attributes). Dependencies: FD1–FD11. Candidate key and primary key: `T_ID`. Normal form: 2NF (it has a
|
|---|
| 332 | one-attribute key), but not 3NF (see 3NF).
|
|---|
| 333 | - **Residual relation `S1`** = `R_EDUBERZA − { T_*, O_*, U_*, M_*, C_*, H_ID, H_* }` =
|
|---|
| 334 | `{ T_ID, O_ID, U_ID, M_ID, C_ID, OE_ID, OE_*, MT_ID, MT_*, O_ID_FILLSBUY, O_ID_FILLSSELL,
|
|---|
| 335 | MC_ID, MC_*, W_ID, W_*, WI_ID, WI_ADDED_AT }` (32 attributes). `O_ID`, `U_ID`, `M_ID` and
|
|---|
| 336 | `C_ID` stay, because FD13, FD16, FD12/FD14/FD15 and FD17/FD18 still need them. Dependencies: FD12–FD18, plus the projected dependencies between the identifiers kept here:
|
|---|
| 337 | `T_ID → O_ID, U_ID, M_ID, C_ID`, `O_ID → U_ID, M_ID, C_ID`, `M_ID → C_ID`,
|
|---|
| 338 | `OE_ID → U_ID, M_ID, C_ID`, `MT_ID → C_ID`, `MC_ID → C_ID`, `WI_ID → U_ID`.
|
|---|
| 339 | Candidate keys: K1–K4 (all
|
|---|
| 340 | their attributes are still here). Normal form: 1NF.
|
|---|
| 341 | - **Dependency preservation:** FD1–FD11 lie entirely in `R_A`, and FD12–FD18 entirely in `S1`. ✓
|
|---|
| 342 | - **Lossless join:** `R_A ∩ S1 = { T_ID, O_ID, U_ID, M_ID, C_ID }` contains `T_ID`, and
|
|---|
| 343 | `T_ID → R_A`, so `(R_A ∩ S1) → R_A`. ✓
|
|---|
| 344 |
|
|---|
| 345 | ### Step 2NF-2 — partial dependency on `OE_ID`
|
|---|
| 346 |
|
|---|
| 347 | - **Relation analyzed:** `S1` (32 attributes). Dependencies: as listed for `S1` in the
|
|---|
| 348 | previous step. Candidate keys: K1–K4. Primary key: K1. Normal form: 1NF.
|
|---|
| 349 | - **Remaining 2NF violations:** the partial dependencies on `OE_ID`, `MT_ID`, `MC_ID`, `W_ID`
|
|---|
| 350 | and `WI_ID` (table above), and the partial dependencies of the kept identifiers
|
|---|
| 351 | `O_ID`, `U_ID`, `M_ID`, `C_ID` on `T_ID`. The kept identifiers cannot leave yet, because
|
|---|
| 352 | other groups still need them. Each one leaves with the last group that needs it (`O_ID` in
|
|---|
| 353 | 2NF-2, `M_ID` in 2NF-4, `U_ID` in 2NF-5, `C_ID` in 2NF-6). **Split first on `OE_ID`**,
|
|---|
| 354 | because after it no group needs `O_ID` any more.
|
|---|
| 355 | - **Decomposition dependency:** `OE_ID → OE_*, O_ID` (FD13). `OE_ID` is a proper part of K1
|
|---|
| 356 | and `OE_*` are non-prime.
|
|---|
| 357 | - **New relation `R_B`** = `{ OE_ID, OE_*, O_ID }` (7 attributes). Dependencies: FD13.
|
|---|
| 358 | Candidate key: `OE_ID`. Normal form: BCNF.
|
|---|
| 359 | - **Residual relation `S2`** = `S1 − { OE_*, O_ID }` (26 attributes). No dependency still
|
|---|
| 360 | needed in the residual uses `O_ID`. Dependencies: FD12, FD14–FD18, plus the projected `T_ID → U_ID, M_ID, C_ID`,
|
|---|
| 361 | `OE_ID → U_ID, M_ID, C_ID`, `M_ID → C_ID`, `MT_ID → C_ID`, `MC_ID → C_ID`, `WI_ID → U_ID`.
|
|---|
| 362 | Candidate keys: K1–K4. Normal form: 1NF.
|
|---|
| 363 | - **Dependency preservation:** FD13 is in `R_B`, and the others are in `S2`.
|
|---|
| 364 | `T_ID → O_ID` is already kept in `R_A`. ✓
|
|---|
| 365 | - **Lossless join:** `R_B ∩ S2 = { OE_ID }`, and `OE_ID → R_B` (FD13). ✓
|
|---|
| 366 |
|
|---|
| 367 | ### Step 2NF-3 — partial dependency on `MT_ID`
|
|---|
| 368 |
|
|---|
| 369 | - **Relation analyzed:** `S2` (26 attributes). Dependencies: as listed for `S2` in the
|
|---|
| 370 | previous step. Candidate keys: K1–K4. Primary key: K1. Normal form: 1NF.
|
|---|
| 371 | - **Remaining 2NF violations:** the groups of `MT_ID`, `MC_ID`, `W_ID`, `WI_ID`, and the kept
|
|---|
| 372 | identifiers `U_ID`, `M_ID`, `C_ID`. **Split first on `MT_ID`**, the next group. `M_ID` must
|
|---|
| 373 | still stay for `MC_ID`.
|
|---|
| 374 | - **Decomposition dependency:** `MT_ID → MT_*, M_ID, O_ID_FILLSBUY, O_ID_FILLSSELL` (FD12).
|
|---|
| 375 | - **New relation `R_C`** = `{ MT_ID, MT_*, M_ID, O_ID_FILLSBUY, O_ID_FILLSSELL }`
|
|---|
| 376 | (9 attributes). Dependencies: FD12. Candidate key: `MT_ID`. Normal form: BCNF.
|
|---|
| 377 | - **Residual relation `S3`** = `S2 − { MT_*, O_ID_FILLSBUY, O_ID_FILLSSELL }` (19 attributes).
|
|---|
| 378 | `M_ID` stays, because FD14/FD15 need it. Dependencies: FD14–FD18, plus the projected `T_ID → U_ID, M_ID, C_ID`,
|
|---|
| 379 | `OE_ID → U_ID, M_ID, C_ID`, `MT_ID → M_ID, C_ID`, `M_ID → C_ID`, `MC_ID → C_ID`,
|
|---|
| 380 | `WI_ID → U_ID`.
|
|---|
| 381 | Candidate keys: K1–K4. Normal form: 1NF.
|
|---|
| 382 | - **Dependency preservation:** FD12 is in `R_C`, and FD14–FD18 are in `S3`. ✓
|
|---|
| 383 | - **Lossless join:** `R_C ∩ S3 = { MT_ID, M_ID }` contains `MT_ID`, and `MT_ID → R_C`
|
|---|
| 384 | (FD12). ✓
|
|---|
| 385 |
|
|---|
| 386 | ### Step 2NF-4 — partial dependency on `MC_ID`
|
|---|
| 387 |
|
|---|
| 388 | - **Relation analyzed:** `S3` (19 attributes). Dependencies: as listed for `S3` in the
|
|---|
| 389 | previous step. Candidate keys: K1–K4. Primary key: K1. Normal form: 1NF.
|
|---|
| 390 | - **Remaining 2NF violations:** the groups of `MC_ID`, `W_ID`, `WI_ID`, and the kept
|
|---|
| 391 | identifiers `U_ID`, `M_ID`, `C_ID`. **Split first on `MC_ID`**, the last group that needs
|
|---|
| 392 | `M_ID`, so `M_ID` can leave with it.
|
|---|
| 393 | - **Decomposition dependency:** `MC_ID → MC_OPEN, MC_HIGH, MC_LOW, MC_CLOSE, MC_VOLUME, M_ID`
|
|---|
| 394 | (FD14). `MC_ID` is a proper part of K1. The prime `MC_TIMEFRAME` and `MC_CANDLE_TIME` also go
|
|---|
| 395 | into the new relation, so that FD15, which needs them with `M_ID` and `MC_ID`, is preserved.
|
|---|
| 396 | - **New relation `R_D`** = `{ MC_ID, MC_TIMEFRAME, MC_OPEN, MC_HIGH, MC_LOW, MC_CLOSE,
|
|---|
| 397 | MC_VOLUME, MC_CANDLE_TIME, M_ID }` (9 attributes). Dependencies: FD14, FD15. Candidate keys:
|
|---|
| 398 | `MC_ID` and `{M_ID, MC_TIMEFRAME, MC_CANDLE_TIME}`. Normal form: BCNF.
|
|---|
| 399 | - **Residual relation `S4`** = `S3 − { MC_OPEN, MC_HIGH, MC_LOW, MC_CLOSE, MC_VOLUME, M_ID }`
|
|---|
| 400 | (13 attributes). `MC_TIMEFRAME` and `MC_CANDLE_TIME` are prime and stay. Dependencies: FD16–FD18, plus the projected `T_ID → U_ID, C_ID`, `OE_ID → U_ID, C_ID`,
|
|---|
| 401 | `MT_ID → C_ID`, `MC_ID → MC_TIMEFRAME, MC_CANDLE_TIME, C_ID`, `WI_ID → U_ID`.
|
|---|
| 402 | Candidate keys: K1–K4. Normal form: 1NF.
|
|---|
| 403 | - **Dependency preservation:** FD14 and FD15 are in `R_D`, and FD16–FD18 are in `S4`. ✓
|
|---|
| 404 | - **Lossless join:** `R_D ∩ S4 = { MC_ID, MC_TIMEFRAME, MC_CANDLE_TIME }` contains `MC_ID`,
|
|---|
| 405 | and `MC_ID → R_D` (FD14). ✓
|
|---|
| 406 |
|
|---|
| 407 | ### Step 2NF-5 — partial dependency on `W_ID`
|
|---|
| 408 |
|
|---|
| 409 | - **Relation analyzed:** `S4` (13 attributes). Dependencies: as listed for `S4` in the
|
|---|
| 410 | previous step. Candidate keys: K1–K4. Primary key: K1. Normal form: 1NF.
|
|---|
| 411 | - **Remaining 2NF violations:** the groups of `W_ID` and `WI_ID`, and the kept identifiers
|
|---|
| 412 | `U_ID`, `C_ID`. **Split first on `W_ID`**, the last group that needs `U_ID`.
|
|---|
| 413 | - **Decomposition dependency:** `W_ID → W_NAME, W_CREATED_AT, U_ID` (FD16). `W_ID` is a proper
|
|---|
| 414 | part of K2.
|
|---|
| 415 | - **New relation `R_E`** = `{ W_ID, W_NAME, W_CREATED_AT, U_ID }` (4 attributes). Dependencies:
|
|---|
| 416 | FD16. Candidate key: `W_ID`. Normal form: BCNF.
|
|---|
| 417 | - **Residual relation `S5`** = `S4 − { W_NAME, W_CREATED_AT, U_ID }` (10 attributes).
|
|---|
| 418 | Dependencies: FD17, FD18, plus the projected `T_ID → C_ID`, `OE_ID → C_ID`, `MT_ID → C_ID`,
|
|---|
| 419 | `MC_ID → MC_TIMEFRAME, MC_CANDLE_TIME, C_ID`.
|
|---|
| 420 | Candidate keys: K1–K4. Normal form: 1NF.
|
|---|
| 421 | - **Dependency preservation:** FD16 is in `R_E`, and FD17 and FD18 are in `S5`. ✓
|
|---|
| 422 | - **Lossless join:** `R_E ∩ S5 = { W_ID }`, and `W_ID → R_E` (FD16). ✓
|
|---|
| 423 |
|
|---|
| 424 | ### Step 2NF-6 — partial dependency on `WI_ID`
|
|---|
| 425 |
|
|---|
| 426 | - **Relation analyzed:** `S5` (10 attributes). Dependencies: as listed for `S5` in the
|
|---|
| 427 | previous step. Candidate keys: K1–K4. Primary key: K1. Normal form: 1NF.
|
|---|
| 428 | - **Remaining 2NF violations:** the group of `WI_ID`, and the kept identifier `C_ID`.
|
|---|
| 429 | **Split on `WI_ID`**, the last group that needs `C_ID`.
|
|---|
| 430 | - **Decomposition dependency:** `WI_ID → WI_ADDED_AT, W_ID, C_ID` (FD17). `WI_ID` is a proper
|
|---|
| 431 | part of K1, and `WI_ADDED_AT` and `C_ID` are non-prime.
|
|---|
| 432 | - **New relation `R_F`** = `{ WI_ID, WI_ADDED_AT, W_ID, C_ID }` (4 attributes). Dependencies:
|
|---|
| 433 | FD17, FD18. Candidate keys: `WI_ID` and `{W_ID, C_ID}`. Normal form: BCNF.
|
|---|
| 434 | - **Residual relation `S6`** = `S5 − { WI_ADDED_AT, C_ID }` =
|
|---|
| 435 | `{ T_ID, OE_ID, MT_ID, MC_ID, MC_TIMEFRAME, MC_CANDLE_TIME, W_ID, WI_ID }` (8 attributes).
|
|---|
| 436 | `W_ID` is prime and stays. Dependencies: no dependency of the cover lies entirely inside
|
|---|
| 437 | `S6`. The projected ones are `MC_ID → MC_TIMEFRAME, MC_CANDLE_TIME` and `WI_ID → W_ID`, plus
|
|---|
| 438 | derived ones such as `MT_ID, MC_TIMEFRAME, MC_CANDLE_TIME → MC_ID` and `MT_ID, W_ID → WI_ID`.
|
|---|
| 439 | Candidate keys: K1–K4. Normal form: 3NF, because every attribute is prime (and so 2NF).
|
|---|
| 440 | - **Dependency preservation:** FD17 and FD18 are in `R_F`. ✓
|
|---|
| 441 | - **Lossless join:** `R_F ∩ S6 = { WI_ID, W_ID }` contains `WI_ID`, and `WI_ID → R_F`
|
|---|
| 442 | (FD17). ✓
|
|---|
| 443 |
|
|---|
| 444 | **Result of 2NF:** `R_A`, `R_B`, `R_C`, `R_D`, `R_E`, `R_F`, `S6`. All seven are in 2NF (`R_A`
|
|---|
| 445 | only 2NF, `S6` 3NF, the rest BCNF). All 18 dependencies are preserved: FD1–FD11 in `R_A`,
|
|---|
| 446 | FD13 in `R_B`, FD12 in `R_C`, FD14–FD15 in `R_D`, FD16 in `R_E`, FD17–FD18 in `R_F`.
|
|---|
| 447 |
|
|---|
| 448 | ## 3NF decomposition
|
|---|
| 449 |
|
|---|
| 450 | Only `R_A` is not in 3NF. `R_B`–`R_F` are already in BCNF, and `S6` is in 3NF (all its
|
|---|
| 451 | attributes are prime).
|
|---|
| 452 |
|
|---|
| 453 | **Dependencies that violate 3NF in `R_A`.** 3NF forbids a non-prime attribute from depending on
|
|---|
| 454 | a key only **transitively**, through a determinant that is not a superkey. The only key of
|
|---|
| 455 | `R_A` is `T_ID`, but inside `R_A`:
|
|---|
| 456 |
|
|---|
| 457 | - `U_ID → U_*` (FD1), `U_USERNAME → U_ID` (FD2), `U_EMAIL → U_ID` (FD3)
|
|---|
| 458 | - `C_ID → C_*` (FD4), `C_SYMBOL → C_ID` (FD5)
|
|---|
| 459 | - `U_ID, C_ID → H_ID` (FD9), `H_ID → H_*, U_ID, C_ID` (FD8)
|
|---|
| 460 | - `M_ID → M_*, C_ID` (FD6), `C_ID, M_QUOTE_CURRENCY → M_ID` (FD7)
|
|---|
| 461 | - `O_ID → O_*, U_ID, M_ID` (FD10)
|
|---|
| 462 |
|
|---|
| 463 | None of these determinants is a superkey of `R_A`. For example, `T_ID → O_ID → O_PRICE` is a
|
|---|
| 464 | transitive dependency of the non-prime `O_PRICE` on the key.
|
|---|
| 465 |
|
|---|
| 466 | **Order of the steps.** An attribute can leave the residual only after every dependency that
|
|---|
| 467 | needs it has been extracted. FD9 needs `U_ID` and `C_ID` together, and extracting `Markets`
|
|---|
| 468 | takes `C_ID` out of the residual, so `Holdings` must come before `Markets`. Extracting
|
|---|
| 469 | `Orders` takes `M_ID` and `U_ID` out, so `Orders` comes last. The dependencies are therefore
|
|---|
| 470 | taken from the "leaves" of the chain `T_ID → O_ID → {U_ID, M_ID → C_ID}` inward.
|
|---|
| 471 |
|
|---|
| 472 | ### Step 3NF-1 — transitive dependency through `U_ID`
|
|---|
| 473 |
|
|---|
| 474 | - **Relation analyzed:** `R_A` (39 attributes), dependencies FD1–FD11, candidate key and
|
|---|
| 475 | primary candidate key and primary key `T_ID`, normal form 2NF.
|
|---|
| 476 | - **3NF violations:** all five groups listed above. **Split first on `U_ID`**. It is a leaf
|
|---|
| 477 | of the chain: its dependents determine nothing outside its own group.
|
|---|
| 478 | - **Decomposition dependency:** `U_ID → U_*` (FD1). `U_ID` is not a superkey of `R_A`.
|
|---|
| 479 | - **New relation `R_USERS`** = `{ U_ID, U_* }` (10 attributes). Dependencies: FD1, FD2, FD3.
|
|---|
| 480 | Candidate keys: `U_ID`, `U_USERNAME`, `U_EMAIL`. Primary key: `U_ID`. Normal form: BCNF.
|
|---|
| 481 | - **Residual relation `R_A1`** = `R_A − U_*` (30 attributes). Dependencies: FD4–FD11, which
|
|---|
| 482 | also imply `T_ID → H_ID` and `O_ID → H_ID` (through `U_ID, C_ID`).
|
|---|
| 483 | Candidate key and primary key: `T_ID`. Normal form: 2NF.
|
|---|
| 484 | - **Dependency preservation:** FD1–FD3 are in `R_USERS`, and FD4–FD11 are in `R_A1`. ✓
|
|---|
| 485 | - **Lossless join:** `R_USERS ∩ R_A1 = { U_ID }`, and `U_ID → R_USERS` (FD1). ✓
|
|---|
| 486 |
|
|---|
| 487 | ### Step 3NF-2 — transitive dependency through `C_ID`
|
|---|
| 488 |
|
|---|
| 489 | - **Relation analyzed:** `R_A1` (30 attributes), dependencies FD4–FD11, candidate key and primary key `T_ID`, normal form
|
|---|
| 490 | 2NF.
|
|---|
| 491 | - **3NF violations:** `C_ID → C_*`, `U_ID, C_ID → H_ID → H_*`, `M_ID → M_*, C_ID`,
|
|---|
| 492 | `O_ID → O_*, U_ID, M_ID`. **Split first on `C_ID`**, the next leaf.
|
|---|
| 493 | - **Decomposition dependency:** `C_ID → C_*` (FD4).
|
|---|
| 494 | - **New relation `R_CRYPTO`** = `{ C_ID, C_* }` (4 attributes). Dependencies: FD4, FD5.
|
|---|
| 495 | Candidate keys: `C_ID`, `C_SYMBOL`. Primary key: `C_ID`. Normal form: BCNF.
|
|---|
| 496 | - **Residual relation `R_A2`** = `R_A1 − C_*` (27 attributes). Dependencies: FD6–FD11. Candidate
|
|---|
| 497 | key and primary key: `T_ID`. Normal form: 2NF.
|
|---|
| 498 | - **Dependency preservation:** FD4 and FD5 are in `R_CRYPTO`, and FD6–FD11 are in `R_A2`. ✓
|
|---|
| 499 | - **Lossless join:** `R_CRYPTO ∩ R_A2 = { C_ID }`, and `C_ID → R_CRYPTO` (FD4). ✓
|
|---|
| 500 |
|
|---|
| 501 | ### Step 3NF-3 — transitive dependency through `{U_ID, C_ID}`
|
|---|
| 502 |
|
|---|
| 503 | - **Relation analyzed:** `R_A2` (27 attributes), dependencies FD6–FD11, candidate key and primary key `T_ID`, normal form
|
|---|
| 504 | 2NF.
|
|---|
| 505 | - **3NF violations:** `U_ID, C_ID → H_ID → H_*`, `M_ID → M_*, C_ID`, `O_ID → O_*, U_ID, M_ID`.
|
|---|
| 506 | **Split first on `{U_ID, C_ID}`**, because it must come before `Markets` takes `C_ID` away.
|
|---|
| 507 | - **Decomposition dependency:** `U_ID, C_ID → H_ID` (FD9), together with `H_ID → H_*`
|
|---|
| 508 | (FD8).
|
|---|
| 509 | - **New relation `R_HOLDINGS`** = `{ H_ID, H_*, U_ID, C_ID }` (8 attributes). Dependencies:
|
|---|
| 510 | FD8, FD9. Candidate keys: `H_ID`, `{U_ID, C_ID}`. Primary key: `H_ID`. Normal form: BCNF.
|
|---|
| 511 | - **Residual relation `R_A3`** = `R_A2 − { H_ID, H_* }` (21 attributes). Dependencies: FD6,
|
|---|
| 512 | FD7, FD10, FD11. Candidate key and primary key: `T_ID`. Normal form: 2NF.
|
|---|
| 513 | - **Dependency preservation:** FD8 and FD9 are in `R_HOLDINGS`, and the others are in `R_A3`. ✓
|
|---|
| 514 | - **Lossless join:** `R_HOLDINGS ∩ R_A3 = { U_ID, C_ID }`, and `U_ID, C_ID → H_ID → H_*`, so
|
|---|
| 515 | `{U_ID, C_ID} → R_HOLDINGS`. ✓
|
|---|
| 516 |
|
|---|
| 517 | ### Step 3NF-4 — transitive dependency through `M_ID`
|
|---|
| 518 |
|
|---|
| 519 | - **Relation analyzed:** `R_A3` (21 attributes), dependencies FD6, FD7, FD10, FD11, key
|
|---|
| 520 | `T_ID`, normal form 2NF.
|
|---|
| 521 | - **3NF violations:** `M_ID → M_*, C_ID` and `O_ID → O_*, U_ID, M_ID`. **Split first on
|
|---|
| 522 | `M_ID`**, because `Orders` still needs `M_ID`.
|
|---|
| 523 | - **Decomposition dependency:** `M_ID → M_*, C_ID` (FD6).
|
|---|
| 524 | - **New relation `R_MARKETS`** = `{ M_ID, M_*, C_ID }` (5 attributes). Dependencies: FD6, FD7.
|
|---|
| 525 | Candidate keys: `M_ID`, `{C_ID, M_QUOTE_CURRENCY}`. Primary key: `M_ID`. Normal form: BCNF.
|
|---|
| 526 | - **Residual relation `R_A4`** = `R_A3 − { M_*, C_ID }` (17 attributes). No dependency left
|
|---|
| 527 | needs `C_ID`. Dependencies: FD10, FD11. Candidate key and primary key: `T_ID`. Normal form: 2NF.
|
|---|
| 528 | - **Dependency preservation:** FD6 and FD7 are in `R_MARKETS`, and FD10 and FD11 are in
|
|---|
| 529 | `R_A4`. ✓
|
|---|
| 530 | - **Lossless join:** `R_MARKETS ∩ R_A4 = { M_ID }`, and `M_ID → R_MARKETS` (FD6). ✓
|
|---|
| 531 |
|
|---|
| 532 | ### Step 3NF-5 — transitive dependency through `O_ID`
|
|---|
| 533 |
|
|---|
| 534 | - **Relation analyzed:** `R_A4` = `{ T_ID, T_*, O_ID, O_*, U_ID, M_ID }` (17 attributes),
|
|---|
| 535 | dependencies FD10, FD11, candidate key and primary key `T_ID`, normal form 2NF.
|
|---|
| 536 | - **3NF violations:** only `O_ID → O_*, U_ID, M_ID`. **Split on `O_ID`**.
|
|---|
| 537 | - **Decomposition dependency:** `O_ID → O_*, U_ID, M_ID` (FD10).
|
|---|
| 538 | - **New relation `R_ORDERS`** = `{ O_ID, O_*, U_ID, M_ID }` (11 attributes). Dependencies:
|
|---|
| 539 | FD10. Candidate key: `O_ID`. Normal form: BCNF.
|
|---|
| 540 | - **Residual relation `R_TRANSACTIONS`** = `R_A4 − { O_*, U_ID, M_ID }` = `{ T_ID, T_*, O_ID }`
|
|---|
| 541 | (7 attributes). Dependencies: FD11. Candidate key: `T_ID`. Normal form: BCNF. Keeping `U_ID`
|
|---|
| 542 | here would have left the transitive dependency `T_ID → O_ID → U_ID` inside the relation.
|
|---|
| 543 | `T_ID → U_ID` was removed from the cover as redundant, so nothing is lost.
|
|---|
| 544 | - **Dependency preservation:** FD10 is in `R_ORDERS`, and FD11 is in `R_TRANSACTIONS`. ✓
|
|---|
| 545 | - **Lossless join:** `R_ORDERS ∩ R_TRANSACTIONS = { O_ID }`, and `O_ID → R_ORDERS`
|
|---|
| 546 | (FD10). ✓
|
|---|
| 547 |
|
|---|
| 548 | **Result of 3NF:** `R_USERS`, `R_CRYPTO`, `R_HOLDINGS`, `R_MARKETS`, `R_ORDERS`,
|
|---|
| 549 | `R_TRANSACTIONS` (from `R_A`), and `R_B`, `R_C`, `R_D`, `R_E`, `R_F`, `S6` unchanged. 12
|
|---|
| 550 | relations, all in 3NF, and all except `S6` in BCNF. All 18 dependencies are preserved.
|
|---|
| 551 |
|
|---|
| 552 | ## BCNF if possible
|
|---|
| 553 |
|
|---|
| 554 | BCNF requires **every** determinant of a non-trivial dependency to be a superkey, even when
|
|---|
| 555 | the dependent attribute is prime.
|
|---|
| 556 |
|
|---|
| 557 | | Relation | Dependencies in force | Determinants | All superkeys? |
|
|---|
| 558 | |---|---|---|---|
|
|---|
| 559 | | `R_USERS` | FD1, FD2, FD3 | `U_ID`, `U_USERNAME`, `U_EMAIL` | yes |
|
|---|
| 560 | | `R_CRYPTO` | FD4, FD5 | `C_ID`, `C_SYMBOL` | yes |
|
|---|
| 561 | | `R_MARKETS` | FD6, FD7 | `M_ID`, `{C_ID, M_QUOTE_CURRENCY}` | yes |
|
|---|
| 562 | | `R_HOLDINGS` | FD8, FD9 | `H_ID`, `{U_ID, C_ID}` | yes |
|
|---|
| 563 | | `R_ORDERS` | FD10 | `O_ID` | yes |
|
|---|
| 564 | | `R_TRANSACTIONS` | FD11 | `T_ID` | yes |
|
|---|
| 565 | | `R_B` | FD13 | `OE_ID` | yes |
|
|---|
| 566 | | `R_C` | FD12 | `MT_ID` | yes |
|
|---|
| 567 | | `R_D` | FD14, FD15 | `MC_ID`, `{M_ID, MC_TIMEFRAME, MC_CANDLE_TIME}` | yes |
|
|---|
| 568 | | `R_E` | FD16 | `W_ID` | yes |
|
|---|
| 569 | | `R_F` | FD17, FD18 | `WI_ID`, `{W_ID, C_ID}` | yes |
|
|---|
| 570 | | `S6` | `MC_ID → MC_TIMEFRAME, MC_CANDLE_TIME`; `WI_ID → W_ID`; derived ones such as `MT_ID, MC_TIMEFRAME, MC_CANDLE_TIME → MC_ID` and `MT_ID, W_ID → WI_ID` | `MC_ID`, `WI_ID`, `{MT_ID, MC_TIMEFRAME, MC_CANDLE_TIME}`, `{MT_ID, W_ID}`, … | **no** |
|
|---|
| 571 |
|
|---|
| 572 | **Dependencies that violate BCNF — only in `S6`.** `MC_ID` determines `MC_TIMEFRAME` and
|
|---|
| 573 | `MC_CANDLE_TIME`, and `WI_ID` determines `W_ID`, but neither `MC_ID` nor `WI_ID` is a superkey
|
|---|
| 574 | of `S6`. 3NF allowed this because the dependent attributes are prime. BCNF does not. The derived
|
|---|
| 575 | dependencies all involve `W_ID` or `MC_TIMEFRAME`/`MC_CANDLE_TIME`, so they disappear once the
|
|---|
| 576 | two steps below remove those attributes.
|
|---|
| 577 |
|
|---|
| 578 | ### Step BCNF-1 — `MC_ID → MC_TIMEFRAME, MC_CANDLE_TIME`
|
|---|
| 579 |
|
|---|
| 580 | - **Relation analyzed:** `S6` (8 attributes), dependencies as in the table above, candidate
|
|---|
| 581 | keys K1–K4, primary key K1, normal form 3NF.
|
|---|
| 582 | - **BCNF violations:** `MC_ID → MC_TIMEFRAME, MC_CANDLE_TIME` and `WI_ID → W_ID`, and the
|
|---|
| 583 | derived ones that depend on them. **Split first on `MC_ID`**. The order does not matter
|
|---|
| 584 | here, because the two violations share no attribute.
|
|---|
| 585 | - **Decomposition dependency:** `MC_ID → MC_TIMEFRAME, MC_CANDLE_TIME`. `MC_ID` is not a
|
|---|
| 586 | superkey of `S6`.
|
|---|
| 587 | - **New relation** `{ MC_ID, MC_TIMEFRAME, MC_CANDLE_TIME }`. Dependencies:
|
|---|
| 588 | `MC_ID → MC_TIMEFRAME, MC_CANDLE_TIME`. Key: `MC_ID`. Normal form: BCNF. It is a
|
|---|
| 589 | projection of `R_D`, which already contains these attributes with the same key, so it adds no
|
|---|
| 590 | information and is merged into `R_D`.
|
|---|
| 591 | - **Residual relation `S7`** = `{ T_ID, OE_ID, MT_ID, MC_ID, W_ID, WI_ID }` (6 attributes).
|
|---|
| 592 | Dependencies: `WI_ID → W_ID`, and derived ones such as `MT_ID, W_ID → WI_ID`. Candidate
|
|---|
| 593 | keys: `{T_ID, OE_ID, MT_ID, MC_ID, WI_ID}` (K1) and `{T_ID, OE_ID, MT_ID, MC_ID, W_ID}` (K2).
|
|---|
| 594 | Normal form: 3NF.
|
|---|
| 595 | - **Dependency preservation:** no dependency of the cover is affected. FD14 and FD15 are in
|
|---|
| 596 | `R_D`. ✓
|
|---|
| 597 | - **Lossless join:** the intersection is `{ MC_ID }`, and `MC_ID → { MC_ID, MC_TIMEFRAME,
|
|---|
| 598 | MC_CANDLE_TIME }`. ✓
|
|---|
| 599 |
|
|---|
| 600 | ### Step BCNF-2 — `WI_ID → W_ID`
|
|---|
| 601 |
|
|---|
| 602 | - **Relation analyzed:** `S7` (6 attributes), dependencies `WI_ID → W_ID` and derived ones,
|
|---|
| 603 | candidate keys K1, K2, primary key K1, normal form 3NF.
|
|---|
| 604 | - **BCNF violations:** only `WI_ID → W_ID` (and the derived `MT_ID, W_ID → WI_ID`). **Split on
|
|---|
| 605 | `WI_ID`**.
|
|---|
| 606 | - **Decomposition dependency:** `WI_ID → W_ID`. `WI_ID` is not a superkey of `S7`.
|
|---|
| 607 | - **New relation** `{ WI_ID, W_ID }`. Dependencies: `WI_ID → W_ID`. Key: `WI_ID`. Normal
|
|---|
| 608 | form: BCNF. For the same reason as in BCNF-1, it is merged into `R_F`.
|
|---|
| 609 | - **Residual relation `R_KEY`** = `{ T_ID, OE_ID, MT_ID, MC_ID, WI_ID }` (5 attributes). No
|
|---|
| 610 | non-trivial dependency holds among these attributes. Candidate key: all five (= K1).
|
|---|
| 611 | Normal form: BCNF.
|
|---|
| 612 | - **Dependency preservation:** no dependency of the cover is affected. FD17 and FD18 are in
|
|---|
| 613 | `R_F`. The derived dependencies of `S6`/`S7` follow from FD12, FD15, FD17 and FD18, which
|
|---|
| 614 | are all preserved. ✓
|
|---|
| 615 | - **Lossless join:** the intersection is `{ WI_ID }`, and `WI_ID → { WI_ID, W_ID }`. ✓
|
|---|
| 616 |
|
|---|
| 617 | **Result: every relation is in BCNF.** The decomposition into these 12 relations is lossless
|
|---|
| 618 | (each of the 13 binary steps passed the test) and preserves all 18 dependencies of the
|
|---|
| 619 | canonical cover.
|
|---|
| 620 |
|
|---|
| 621 | ## Final result and discussion
|
|---|
| 622 |
|
|---|
| 623 | ### Normalized relational model
|
|---|
| 624 |
|
|---|
| 625 | Each relation is followed by its keys (primary key first). An attribute that is the
|
|---|
| 626 | identifier of another relation is marked `→` with that relation.
|
|---|
| 627 |
|
|---|
| 628 | ```
|
|---|
| 629 | R_USERS (U_ID, U_USERNAME, U_EMAIL, U_FULL_NAME, U_PASSWORD_HASH,
|
|---|
| 630 | U_AVAILABLE_BALANCE, U_INVESTED_BALANCE, U_RESERVED_BALANCE,
|
|---|
| 631 | U_CREATED_AT, U_UPDATED_AT)
|
|---|
| 632 | keys: U_ID; U_USERNAME; U_EMAIL
|
|---|
| 633 | R_CRYPTO (C_ID, C_SYMBOL, C_NAME, C_CREATED_AT)
|
|---|
| 634 | keys: C_ID; C_SYMBOL
|
|---|
| 635 | R_MARKETS (M_ID, C_ID → R_CRYPTO, M_QUOTE_CURRENCY, M_IS_ACTIVE, M_CREATED_AT)
|
|---|
| 636 | keys: M_ID; {C_ID, M_QUOTE_CURRENCY}
|
|---|
| 637 | R_HOLDINGS (H_ID, U_ID → R_USERS, C_ID → R_CRYPTO, H_QUANTITY,
|
|---|
| 638 | H_RESERVED_QUANTITY, H_AVG_PRICE, H_CREATED_AT, H_UPDATED_AT)
|
|---|
| 639 | keys: H_ID; {U_ID, C_ID}
|
|---|
| 640 | R_ORDERS (O_ID, U_ID → R_USERS, M_ID → R_MARKETS, O_SIDE, O_TYPE, O_STATUS,
|
|---|
| 641 | O_QUANTITY, O_FILLED_QUANTITY, O_PRICE, O_PLACED_AT, O_EXECUTED_AT)
|
|---|
| 642 | key: O_ID
|
|---|
| 643 | R_TRANSACTIONS (T_ID, O_ID → R_ORDERS, T_TYPE, T_AMOUNT, T_CURRENCY, T_CREATED_AT,
|
|---|
| 644 | T_DESCRIPTION)
|
|---|
| 645 | key: T_ID
|
|---|
| 646 | R_MARKET_TRADES (MT_ID, M_ID → R_MARKETS, MT_EXECUTED_AT, MT_PRICE, MT_QUANTITY,
|
|---|
| 647 | MT_SIDE, MT_SOURCE, O_ID_FILLSBUY → R_ORDERS (nullable),
|
|---|
| 648 | O_ID_FILLSSELL → R_ORDERS (nullable)) [= R_C]
|
|---|
| 649 | key: MT_ID
|
|---|
| 650 | R_ORDER_EVENTS (OE_ID, O_ID → R_ORDERS, OE_EVENT_TYPE, OE_QUANTITY, OE_PRICE,
|
|---|
| 651 | OE_STATUS_AFTER, OE_CREATED_AT) [= R_B]
|
|---|
| 652 | key: OE_ID
|
|---|
| 653 | R_MARKET_CANDLES (MC_ID, M_ID → R_MARKETS, MC_TIMEFRAME, MC_OPEN, MC_HIGH, MC_LOW,
|
|---|
| 654 | MC_CLOSE, MC_VOLUME, MC_CANDLE_TIME) [= R_D]
|
|---|
| 655 | keys: MC_ID; {M_ID, MC_TIMEFRAME, MC_CANDLE_TIME}
|
|---|
| 656 | R_WATCHLISTS (W_ID, U_ID → R_USERS, W_NAME, W_CREATED_AT) [= R_E]
|
|---|
| 657 | key: W_ID
|
|---|
| 658 | R_WATCHLIST_ITEMS(WI_ID, W_ID → R_WATCHLISTS, C_ID → R_CRYPTO, WI_ADDED_AT) [= R_F]
|
|---|
| 659 | keys: WI_ID; {W_ID, C_ID}
|
|---|
| 660 | R_KEY (T_ID, OE_ID, MT_ID, MC_ID, WI_ID) [= R_KEY]
|
|---|
| 661 | key: all five
|
|---|
| 662 | ```
|
|---|
| 663 |
|
|---|
| 664 | ### Discussion
|
|---|
| 665 |
|
|---|
| 666 | **The eleven data relations are the P2 design, with one difference** (`transactions.user_id`,
|
|---|
| 667 | explained below). Each relation is one entity set of the ER model:
|
|---|
| 668 |
|
|---|
| 669 | | P5 relation | P2 table | How the relationships appear |
|
|---|
| 670 | |---|---|---|
|
|---|
| 671 | | `R_USERS` | `users` | — |
|
|---|
| 672 | | `R_CRYPTO` | `crypto` | — |
|
|---|
| 673 | | `R_MARKETS` | `markets` | `C_ID` = `crypto_id` (`QuotedOn`) |
|
|---|
| 674 | | `R_HOLDINGS` | `holdings` | `U_ID` = `user_id` (`Holds`), `C_ID` = `crypto_id` (`PositionIn`) |
|
|---|
| 675 | | `R_ORDERS` | `orders` | `U_ID` = `user_id` (`Places`), `M_ID` = `market_id` (`PlacedOn`) |
|
|---|
| 676 | | `R_TRANSACTIONS` | `transactions` | `O_ID` = `related_order` (`Settles`); P2 also stores `user_id` (`Records`), see below |
|
|---|
| 677 | | `R_MARKET_TRADES` | `market_trades` | `M_ID` = `market_id` (`Fills`), `O_ID_FILLSBUY` = `buy_order_id`, `O_ID_FILLSSELL` = `sell_order_id` |
|
|---|
| 678 | | `R_ORDER_EVENTS` | `order_events` | `O_ID` = `order_id` (`Logs`) |
|
|---|
| 679 | | `R_MARKET_CANDLES` | `market_candles` | `M_ID` = `market_id` (`Aggregates`) |
|
|---|
| 680 | | `R_WATCHLISTS` | `watchlists` | `U_ID` = `user_id` (`Owns`) |
|
|---|
| 681 | | `R_WATCHLIST_ITEMS` | `watchlist_items` | `W_ID` = `watchlist_id` (`Contains`), `C_ID` = `crypto_id` (`Lists`) |
|
|---|
| 682 |
|
|---|
| 683 | The two methods produce the foreign keys differently. In P2 they come from a transformation
|
|---|
| 684 | rule: a 1:N relationship becomes a column on the N side. Here, each one appears because a
|
|---|
| 685 | dependency of kind (R), for example `O_ID → U_ID`, keeps the other entity's identifier in the
|
|---|
| 686 | same relation as the entity that depends on it. The candidate keys also match, including the
|
|---|
| 687 | composite ones (`{C_ID, M_QUOTE_CURRENCY}`, `{U_ID, C_ID}`, `{M_ID, MC_TIMEFRAME,
|
|---|
| 688 | MC_CANDLE_TIME}`, `{W_ID, C_ID}`). They are exactly the `UNIQUE` constraints in
|
|---|
| 689 | [`schema_creation.sql`](../../server/db/schema_creation.sql).
|
|---|
| 690 |
|
|---|
| 691 | **The one difference: `transactions.user_id`.** The decomposition drops `U_ID` from
|
|---|
| 692 | `R_TRANSACTIONS`, because `T_ID → U_ID` follows from `T_ID → O_ID` and `O_ID → U_ID`. That is
|
|---|
| 693 | correct for every ledger entry that settles an order. It does not work for a **deposit**.
|
|---|
| 694 | `Settles` is partial, so a deposit has no order, and without `user_id` a deposit would have no
|
|---|
| 695 | owner at all. The de-normalized relation cannot show this case. Every one of its tuples
|
|---|
| 696 | contains an order (every key contains `OE_ID`, and every order event has an order), so a
|
|---|
| 697 | ledger entry without an order cannot appear in it. P2 therefore keeps `user_id` (the
|
|---|
| 698 | relationship `Records`) as a deliberate exception. As a result, the implemented
|
|---|
| 699 | `transactions` table is in **2NF but not in 3NF** (`related_order → user_id` is a transitive
|
|---|
| 700 | dependency), and this is by design. For entries with an order,
|
|---|
| 701 | `transactions.user_id` repeats the order's user. The only code that sets `related_order` (the buy
|
|---|
| 702 | and sell inserts in `advanced_db.sql`) writes the user and the id of the same order row. No
|
|---|
| 703 | database constraint enforces this.
|
|---|
| 704 |
|
|---|
| 705 | **Two order columns in `market_trades`.** `FillsBuy` and `FillsSell` needed two role
|
|---|
| 706 | attributes already in the de-normalized relation, and both end up in `R_MARKET_TRADES`.
|
|---|
| 707 | They correspond to `buy_order_id` and `sell_order_id`.
|
|---|
| 708 |
|
|---|
| 709 | **`R_KEY` belongs to the formal result, but it is not implemented as a table.** It is the
|
|---|
| 710 | relation that contains a key of `R_EDUBERZA`, and the lossless-join result above holds for all
|
|---|
| 711 | 12 relations *including* it. It records no fact of the domain. It only says which ledger
|
|---|
| 712 | entry, order event, trade, candle and watchlist item were put into the same tuple, and that
|
|---|
| 713 | combination exists only because we started from one single relation. Not implementing it is
|
|---|
| 714 | an implementation decision. The eleven implemented tables are not claimed to reconstruct
|
|---|
| 715 | `R_EDUBERZA` on their own. They keep every attribute and every dependency of the canonical
|
|---|
| 716 | cover, and that is what the application needs.
|
|---|
| 717 |
|
|---|
| 718 | **`holdings.avg_price`** is shown as a *derived* attribute in the ER model: it can be
|
|---|
| 719 | recomputed from the buy history. It is still stored, and that is a deliberate
|
|---|
| 720 | denormalisation (see [RelationalDesign](../P2-RelationalDesign/RelationalDesign.md#normalisation)).
|
|---|
| 721 | Normalisation cannot detect this. `H_ID → H_AVG_PRICE` is an ordinary functional dependency,
|
|---|
| 722 | because "derivable from rows of another entity" is a property of the application logic
|
|---|
| 723 | that maintains the value (see [UseCase0004](../P3-UseCaseModel/UseCase0004.md),
|
|---|
| 724 | `ON CONFLICT … DO UPDATE`), not a dependency between attributes of one tuple.
|
|---|
| 725 |
|
|---|
| 726 | **Which design is used going forward:** P2's, unchanged. The eleven data relations coincide
|
|---|
| 727 | with the eleven tables of [`schema_creation.sql`](../../server/db/schema_creation.sql) and
|
|---|
| 728 | [`advanced_db.sql`](../../server/db/advanced_db.sql) column for column, except for the
|
|---|
| 729 | deliberately kept `transactions.user_id` explained above. So there are no database objects
|
|---|
| 730 | to restructure, and the prototype and the reports of P6/P7 keep working against the same
|
|---|
| 731 | schema.
|
|---|