Към съдържанието
wannacode

Естествен ключ и денормализация

Накратко: Естественият ключ идва от самите данни, като имейла, изкуственият е номер без значение, а денормализацията е копие на данни, пазено нарочно.

Проблем

Мария смени имейла си

Ако проектираш клиентите сам, имейлът изглежда добър първичен ключ. Уникален е, всеки клиент го има, с него се влиза в сайта. Ето такъв проект: ключът на клиента е имейлът, а поръчките сочат към имейла.

customers

  • PKemailtext
  • nametext *
  • citytext

orders

  • PKidinteger
  • FKcustomer_emailtext *→ customers
  • ordered_attext *

Мария сменя имейла си:

SQLпразна база

Базата не пуска промяната, защото три поръчки сочат към стария имейл. Ако смениш и тях, остават фактурите, които вече са пратени, отчетите и всяка друга система, която е запомнила стария ключ.

Идеята

Естествен и изкуствен ключ

Ключ, взет от света (имейл, телефон, номер на книга), е естествен ключ. Число, което базата дава сама и което няма смисъл извън нея, е изкуствен ключ (на английски surrogate key). Такъв е id в магазина.

Изкуственият ключ не се сменя никога, защото нищо в света не го променя. Затова имейлът се сменя на един ред, а поръчките не забелязват:

SQLбаза: магазин

Имейлът не губи правилото си. Той вече не е ключ, но е UNIQUE, затова двама клиенти не могат да имат един имейл:

SQLбаза: магазин

А ЕГН? Не. То е лични данни, а ключът се копира във всяка таблица, която сочи към клиента. Клиент от чужбина обикновено няма ЕГН. И магазинът няма нужда да го знае.

По-точно, за любопитните

ON UPDATE CASCADE в REFERENCES кара базата да смени и редовете, които сочат към стария ключ. Това помага вътре в базата, но не и за копията на ключа извън нея: в писма, в логове, в друга система.

Естественият ключ е на място, когато стойността наистина не се мени и се знае отвън. Пример е кодът на валутата (EUR) или на държавата (BG).

UUID е друг вид изкуствен ключ: 128-битово число, записвано като 550e8400-e29b-41d4-a716-446655440000. Прави се навсякъде, без да питаш базата (в приложението, в телефона без интернет), две бази могат да се слеят без сблъсък, а адресът /orders/1043 вече не казва колко поръчки има магазинът. Цената е, че е по-дълъг (16 байта срещу 8 за голямо цяло число), а случайните UUID се пишат на случайни места в индекса и го забавят. Затова има и подредени по време UUID (версия 7). PostgreSQL има тип uuid и функция gen_random_uuid(). SQLite няма отделен тип и го пази като текст или като BLOB.

Пробвай

Нов имейл

Клиент 12, Борис, пише, че новият му имейл е boris.m@example.com. Поправи го.

SQLбаза: магазин

Идеята

Цената в реда на поръчката

В order_items има unit_price, а в products има price. Това изглежда като повторение, което нормализацията маха. Вдигни цената на кафето:

SQLбаза: магазин

Старите поръчки помнят 14,90 €, а продуктът струва 15,90 €. Това не са две копия на един факт, а два различни факта: „колко струва днес“ и „колко е платил клиентът“. Ако поръчката взимаше цената от products, всяко поскъпване щеше да промени и миналите сметки.

1. Цената на кафето в products стана 15,90 €. Какво трябва да покаже старата поръчка 8?

Идеята

Денормализация

Сумата на поръчката е друго. Тя наистина се смята от редовете. Но списъкът с поръчки в сайта я показва за всяка, и вместо да събираш редовете всеки път, можеш да я пазиш в orders:

SQLбаза: магазин

Съзнателното копие на данни, които могат да се изчислят, се казва денормализация. Прави се, когато четенето е много по-често от промяната и сметката е скъпа.

Виж го

Сумата, която лъже

Колега добавя кафе в поръчка 60, но забравя за total:

SQLбаза: магазин

Сайтът показва 14 €, а редовете дават 28,90 €. Нищо в базата не е спряло това. Цената на копието е, че всяка промяна на редовете трябва да промени и сумата: в програмата, на всяко място, което пише в order_items, или с тригер (модулът за транзакции).

Упражнение

Две задачи

1. Счетоводството подозира, че някои суми не са верни. Намери поръчките, при които total не е равно на сбора на редовете. Покажи номера, записаната сума и истинската.

Поръчките с грешна сума:

SQLбаза: магазин

2. Поправи сумите на двете поръчки, като ги сметнеш наново от редовете.

Обнови total на поръчки 59 и 60:

SQLбаза: магазин

Тест

Провери се

1. Кое е изкуствен (surrogate) ключ?
2. Магазинът ползва id като ключ. Трябва ли имейлът все пак да е UNIQUE?
3. Защо unit_price в order_items не нарушава нормализацията?
4. Колона total в orders пази сбора на редовете. Каква е цената?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·