Wiele różnych ról w świecie technologii styka się z normalizacją danych jako rutynową częścią wielu projektów. Programiści, administratorzy baz danych, osoby zajmujące się modelowaniem domen, interesariusze biznesowi i wiele innych osób przechodzi przez proces normalizacji tak samo, jak oddycha. A jednak, czy coś, co wydaje się tak integralne, może stać się przestarzałe?
W miarę jak krajobraz baz danych staje się coraz bardziej zróżnicowany, a sprzęt staje się coraz potężniejszy, możemy się zastanawiać, czy praktyka normalizacji danych jest już potrzebna. Czy powinniśmy martwić się optymalizacją przechowywania danych i zapytań, aby zwracać minimalną ilość danych? A jeśli powinniśmy, to czy niektóre struktury danych sprawiają, że rozwiązywanie tych problemów jest bardziej istotne niż inne?
W tym artykule dokonamy przeglądu procesu normalizacji danych i ocenimy, kiedy jest to potrzebne lub czy jest to nadal niezbędna część cyfrowego przechowywania i pobierania danych.
Czym jest normalizacja danych?
Normalizacja danych to optymalizacja struktur danych w relacyjnej bazie danych w celu zapewnienia integralności danych i wydajności zapytań. Zmniejsza ona nadmiarowość i poprawia dokładność, poddając dane serii kroków w celu normalizacji struktury (normalnych formularzy). Normalizacja danych pomaga uniknąć anomalii związanych z wstawianiem, aktualizowaniem i usuwaniem danych. Anomalie te występują podczas tworzenia nowych danych, aktualizacji istniejących danych lub usuwania danych i powodują wyzwania w utrzymaniu synchronizacji wartości danych (integralności). Porozmawiamy o tym więcej, gdy przejdziemy przez proces normalizacji.
Kroki te obejmują weryfikację kluczy (łączy do powiązanych danych), oddzielenie niepowiązanych jednostek do innych tabel oraz sprawdzenie wiersza i kolumn jako ujednoliconego obiektu danych. Podczas gdy pełna lista kroków postaci normalnej jest dość rygorystyczna, skupimy się na tych najczęściej stosowanych w praktyce biznesowej: pierwszej, drugiej i trzeciej formie normalnej. Inne formy normalne są najczęściej używane w nauce i statystyce. Kroki formy normalnej muszą być wykonywane w kolejności i nie możemy przejść do następnej formy normalnej, dopóki poprzednia nie zostanie ukończona.
Jak przeprowadzić normalizację danych?
Ponieważ mamy trzy normalne formy, przez które przechodzą nasze dane, będziemy mieli trzy etapy tego procesu. Są one następujące:
- Pierwsza forma normalna (1NF)
- 2. postać normalna (2NF)
- 3. postać normalna (3NF)
Profesor baz danych ze studiów nauczył moją klasę zapamiętywania trzech form normalnych jako “klucz, cały klucz i tylko klucz” (jak przysięga sądowa na prawdę). Musiałem odświeżyć niektóre szczegóły dotyczące form normalnych na potrzeby tego artykułu, ale ta podstawowa fraza zawsze utkwiła mi w pamięci. Mam nadzieję, że pomoże to również Państwu je zapamiętać.
Niedawno natknąłem się na zestaw danych z kawiarni, który wydaje się dobrze pasować do wykorzystania jako przykład normalizacji zestawu danych. Przy odrobinie dostosowania do naszych przykładów tutaj, możemy przejść przez ten proces.
Zdenormalizowane dane
transaction_date |
transaction_time |
instore_yn |
klient |
loyalty_num |
line_item_id |
produkt |
ilość |
unit_price |
promo_item_yn |
2019-04-01 |
12:24:53 |
Y |
Camille Tyler |
102-192-8157 |
1 |
Columbian Medium Roast Sm |
1 |
2.00 |
N |
2019-04-01 |
12:30:00 |
N |
Griffith Lindsay |
769-005-9211 |
1,2 |
Jamaican Coffee River Sm, Oatmeal Scone |
1,1 |
2.45,3.00 |
N,N |
2019-04-01 |
16:44:46 |
Y |
Stuart Nunez |
796-362-1661 |
1 |
Morning Sunrise Chai Rg |
2 |
2.50 |
N |
2019-04-01 |
14:24:55 |
Y |
Allistair Ramirez |
253-876-9471 |
1,2 |
Cappuccino Lg, Jumbo Savory Scone |
2,1 |
4.25,3.75 |
N,N |
Dane zawierają wpływy ze sprzedaży dla firmy i zostały pierwotnie opublikowane na stronie Przykładowe dane Kaggle Coffee Shop chociaż stworzyłem również repozytorium repozytorium GitHub na potrzeby dzisiejszego wpisu. Dane wyświetlone powyżej pokazują sprzedaż dokonaną na rzecz klienta dla zamówionych produktów.
Dlaczego te dane stanowią problem? Wcześniej wspomnieliśmy o normalizacji w celu rozwiązania anomalii związanych z wstawianiem, aktualizacją i usuwaniem danych. Jeśli spróbujemy wstawić nowy wiersz do tych danych, możemy utworzyć duplikat lub, co gorsza, będziemy musieli zebrać wszystkie informacje o klientach, produktach i dacie/czasie paragonu, aby go utworzyć. Jeśli chcielibyśmy zaktualizować lub usunąć produkty zakupione na paragonie, musielibyśmy posortować listę w każdej kolumnie produktu, aby wyszukać wartość. Zobaczmy więc, jak poprawić redundancję i integralność poprzez normalizację tych danych.
1st Normal Form: Klucz
W pierwszym kroku naszego “klucza, całego klucza i tylko klucza” tabela powinna mieć klucz podstawowy (pojedynczy lub zestaw kolumn), który zapewnia unikalność wiersza. Każda kolumna w wierszu powinna również zawierać tylko jedną wartość; tj. żadnych zagnieżdżonych tabel.
Nasz przykładowy zestaw danych wymaga trochę pracy, aby uzyskać 1NF. Chociaż możemy uzyskać unikalne wiersze za pomocą kombinacji daty/czasu lub daty/czasu/klienta, często znacznie łatwiej jest odwoływać się do wierszy za pomocą wygenerowanej unikalnej wartości. Zróbmy to, dodając atrybut transaction_id
do naszej tabeli paragonów.
Istnieje również kilka wierszy, które mają więcej niż jedną zamówioną pozycję (transaction_id 156 i 199), więc kilka kolumn ma pozycje z więcej niż jedną wartością. Możemy to skorygować, rozdzielając wiersze z wieloma wartościami na osobne wiersze.
1NF Data
transaction_id |
transaction_date |
transaction_time |
instore_yn |
klient |
loyalty_num |
line_item_id |
produkt |
ilość |
unit_price |
promo_item_yn |
150 |
2019-04-01 |
12:24:53 |
Y |
Camille Tyler |
102-192-8157 |
1 |
Columbian Medium Roast Sm |
1 |
2.00 |
N |
156 |
2019-04-01 |
12:30:00 |
N |
Griffith Lindsay |
769-005-9211 |
1 |
Jamaican Coffee River Sm |
1 |
2.45 |
N |
156 |
2019-04-01 |
12:30:00 |
N |
Griffith Lindsay |
769-005-9211 |
2 |
Podpłomyk owsiany |
1 |
3.00 |
N |
165 |
2019-04-01 |
16:44:46 |
Y |
Stuart Nunez |
796-362-1661 |
1 |
Morning Sunrise Chai Rg |
2 |
2.50 |
N |
199 |
2019-04-01 |
14:24:55 |
Y |
Allistair Ramirez |
253-876-9471 |
1 |
Cappuccino Lg |
2 |
4.25 |
N |
199 |
2019-04-01 |
14:24:55 |
Y |
Allistair Ramirez |
253-876-9471 |
2 |
Jumbo Savory Scone |
1 |
3.75 |
N |
W przypadku tych danych klucz złożony (wielokolumnowy) jednoznacznie identyfikuje wiersz za pomocą kombinacji transaction_id
i line_item_id
, ponieważ pojedynczy paragon nie może zawierać wielu pozycji nr 1. Proszę spojrzeć na dane, jeśli sprowadzimy tabelę tylko do tych wartości klucza głównego.
transaction_id |
line_item_id |
150 |
1 |
156 |
1 |
156 |
5 |
165 |
1 |
199 |
1 |
199 |
5 |
Każda kombinacja tych dwóch wartości jest unikalna. Zastosowaliśmy pierwszą postać normalną do naszych danych, ale nadal mamy pewne potencjalne anomalie danych. Jeśli chcielibyśmy dodać nowy paragon, być może musielibyśmy utworzyć wiele wierszy (w zależności od tego, ile pozycji zawierał) i zduplikować identyfikator transakcji, datę, godzinę i inne informacje w każdym wierszu. Aktualizacje i usunięcia powodują podobne problemy, ponieważ musielibyśmy upewnić się, że otrzymamy wszystkie wiersze, których to dotyczy, aby dane były spójne. W tym miejscu do gry wkracza druga postać normalna.
Druga postać normalna: Cały klucz
Druga postać normalna zapewnia, że każda kolumna niebędąca kluczem jest w pełni zależna od całego klucza. Jest to bardziej istotne w przypadku tabel z więcej niż jedną kolumną jako kluczem podstawowym (takich jak nasza tabela paragonów). Oto nasze dane ponownie w pierwszej postaci normalnej:
transaction_id |
transaction_date |
transaction_time |
instore_yn |
klient |
loyalty_num |
line_item_id |
produkt |
ilość |
unit_price |
promo_item_yn |
150 |
2019-04-01 |
12:24:53 |
Y |
Camille Tyler |
102-192-8157 |
1 |
Columbian Medium Roast Sm |
1 |
2.00 |
N |
156 |
2019-04-01 |
12:30:00 |
N |
Griffith Lindsay |
769-005-9211 |
1 |
Jamaican Coffee River Sm |
1 |
2.45 |
N |
156 |
2019-04-01 |
12:30:00 |
N |
Griffith Lindsay |
769-005-9211 |
2 |
Podpłomyk owsiany |
1 |
3.00 |
N |
165 |
2019-04-01 |
16:44:46 |
Y |
Stuart Nunez |
796-362-1661 |
1 |
Morning Sunrise Chai Rg |
2 |
2.50 |
N |
199 |
2019-04-01 |
14:24:55 |
Y |
Allistair Ramirez |
253-876-9471 |
1 |
Cappuccino Lg |
2 |
4.25 |
N |
199 |
2019-04-01 |
14:24:55 |
Y |
Allistair Ramirez |
253-876-9471 |
2 |
Jumbo Savory Scone |
1 |
3.75 |
N |
Będziemy musieli ocenić każde pole niebędące kluczem, aby sprawdzić, czy mamy jakieś częściowe zależności; tj. kolumna zależy tylko od części klucza, a nie od całego klucza. Ponieważ transaction_id
i line_item_id
tworzą nasz klucz podstawowy, zacznijmy od klucza transaction_date
. Data transakcji zależy od identyfikatora transakcji, ponieważ ten sam identyfikator transakcji nie może być ponownie użyty w innym dniu. Data transakcji nie zależy jednak w ogóle od identyfikatora pozycji. Pozycje liniowe mogą być ponownie wykorzystane w transakcjach, dniach, a nawet klientach.
Ok, więc odkryliśmy już, że tabela nie jest zgodna z drugą postacią normalną, ale sprawdźmy inną kolumnę. Co z kolumną klienta? Klient nie jest zależny zarówno od identyfikatora transakcji, jak i identyfikatora pozycji. Gdyby ktoś podał nam identyfikator transakcji, wiedzielibyśmy, który klient dokonał zakupu, ale gdybyśmy otrzymali identyfikator pozycji, nie wiedzielibyśmy, do którego klienta należał dany paragon. W końcu wielu klientów mogło zamówić jedną, dwie lub sześć pozycji na swoich paragonach. Klient jest powiązany z identyfikatorem transakcji (proszę założyć, że wielu klientów nie może dzielić paragonów), ale klient nie jest zależny od pozycji. Musimy naprawić te częściowe zależności.
Najbardziej bezpośrednim rozwiązaniem jest utworzenie oddzielnej tabeli dla pozycji zamówienia, pozostawiając kolumny, które są zależne tylko od transaction_id
w tabeli paragonów. Zaktualizowane dane w drugiej postaci normalnej wyglądają jak poniżej.
Odbiór
transaction_id |
transaction_date |
transaction_time |
instore_yn |
klient |
loyalty_num |
150 |
2019-04-01 |
12:24:53 |
Y |
Camille Tyler |
102-192-8157 |
156 |
2019-04-01 |
12:30:00 |
N |
Griffith Lindsay |
769-005-9211 |
165 |
2019-04-01 |
16:44:46 |
Y |
Stuart Nunez |
796-362-1661 |
199 |
2019-04-01 |
14:24:55 |
Y |
Allistair Ramirez |
253-876-9471 |
Pozycja paragonu
transaction_id |
line_item_id |
product_id |
produkt |
ilość |
unit_price |
promo_item_yn |
150 |
1 |
28 |
Columbian Medium Roast Sm |
1 |
2.00 |
N |
156 |
1 |
34 |
Jamaican Coffee River Sm |
1 |
2.45 |
N |
156 |
2 |
77 |
Podpłomyk owsiany |
1 |
3.00 |
N |
165 |
1 |
54 |
Morning Sunrise Chai Rg |
2 |
2.50 |
N |
199 |
1 |
41 |
Cappuccino Lg |
2 |
4.25 |
N |
199 |
2 |
79 |
Jumbo Savory Scone |
1 |
3.75 |
N |
Sprawdźmy teraz, czy nasza zmiana naprawiła problem i jest zgodna z drugą postacią normalną. Dla naszej tabeli Receipt, transaction_id
staje się jedynym kluczem podstawowym. Data transakcji jest unikalna na podstawie transaction_id
, podobnie jak transaction_time
; tj. może istnieć tylko jedna data i godzina dla identyfikatora transakcji.
Zamówienia nie mogą być składane zarówno w sklepie, jak i poza nim, więc wartość tego, czy zakup został dokonany w sklepie, czy nie, zależy od transaction_id
. Ponieważ klienci nie mogą dzielić paragonu, transakcja informowałaby nas również o unikalnym kliencie. Wreszcie, gdyby ktoś podał nam identyfikator transakcji, moglibyśmy zidentyfikować pojedynczy numer lojalnościowy klienta, który jest do niej dołączony.
Następnie znajduje się tabela pozycji paragonu. Pozycje liniowe są zależne od transakcji (paragonu), z którą są powiązane, więc zachowaliśmy identyfikator transakcji w naszej tabeli pozycji liniowych. Połączenie transaction_id
i line_item_id
staje się naszym kluczem złożonym w tabeli pozycji. Product_id
i product
są określane na podstawie transakcji i pozycji razem. Pojedynczy identyfikator transakcji nie powie nam, który produkt (jeśli paragon zawiera wiele zakupionych produktów), a pojedynczy identyfikator pozycji nie powie nam, do którego zakupu się odnosi (różne paragony mogą zamawiać te same produkty). Oznacza to, że product_id
i product
są zależne od całego klucza.
Możemy również powiązać wartość z transaction_id
i line_item_id
. Ilości mogą być takie same w przypadku paragonów lub identyfikatorów pozycji, ale kombinacja obu kluczy daje nam pojedynczą wartość ilości. Nie możemy również jednoznacznie zidentyfikować naszych unit_price
lub promo_item_yn
wartości kolumn bez pól ID transakcji i ID pozycji razem.
Chociaż spełniliśmy drugą postać normalną, nadal istnieją pewne anomalie danych. Gdybyśmy próbowali utworzyć nowy produkt do zakupu lub nowego klienta, nie moglibyśmy ich utworzyć w naszych bieżących tabelach, ponieważ możemy nie mieć jeszcze powiązanych z nimi paragonów. Jeśli chcielibyśmy zaktualizować produkt lub klienta (z powodu literówki lub zmiany nazwy), musielibyśmy zaktualizować wszystkie wiersze pozycji o te wartości. Jeśli chcielibyśmy usunąć produkt lub klienta, nie moglibyśmy tego zrobić, chyba że usunęlibyśmy paragony lub pozycje, które się do nich odwoływały. Aby rozwiązać te problemy, możemy przejść do trzeciej postaci normalnej.
Trzecia postać normalna: I nic poza kluczem
Trzecia postać normalna zapewnia, że pola niebędące kluczami nie są zależne od niczego poza kluczem. Innymi słowy, nie są one zależne od innych pól niekluczowych, powodując zależność przechodnią. Przeanalizujmy jeszcze raz nasze dane 2NF:
Odbiór
transaction_id |
transaction_date |
transaction_time |
instore_yn |
klient |
loyalty_num |
150 |
2019-04-01 |
12:24:53 |
Y |
Camille Tyler |
102-192-8157 |
156 |
2019-04-01 |
12:30:00 |
N |
Griffith Lindsay |
769-005-9211 |
165 |
2019-04-01 |
16:44:46 |
Y |
Stuart Nunez |
796-362-1661 |
199 |
2019-04-01 |
14:24:55 |
Y |
Allistair Ramirez |
253-876-9471 |
Pozycja paragonu
transaction_id |
line_item_id |
product_id |
produkt |
ilość |
unit_price |
promo_item_yn |
150 |
1 |
28 |
Columbian Medium Roast Sm |
1 |
2.00 |
N |
156 |
1 |
34 |
Jamaican Coffee River Sm |
1 |
2.45 |
N |
156 |
2 |
77 |
Podpłomyk owsiany |
1 |
3.00 |
N |
165 |
1 |
54 |
Morning Sunrise Chai Rg |
2 |
2.50 |
N |
199 |
1 |
41 |
Cappuccino Lg |
2 |
4.25 |
N |
199 |
2 |
79 |
Jumbo Savory Scone |
1 |
3.75 |
N |
W naszej tabeli Receipt musimy sprawdzić pola niekluczowe (wszystko poza transaction_id
), aby sprawdzić, czy wartości zależą od innych pól niekluczowych. Wartości daty transakcji, godziny i sklepu nie zmieniają się w zależności od siebie nawzajem ani od powiązanego klienta lub numeru lojalnościowego, więc są one prawidłowo zależne tylko od klucza.
Ale co z informacjami o kliencie? Wartość numeru lojalnościowego może ulec zmianie, jeśli zmieni się klient. Na przykład, gdybyśmy musieli usunąć lub zaktualizować klienta, który dokonał zakupu, numer lojalnościowy również musiałby zostać usunięty lub zaktualizowany wraz z nim. Tak więc numer lojalnościowy zależy od klienta, który jest polem niekluczowym. Oznacza to, że nasza tabela Receipt nie ma trzeciej postaci normalnej.
A co z naszą tabelą pozycji paragonu? Wartości ilości, ceny jednostkowej i pozycji promocyjnej nie zmieniają się w zależności od wartości innych pól ani informacji o produkcie, ponieważ te trzy pola określają wartość produktu w momencie zakupu. Produkt jest jednak zależny od product_id
ponieważ wartość zmienia się w zależności od tego, do którego identyfikatora produktu się odwołano. Tak więc ta tabela również wymaga pewnych aktualizacji, aby była zgodna z trzecią formą normalną.
Ponownie, najlepszą metodą rozwiązania tych problemów jest przeniesienie powiązanych kolumn do oddzielnych tabel i pozostawienie referencyjnego identyfikatora (klucza obcego), aby połączyć oryginalne tabele z nowymi. Eliminujemy anomalie danych podczas wstawiania, aktualizacji i usuwania, a także zmniejszamy redundancję danych i poprawiamy wydajność przechowywania i zapytań.
Odbiór
transaction_id |
transaction_date |
transaction_time |
instore_yn |
customer_id |
150 |
2019-04-01 |
12:24:53 |
Y |
604 |
156 |
2019-04-01 |
12:30:00 |
N |
32 |
165 |
2019-04-01 |
16:44:46 |
Y |
127 |
199 |
2019-04-01 |
14:24:55 |
Y |
112 |
Pozycja paragonu
transaction_id |
line_item_id |
product_id |
ilość |
unit_price |
promo_item_yn |
150 |
1 |
28 |
1 |
2.00 |
N |
156 |
1 |
34 |
1 |
2.45 |
N |
156 |
2 |
77 |
1 |
3.00 |
N |
165 |
1 |
54 |
2 |
2.50 |
N |
199 |
1 |
41 |
2 |
4.25 |
N |
199 |
2 |
79 |
1 |
3.75 |
N |
Produkt
product_id |
produkt |
28 |
Columbian Medium Roast Sm |
34 |
Jamaican Coffee River Sm |
77 |
Podpłomyk owsiany |
54 |
Morning Sunrise Chai Rg |
41 |
Cappuccino Lg |
79 |
Jumbo Savory Scone |
Klient
customer_id |
klient |
loyalty_num |
604 |
Camille Tyler |
102-192-8157 |
32 |
Griffith Lindsay |
769-005-9211 |
127 |
Stuart Nunez |
796-362-1661 |
112 |
Allistair Ramirez |
253-876-9471 |
Normalizacja danych poza relacyjnymi bazami danych
Czy ten proces normalizacji danych ma sens poza innymi bazami danych? Czy jest on potrzebny w przypadku baz danych dokumentów, kolumnowych, klucz-wartość i/lub grafowych?
Z mojej perspektywy cele normalizacji danych – zmniejszenie redundancji, poprawa integralności danych i zwiększenie wydajności zapytań – są nadal bardzo cenne bez względu na bazę danych, z którą Państwo pracują. Jednak proces i zasady normalnych formularzy w relacyjnej normalizacji danych prawdopodobnie nie pasują jeden do jednego z innymi modelami danych. Zobaczmy kilka przykładów wykorzystujących nasz klucz pamięci “klucz, cały klucz i tylko klucz” dla trzech głównych kategorii baz danych: relacyjnych, dokumentowych i grafowych.
The relacyjne bazy danychCelem było zoptymalizowanie łączenia danych w różne znaczące zestawy poprzez łączenie tabel w zapytaniach SQL. Przeszliśmy już przez proces normalizacji z tej perspektywy, więc mamy nadzieję, że korzyści płynące z redundancji, wydajności i integralności danych są jasne z naszej wcześniejszej dyskusji.
W bazy danych dokumentówModel ten jest zoptymalizowany pod kątem grupowania powiązanych informacji w jednym dokumencie, tak aby wyszukiwanie pojedynczego klienta pobierało wszystkie paragony i wszelkie inne szczegóły. To stwierdzenie jest już sprzeczne z naszym celem redundancji danych, ponieważ możemy potencjalnie powielać lub dopuszczać niespójności w informacjach o produkcie w celu przechowywania tych danych z klientem. Klucze podstawowe dla dokumentów, które będą wyszukiwane, nadal mają sens, aby uniknąć wielokrotnego wyszukiwania, ale dodatkowe kroki normalizacji mogą, ale nie muszą, być sprzeczne z celami samego modelu bazy danych.
Grafowe bazy danych równoważą integralność danych zapewnianą przez relacyjne i wstępnie upieczone dane relacji dostarczane przez dokumenty w celu stworzenia unikalnego modelu zoptymalizowanego pod kątem łączenia relacji danych bez tworzenia większej nadmiarowości danych. Unikalne jednostki za pośrednictwem klucza głównego są nadal ważne dla poprawy wydajności zapytań i przechowywania, ale złączenia są przechowywane jako oddzielne jednostki, naturalnie rozdzielając powiązane dane na oddzielne jednostki bez analizowania każdego pola pod kątem częściowych lub niekluczowych zależności. Normalizacja istnieje tutaj, ale wydaje się bardziej organiczna i mniej oparta na procesach.
Podsumowanie
Podsumowując, omówiliśmy proces normalizacji danych w odniesieniu do tradycyjnego świata relacyjnych baz danych. Omówiliśmy każdy krok trzech form normalnych i zastosowaliśmy każdy z nich do zbioru danych paragonów z kawiarni.
Na koniec przyjrzeliśmy się, jak wygląda normalizacja danych w innych typach baz danych (dokumentowych i grafowych) oraz jakie formy mają sens w oparciu o strukturę modelu bazy danych.