SQL tabelite täiendava normaliseerimine interaktiivne õpetus

Õpi samm-sammult, kuidas viia tabel kõrgematele normaalkujudele ja rakendada ortogonaalsuse põhimõtet.

Algne tabel (1NK)

Alustame tabeliga Kliendi_rentimine. Tabel on esimesel normaalkujul (1NK), kuna kõik veerud sisaldavad atomaarseid väärtusi ja igal real on unikaalne identifikaator (primaarvõti).

Jälgime edaspidi esimese rea (roheliselt tähistatud) andmete liikumist normaliseerimise käigus.

Tabel: Kliendi_rentimine

Kliendi_rentimine (
    kliendi_nr,
    vara_nr,        -- PK osa
    kliendi_eesnimi,
    kliendi_perenimi,
    vara_aadress,
    rendi_algus,    -- PK osa
    rendi_lopp,
    rent,
    omaniku_nr,
    omaniku_eesnimi,
    omaniku_perenimi
)
Primaarvõti (PK): (vara_nr, rendi_algus)

(PK) - tähistab primaarvõtme osa tabeli päises.

Näidisandmed

Tabeli Kliendi_rentimine näidisandmed. Vara V001 rent on 520€. Esimene rida on roheliselt märgitud.
kliendi_nr vara_nr(PK) kliendi_eesnimi kliendi_perenimi vara_aadress rendi_algus(PK) rendi_lopp rent omaniku_nr omaniku_eesnimi omaniku_perenimi
K001 V001 Jaan Tamm Pikk tn 1, Tartu 2023-01-15 2024-01-14 520€ O001 Peeter Kask
K002 V002 Mari Maasikas Lai tn 5, Tallinn 2023-03-01 2024-02-29 750€ O002 Anna Mänd
K001 V003 Jaan Tamm Mere pst 8, Pärnu 2023-05-10 600€ O003 Mari Maasikas
K003 V001 Eva Kuusk Pikk tn 1, Tartu 2024-02-01 2025-01-31 520€ O001 Peeter Kask
K002 V004 Mari Maasikas Metsa tee 3, Viljandi 2024-04-01 480€ O002 Anna Mänd

Märkad endiselt, et Jaani nimi ja Mari nimi korduvad eri ridades ja rollides.

Tabelite viimine teisele normaalkujule (2NK)

Funktsionaalsed sõltuvused (1NK tabelis)

Esmalt tuvastame funktsionaalsed sõltuvused algses tabelis Kliendi_rentimine. Primaarvõti on liitvõti (vara_nr, rendi_algus).

fs1: (vara_nr, rendi_algus) -> (kliendi_nr, kliendi_eesnimi, kliendi_perenimi, rendi_lopp, vara_aadress, omaniku_nr, omaniku_eesnimi, omaniku_perenimi)
   (Rent ei sõltu enam tervikvõtmest, seega eemaldatud siit)

fs2: kliendi_nr -> (kliendi_eesnimi, kliendi_perenimi)
   (Kliendi number määrab kliendi nime) - Transitiivne sõltuvus

fs3: vara_nr -> (vara_aadress, rent, omaniku_nr, omaniku_eesnimi, omaniku_perenimi)
   (Vara number määrab vara aadressi, rendi ja omaniku info) - Osaline sõltuvus primaarvõtmest (sõltub ainult `vara_nr`-st)

fs4: omaniku_nr -> (omaniku_eesnimi, omaniku_perenimi)
   (Omaniku number määrab omaniku nime) - Transitiivne sõltuvus (fs3 kaudu)

2NK Definitsioon ja Põhjendus

Tabel on teisel normaalkujul (2NK), kui:

  1. See on esimesel normaalkujul (1NK).
  2. Iga mitte-primaarvõtme veerg on täielikult funktsionaalselt sõltuv primaarvõtmest.

Meie tabelis rikub 2NK tingimust osaline sõltuvus fs3, kus veerud (vara_aadress, rent, omaniku_nr, omaniku_eesnimi, omaniku_perenimi) sõltuvad ainult primaarvõtme osast vara_nr.

Lahendus: Eemaldame osalise sõltuvuse, luues uue tabeli Vara_omanik nende veergude jaoks, mille determinantiks on vara_nr.

Tulemuseks olevad tabelid (2NK)

Selle tulemusena jagame algse tabeli kaheks. Jälgime roheliselt märgitud andmete liikumist. Pange tähele, et `rent` on nüüd tabelis `Vara_omanik`.

Tabel 1: Vara_rentimine

Vara_rentimine (
    kliendi_nr (FK -> ?), -- Viitab tulevasele Klient tabelile
    kliendi_eesnimi,
    kliendi_perenimi,
    vara_nr (PK osa) (FK -> Vara_omanik),
    rendi_algus (PK osa),
    rendi_lopp
)
Primaarvõti (PK): (vara_nr, rendi_algus)
Välisvõti (FK): vara_nr -> Vara_omanik(vara_nr)
(Rendi summa eemaldati siit, kuna see sõltub osaliselt PK-st)

Tabel 2: Vara_omanik

Vara_omanik (
    vara_nr (PK),
    vara_aadress,
    rent, -- Rent kuulub nüüd siia, kuna sõltub ainult varast
    omaniku_nr (FK -> ?), -- Viitab tulevasele Omanik tabelile
    omaniku_eesnimi,
    omaniku_perenimi
)
Primaarvõti (PK): vara_nr

Näidisandmed (2NK)

Vara_rentimine tabel

Vara_rentimine andmed. Rent on siit eemaldatud. Roheliselt on märgitud algsest esimesest reast pärinevad andmed.
kliendi_nr kliendi_eesnimi kliendi_perenimi vara_nr(PK)(FK) rendi_algus(PK) rendi_lopp
K001JaanTammV0012023-01-152024-01-14
K002MariMaasikasV0022023-03-012024-02-29
K001JaanTammV0032023-05-10
K003EvaKuuskV0012024-02-012025-01-31
K002MariMaasikasV0042024-04-01

Vara_omanik tabel

Vara_omanik andmed. Rent on nüüd siin. Roheliselt märgitud algsest reast pärinevad andmed.
vara_nr(PK) vara_aadress rent omaniku_nr omaniku_eesnimi omaniku_perenimi
V001Pikk tn 1, Tartu520€O001PeeterKask
V002Lai tn 5, Tallinn750€O002AnnaMänd
V003Mere pst 8, Pärnu600€O003MariMaasikas
V004Metsa tee 3, Viljandi480€O002AnnaMänd

* Märkus: Rent asub nüüd õiges tabelis vastavalt eeldusele, et see sõltub ainult varast.

Tabelite viimine kolmandale normaalkujule (3NK)

Funktsionaalsed sõltuvused (2NK tabelites)

Vaatame nüüd 2NK tabeleid ja nende sõltuvusi:

Tabel: Vara_rentimine

fs1: (vara_nr, rendi_algus) -> (kliendi_nr, kliendi_eesnimi, kliendi_perenimi, rendi_lopp)
   (Primaarvõti määrab ülejäänud veerud)

fs2: kliendi_nr -> (kliendi_eesnimi, kliendi_perenimi)
   (Kliendi nr määrab nime) - Transitiivne sõltuvus (PK -> kliendi_nr -> nimi)

Tabel: Vara_omanik

fs3: vara_nr -> (vara_aadress, rent, omaniku_nr, omaniku_eesnimi, omaniku_perenimi)
    (Primaarvõti määrab ülejäänud veerud)

fs4: omaniku_nr -> (omaniku_eesnimi, omaniku_perenimi)
   (Omaniku nr määrab nime) - Transitiivne sõltuvus (PK -> omaniku_nr -> nimi)

3NK Definitsioon ja Põhjendus

Tabel on kolmandal normaalkujul (3NK), kui:

  1. See on teisel normaalkujul (2NK).
  2. Mitte ükski mitte-primaarvõtme veerg ei sõltu transitiivselt primaarvõtmest.

Meie tabelites rikuvad 3NK tingimust transitiivsed sõltuvused:

  • Tabelis Vara_rentimine: fs2, kus kliendi nimi sõltub kliendi_nr-st.
  • Tabelis Vara_omanik: fs4, kus omaniku nimi sõltub omaniku_nr-st.

Lahendus: Eemaldame transitiivsed sõltuvused, luues eraldi tabelid Klient ja Omanik.

Tulemuseks olevad tabelid (3NK)

Jagame tabelid edasi. Roheliselt on tähistatud endiselt algsest 1NK tabeli esimesest reast pärit andmed. Rent jääb nüüd tabelisse Vara.

Tabel 1: Klient

Klient (
    kliendi_nr (PK),
    kliendi_eesnimi,
    kliendi_perenimi
)
Primaarvõti (PK): kliendi_nr

Tabel 2: Rentimine

Rentimine (
    vara_nr (PK osa) (FK -> Vara),
    kliendi_nr (FK -> Klient),
    rendi_algus (PK osa),
    rendi_lopp
)
Primaarvõti (PK): (vara_nr, rendi_algus)
Välisvõti (FK): kliendi_nr -> Klient(kliendi_nr)
Välisvõti (FK): vara_nr -> Vara(vara_nr)

Tabel 3: Vara

Vara (
    vara_nr (PK),
    vara_aadress,
    rent, -- Rent on nüüd siin
    omaniku_nr (FK -> Omanik)
)
Primaarvõti (PK): vara_nr
Välisvõti (FK): omaniku_nr -> Omanik(omaniku_nr)

Tabel 4: Omanik

Omanik (
    omaniku_nr (PK),
    omaniku_eesnimi,
    omaniku_perenimi
)
Primaarvõti (PK): omaniku_nr

Näidisandmed (3NK)

Klient tabel

Klientide andmed. Klient K001 pärineb algsest reast.
kliendi_nr(PK)kliendi_eesnimikliendi_perenimi
K001JaanTamm
K002MariMaasikas
K003EvaKuusk

Rentimine tabel

Rendilepingute andmed (ilma rendisummata). Esimene rida pärineb algsest reast.
vara_nr(PK)(FK) kliendi_nr(FK) rendi_algus(PK) rendi_lopp
V001K0012023-01-152024-01-14
V002K0022023-03-012024-02-29
V003K0012023-05-10
V001K0032024-02-012025-01-31
V004K0022024-04-01

Vara tabel

Varade andmed (koos rendiga). Vara V001 pärineb algsest reast.
vara_nr(PK) vara_aadress rent omaniku_nr(FK)
V001Pikk tn 1, Tartu520€O001
V002Lai tn 5, Tallinn750€O002
V003Mere pst 8, Pärnu600€O003
V004Metsa tee 3, Viljandi480€O002

Omanik tabel

Omanike andmed. Omanik O001 pärineb algsest reast.
omaniku_nr(PK)omaniku_eesnimiomaniku_perenimi
O001PeeterKask
O002AnnaMänd
O003MariMaasikas

Tabelite viimine Boyce/Coddi normaalkujule (BCNF)

BCNF Definitsioon

Tabel on Boyce/Coddi normaalkujul (BCNF), kui iga funktsionaalse sõltuvuse (X -> Y) korral on X supervõti.

Praktikas tähendab see, et iga sõltuvuse determinant (vasak pool, X) peab olema kandidaatvõti.

Sõltuvused 3NK tabelites

Klient
fs_k: kliendi_nr -> (kliendi_eesnimi, kliendi_perenimi)
   (Determinant on PK. BCNF.)

Rentimine
fs_r: (vara_nr, rendi_algus) -> (kliendi_nr, rendi_lopp)
   (Determinant on PK. BCNF.)

Vara
fs_v: vara_nr -> (vara_aadress, rent, omaniku_nr)
   (Determinant on PK. BCNF.)

Omanik
fs_o: omaniku_nr -> (omaniku_eesnimi, omaniku_perenimi)
   (Determinant on PK. BCNF.)

Järeldus: Kõikides 3NK tabelites on ainsate funktsionaalsete sõltuvuste determinandid primaarvõtmed. Kuna primaarvõti on kandidaatvõti, siis kõik meie 3NK tabelid on juba ka BCNF kujul.

BCNF on rangem kui 3NK ja probleemid tekivad tavaliselt siis, kui tabelis on mitu kattuvat kandidaatvõtit ja sõltuvused ei lähe alati välja terviklikust kandidaatvõtmest.

(Andmete kuvamisel muudatusi ei ole võrreldes 3NK sammuga, seega tabeleid uuesti ei kuvata.)

Arutelu viienda normaalkuju (5NK) ja ortogonaalsuse üle

Viienda normaalkuju (5NK) reeglid

BCNF on sageli piisav. Vaatame siiski 5NK reegleid:

  • Reegel 1: Kui tabel on BCNF ja mõni kandidaatvõti on lihtne (1 veerg), siis tabel on 4NK.
  • Reegel 2: Kui tabel on 3NK/BCNF ja *iga* kandidaatvõti on lihtne, siis on tabel 5NK.
  • Reegel 3: Kui tabel on BCNF ja sel on vähemalt üks atribuut, mis ei kuulu ühtegi kandidaatvõtmesse, siis tabel on 5NK.

Tabelite analüüs 5NK reeglite järgi

  • Klient: BCNF, PK lihtne. 5NK (Reegel 2).
  • Omanik: BCNF, PK lihtne. 5NK (Reegel 2).
  • Vara: BCNF, PK lihtne. Sisaldab mitte-võtmeid (`vara_aadress`, `rent`, `omaniku_nr`). 5NK (Reegel 3 või 2).
  • Rentimine: BCNF. PK pole lihtne. Sisaldab mitte-võtmeid (`kliendi_nr`, `rendi_lopp`). 5NK (Reegel 3).

Kõik tabelid näivad olevat ka 5NK.

Andmete liiasus ja ortogonaalsus

Probleem: Varjatud andmete liiasus

Vaatamata kõrgetele normaalkujudele, on endiselt probleem: Mari Maasikas.

  • Tema nimi on tabelis Klient.
  • Tema nimi on tabelis Omanik.

See on liiasus. Nime muutmine nõuaks muutust kahes kohas.

Ortogonaalse andmebaasi disaini printsiip

Ortogonaalne disain: iga fakt ühes kohas. Tabelid sõltumatud. Praegu rikutud.

Lahendus: Isiku tabel

Lahendus: Loome keskse Isik tabeli.

Tabel: Isik

Isik (isik_id (PK), eesnimi, perenimi)

Muudetud tabelid: Klient ja Omanik

Klient (kliendi_nr (PK), isik_id (FK -> Isik) (AK))
Omanik (omaniku_nr (PK), isik_id (FK -> Isik) (AK))

Tulemus: Isikuandmed ühes kohas. Rollid viitavad isikule. Liiasus kaotatud.

Järgmised sammud

Vaatame lõplikke tabeleid. Kollane tähistab Mari Maasika (I02) andmeid/rolle.

Lõplikud tabelid (pärast ortogonaalsuse rakendamist)

Pärast ortogonaalsuse printsiibi rakendamist ja keskse Isik tabeli loomist on meie andmebaasi struktuur ja andmed järgmised.

Roheline taust näitab algsest 1NK esimesest reast pärit andmeid. Kollane taust näitab isiku I02 (Mari Maasikas) andmeid/rolle tabelites Isik, Klient ja Omanik. Tabelis Vara on nüüd veerg `rent`.

Lõplik tabelite struktuur

Tabel: Isik

Isik (
    isik_id (PK),
    eesnimi,
    perenimi
)
Primaarvõti (PK): isik_id

Tabel: Klient

Klient (
    kliendi_nr (PK),
    isik_id (FK -> Isik) (AK)
)
Primaarvõti (PK): kliendi_nr
Alternatiivvõti (AK): isik_id
Välisvõti (FK): isik_id -> Isik(isik_id)

Tabel: Omanik

Omanik (
    omaniku_nr (PK),
    isik_id (FK -> Isik) (AK)
)
Primaarvõti (PK): omaniku_nr
Alternatiivvõti (AK): isik_id
Välisvõti (FK): isik_id -> Isik(isik_id)

Tabel: Vara

Vara (
    vara_nr (PK),
    vara_aadress,
    rent, -- Vara rendihind
    omaniku_nr (FK -> Omanik)
)
Primaarvõti (PK): vara_nr
Välisvõti (FK): omaniku_nr -> Omanik(omaniku_nr)

Tabel: Rentimine

Rentimine (
    vara_nr (PK osa) (FK -> Vara),
    kliendi_nr (FK -> Klient),
    rendi_algus (PK osa),
    rendi_lopp
)
Primaarvõti (PK): (vara_nr, rendi_algus)
Välisvõti (FK): kliendi_nr -> Klient(kliendi_nr)
Välisvõti (FK): vara_nr -> Vara(vara_nr)

Lõplikud näidisandmed

Isik tabel

Isikute andmed keskselt. Isik I02 (Mari Maasikas) on kollasega. I01 ja I03 (algse rea isikud) rohelisega.
isik_id(PK)eesnimiperenimi
I01JaanTamm
I02MariMaasikas
I03PeeterKask
I04AnnaMänd
I05EvaKuusk

Klient tabel

Klientide rollid. K001 (algne rida) rohelisega. K002 (seotud I02-ga) kollasega.
kliendi_nr(PK) isik_id(FK)(AK)
K001I01
K002I02
K003I05

Omanik tabel

Omanike rollid. O001 (algne rida) rohelisega. O003 (seotud I02-ga) kollasega.
omaniku_nr(PK) isik_id(FK)(AK)
O001I03
O003I02
O002I04

Vara tabel

Varade andmed (koos rendihinnaga). V001 (algne rida) rohelisega. Vara V003 kuulub omanikule O003 (Mari Maasikas).
vara_nr(PK) vara_aadress rent omaniku_nr(FK)
V001Pikk tn 1, Tartu520€O001
V002Lai tn 5, Tallinn750€O002
V003Mere pst 8, Pärnu600€O003
V004Metsa tee 3, Viljandi480€O002

Rentimine tabel

Rendilepingute andmed (ilma rendisummata). Esimene leping (algne rida) rohelisega.
vara_nr(PK)(FK) kliendi_nr(FK) rendi_algus(PK) rendi_lopp
V001K0012023-01-152024-01-14
V002K0022023-03-012024-02-29
V003K0012023-05-10
V001K0032024-02-012025-01-31
V004K0022024-04-01

Kokkuvõte

Selle lõpliku struktuuriga on isikuandmed hoitud ühes kohas (Isik tabel). Vara omadused, sh `rent`, on tabelis Vara. Konkreetsed rendiperioodid on tabelis Rentimine.