Wolisz normalizację bazy danych niż przejrzystość schematu?

10

Na starej bazie kodu pojawiło się nowe wymaganie, które zasadniczo umożliwia bezpośrednią (wewnętrzną) komunikację między dwiema poprzednio nie bezpośrednio powiązanymi klasami użytkowników (przechowywanymi w różnych tabelach z zupełnie innym schematem) i, niestety, kod jest ledwie świadomy OO, wiele mniej zaprojektowane, więc nie ma klasy nadrzędnej). Ponieważ mamy zamiar zawiesić torbę na tej starej konfiguracji, która nigdy nie rozważała tej funkcjonalności, nie ma gwarancji, że nie wystąpią kolizje PK - biorąc pod uwagę używany zestaw danych, jest praktycznie gwarantowane, że SĄ.

Tak więc rozwiązanie wydaje się oczywiste: zabij go ogniem i przepisz cały bałagan Tabela map. Dostałem dwa kierunki możliwych sposobów implementacji mapy, ale nie jestem DBA, więc nie jestem pewien, czy są jakieś plusy i minusy, których mi brakowało.

W celu wyjaśnienia abstrakcji rozważ trzy grupy różnych danych użytkowników: profesorowie, administracja, studenci (nie, to nie zadanie domowe. Obiecuj!)

Mapowanie 1

(professor_id, admin_id i student_id są kluczami obcymi do odpowiednich tabel)

| mailing_id (KEY) | professor_id | admin_id | student_id | 
-------------------------------------------------------
| 1001             |     NULL     |    87    |  NULL      |
| 1002             |     123      |   NULL   |  NULL      |
| 1003             |     NULL     |   NULL   |  123       |

Podejście +/- do tego podejścia wydaje się dość duże wady:

  • Dwa „zmarnowane” pola w rzędzie
  • Narusza 2NF
  • Podatne na wstawienie / aktualizację anomalii (wiersz z ustawionym tylko wartością 0-1 NULL, np.)

Zalety nie są jednak pozbawione własnych zalet:

  • Mapowanie można wykonać za pomocą pojedynczego wyszukiwania
  • Łatwo określ dane „źródłowe” dla danego użytkownika na podstawie id_poczty

Prawdę mówiąc, w moich jelitach wcale nie podoba mi się ten pomysł.

Mapowanie 2

(załóżmy, że MSG_ * to zdefiniowane stałe, typy wyliczeniowe lub inny odpowiedni identyfikator)

| mailing_id (KEY)  | user_type (UNIQUE1) | internal_id (UNIQUE2)| 
------------------------------------------------------------------
| 1001              | MSG_ADMIN          | 87                    |
| 1002              | MSG_PROF           | 123                   |
| 1003              | MSG_STUDENT        | 123                   |

Dzięki tej konfiguracji i unikalnemu indeksowi złożonemu {typ_użytkownika, identyfikator_wewnętrzny} rzeczy stają się znacznie czystsze, utrzymywana jest 3NF, a kod aplikacji nie musi sprawdzać anomalii I / U.

Wadą jest jednak niewielka utrata przejrzystości przy określaniu tabel źródłowych użytkownika, które muszą być obsługiwane poza DB, co w zasadzie sprowadza się do mapowania na poziomie aplikacji wartości user_type na tabele. W tej chwili (raczej mocno) pochylam się w kierunku tego drugiego mapowania, ponieważ wada jest raczej niewielka.

ALE jestem boleśnie świadomy własnych ograniczeń i jestem pewien, że prawdopodobnie przegapiłem zalety lub przeszkody w obu kierunkach, więc zwracam się do mądrzejszych umysłów niż moich.

GeminiDomino
źródło
2
Ciekawe mogą być pomysły Martina Fowlera na temat ról .
Marjan Venema,
To było naprawdę interesujące. Niestety nie ma zbyt dużego wglądu w mój konkretny problem
GeminiDomino
Dostaniesz profesorów, którzy zostaną administratorami i studentów, którzy dostaną pracę w administracji, a nawet wrócą 10 lat później na wydział. Prawdopodobnie już je masz. Zamierzasz oddzielić je od siebie, czy starać się zjednoczyć?
Elin
Role to tylko przykłady, ale rozumiem twój punkt widzenia. W praktyce nawet gdyby użytkownicy zmieniali role, i tak pozostaliby jako osobne rekordy.
GeminiDomino,
Byłoby wspaniale, gdybyś ponownie sformułował pierwszy akapit. To jest trochę niejasne. To znaczy, to oczywiste, że jest problem, ale nie jest wystarczająco jasne, co to jest.
Tulains Córdova,

Odpowiedzi:

1

Twój drugi pomysł jest właściwy. Takie podejście umożliwia wykonanie wszystkich mapowań, które należy wykonać, aby zintegrować trzy kolidujące ze sobą przestrzenie klawiszy.

Co ważne, pozwala bazie danych narzucić większość spójności, którą trzeba mieć przy użyciu ograniczeń deklaratywnych .

Masz już więcej kodu, niż chcesz, więc nie dodawaj więcej kodu, niż jest to absolutnie konieczne, aby zachować spójność zintegrowanej listy kluczy. Pozwól, aby silnik bazy danych zrobił to, co został zbudowany.

„Problematyczne dziecko”, które powoduje dyskomfort w Mapowaniu 2, to USER_TYPEkolumna. Ta kolumna jest ważna, ponieważ potrzebujesz jej, aby INTERNAL_IDwyświetlała się tylko raz dla każdego typu użytkownika. Jedyny raz, kiedy potrzebujesz kodu, który jest nawet świadomy, USER_TYPEto kod, który wstawia i usuwa z tabeli odwzorowań. Można to dość dobrze zlokalizować. Zakładam, że utworzysz pojedynczy punkt w kodzie, w którym utrzymywana będzie zawartość tabeli mapowania. Dodatkowa kolumna w tym jednym miejscu, w którym zapisywane są dane, nie jest wielkim problemem. To, czego naprawdę chcesz uniknąć, to dodawanie dodatkowej kolumny wszędzie tam, gdzie czytane są dane .

Kod w twoich podaplikacjach, które muszą korzystać z mapowania, może być błogo ignorantem, po USER_TYPEprostu dając każdej pod-aplikacji widok, który filtruje mapowania do jednego typu użytkownika specyficznego dla aplikacji.

Joel Brown
źródło
3

Z doświadczenia zalecam, aby wybierać spójność zamiast elegancji lub „najlepszych praktyk”. To znaczy, aby dopasować się do istniejącego projektu i iść z TRZYMI tabelami mailingowymi (po jednej dla każdej roli) o prostej mailing_id, user_idstrukturze pola.

Jest nieelegancki, ale ma kilka zalet ...

  1. Dopasowanie istniejącej struktury będzie łatwiejsze dla każdego, kto będzie pracował nad tym schematem, zanim zostanie on wypuszczony na pastwisko.
  2. Nie masz zmarnowanych pól i nie pytasz db o dopasowanie rzeczy, które nie będą istnieć.
  3. Ponieważ każda tabela będzie tylko do siebie nawzajem i stosunkowo łatwo będzie stworzyć widok, który wiąże wszystkie dane do wykorzystania przez twoje procedury.

Jestem pewien, że wielu innych nie zgodzi się z tym podejściem, ale głównym celem normalizacji i najlepszych praktyk jest uczynienie kodu bardziej spójnym, aby łatwiej było śledzić i debugować ... i oczywiście doprowadzenie całej bazy kodu do zera prawdopodobnie nie jest możliwe.

James Snell
źródło
Problem z tym podejściem polega na tym, że baza danych nie może wtedy wymusić unikalności w identyfikatorach wysyłkowych, co jest przede wszystkim głównym celem odwzorowania: w przeciwnym razie parowanie poszczególnych pól ID z każdej tabeli ze wskaźnikiem „typ użytkownika” może być zrobione bez żadnych zmian.
GeminiDomino,
Rozumiem, o co ci chodzi, ale pracując nad tego rodzaju systemem dałem opcję, której mogłeś nie rozważyć. Tak dalece, jak to widzę, identyfikator mailingowy potrzebuje pewnej zawartości, aby się do niego odwoływać (co zostało wysłane lub jak znaleźć dokument), więc identyfikator mailingowy powinien być kluczem obcym, co oznacza, że ​​problemy z unikalnością zostałyby rozwiązane gdzie indziej. W trakcie czytania tabele danych studentów-administratorów i profów mogą mieć różne struktury, więc nie widzę wartości pola typu użytkownika. Pierwotni programiści musieli rozwiązać ten problem, co oni zrobili?
James Snell
Pole „typ użytkownika” określa, która tabela ma zostać powiązana z tym konkretnym rekordem. Tak czy inaczej musiałoby być obsługiwane na poziomie aplikacji, a ponieważ SĄ one w różnych tabelach, nie ma dobrego sposobu, aby uczynić to ograniczeniem klucza obcego. Wydaje się, że pierwotni programiści wcale nie brali pod uwagę tego problemu, dlatego zamienia się w taki bałagan. :)
GeminiDomino