Zastanawiam się, jak reprezentować złożoną strukturę w bazie danych SQL Server.
Rozważ aplikację, która musi przechowywać szczegóły rodziny obiektów, które mają pewne atrybuty, ale wiele innych nie jest wspólnych. Na przykład pakiet ubezpieczeń komercyjnych może obejmować ochronę od odpowiedzialności cywilnej, komunikacyjnej, majątkowej i odszkodowawczej w ramach tego samego zapisu polisy.
Zaimplementowanie tego w C # itp. Jest trywialne, ponieważ możesz utworzyć Politykę z kolekcją Sekcji, w których sekcja jest dziedziczona zgodnie z wymaganiami dla różnych rodzajów ochrony. Jednak relacyjne bazy danych nie pozwalają na to łatwo.
Widzę, że są dwie główne opcje:
Utwórz tabelę zasad, a następnie tabelę sekcji ze wszystkimi wymaganymi polami dla wszystkich możliwych odmian, z których większość byłaby zerowa.
Utwórz tabelę zasad i liczne tabele sekcji, po jednej dla każdego rodzaju ubezpieczenia.
Obie te alternatywy wydają się niezadowalające, zwłaszcza, że konieczne jest pisanie zapytań we wszystkich sekcjach, co wiązałoby się z licznymi sprzężeniami lub licznymi kontrolami zerowymi.
Jaka jest najlepsza praktyka dla tego scenariusza?
Odpowiedzi:
@Bill Karwin opisuje trzy modele dziedziczenia w swojej książce SQL Antipatterns , proponując rozwiązania antipatternu SQL Entity-Attribute-Value . Oto krótki przegląd:
Dziedziczenie pojedynczej tabeli (alias Table Per Hierarchy Inheritance):
Korzystanie z jednego stołu jak w pierwszej opcji jest prawdopodobnie najprostszym projektem. Jak wspomniałeś, wiele atrybutów specyficznych dla podtypów będzie musiało otrzymać
NULL
wartość w wierszach, w których te atrybuty nie mają zastosowania. W tym modelu miałbyś jedną tabelę polityk, która wyglądałaby mniej więcej tak:Prostota projektu to plus, ale główne problemy z tym podejściem są następujące:
Jeśli chodzi o dodawanie nowych podtypów, musiałbyś zmienić tabelę, aby uwzględnić atrybuty opisujące te nowe obiekty. Może to szybko stać się problematyczne, jeśli masz wiele podtypów lub planujesz regularnie dodawać podtypy.
Baza danych nie będzie w stanie wymusić, które atrybuty mają zastosowanie, a które nie, ponieważ nie ma metadanych określających, które atrybuty należą do poszczególnych podtypów.
Nie można również wymuszać
NOT NULL
atrybutów podtypu, które powinny być obowiązkowe. Będziesz musiał sobie z tym poradzić w swojej aplikacji, co ogólnie nie jest idealne.Dziedziczenie stołu betonowego:
Innym podejściem do rozwiązania problemu dziedziczenia jest utworzenie nowej tabeli dla każdego podtypu, powtarzając wszystkie wspólne atrybuty w każdej tabeli. Na przykład:
Ten projekt zasadniczo rozwiąże problemy zidentyfikowane dla metody z pojedynczą tabelą:
Obowiązkowe atrybuty można teraz egzekwować
NOT NULL
.Dodanie nowego podtypu wymaga dodania nowej tabeli zamiast dodawania kolumn do istniejącej.
Nie ma również ryzyka, że ustawiony zostanie nieodpowiedni atrybut dla konkretnego podtypu, takiego jak
vehicle_reg_no
pole dla polityki właściwości.type
Atrybut ten nie jest potrzebny, jak w metodzie pojedynczej tabeli. Typ jest teraz zdefiniowany przez metadane: nazwa tabeli.Jednak ten model ma również kilka wad:
Wspólne atrybuty są mieszane z atrybutami specyficznymi dla podtypu i nie ma łatwego sposobu ich zidentyfikowania. Baza danych też nie będzie wiedziała.
Podczas definiowania tabel należy powtórzyć wspólne atrybuty dla każdej tabeli podtypu. To zdecydowanie nie jest SUCHE .
Wyszukiwanie wszystkich zasad niezależnie od podtypu staje się trudne i wymagałoby kilku
UNION
s.W ten sposób będziesz musiał wysłać zapytanie do wszystkich zasad niezależnie od typu:
Zauważ, że dodanie nowych podtypów wymagałoby modyfikacji powyższego zapytania o dodatkowe
UNION ALL
dla każdego podtypu. Może to łatwo prowadzić do błędów w aplikacji, jeśli operacja zostanie zapomniana.Dziedziczenie tabeli klas (zwanej także dziedziczeniem tabeli według typu):
Jest to rozwiązanie, o którym @David wspomina w innej odpowiedzi . Tworzysz pojedynczą tabelę dla swojej klasy podstawowej, która zawiera wszystkie typowe atrybuty. Następnie utworzyłbyś określone tabele dla każdego podtypu, którego klucz podstawowy służy również jako klucz obcy do tabeli podstawowej. Przykład:
To rozwiązanie rozwiązuje problemy zidentyfikowane w dwóch pozostałych projektach:
Atrybuty obowiązkowe można egzekwować
NOT NULL
.Dodanie nowego podtypu wymaga dodania nowej tabeli zamiast dodawania kolumn do istniejącej.
Brak ryzyka, że dla określonego podtypu zostanie ustawiony niewłaściwy atrybut.
Nie ma potrzeby tego
type
atrybutu.Teraz wspólne atrybuty nie są już mieszane z atrybutami specyficznymi dla podtypu.
W końcu możemy pozostać SUCHY. Podczas tworzenia tabel nie trzeba powtarzać wspólnych atrybutów dla każdej tabeli podtypu.
Zarządzanie automatyczną inkrementacją
id
zasad staje się łatwiejsze, ponieważ może to być obsługiwane przez tabelę podstawową, zamiast generowania ich niezależnie przez każdą tabelę podtypów.Wyszukiwanie wszystkich polityk niezależnie od podtypu staje się teraz bardzo łatwe: nie
UNION
są potrzebne - wystarczySELECT * FROM policies
.Podejście oparte na stole klasowym uważam za najbardziej odpowiednie w większości sytuacji.
Nazwy tych trzech modeli pochodzą z książki Martina Fowlera Wzory architektury aplikacji korporacyjnych .
źródło
Trzecią opcją jest utworzenie tabeli „Zasady”, a następnie tabeli „SectionsMain”, która przechowuje wszystkie pola wspólne dla różnych typów sekcji. Następnie utwórz inne tabele dla każdego typu sekcji, które zawierają tylko pola, które nie są wspólne.
Wybór najlepszego zależy głównie od tego, ile pól masz i jak chcesz napisać SQL. Wszyscy by działali. Jeśli masz tylko kilka pól, prawdopodobnie wybrałbym numer 1. Z „dużą ilością pól” skłaniałbym się ku # 2 lub # 3.
źródło
Na podstawie dostarczonych informacji modelowałbym bazę danych, aby miała:
POLITYKI
ZADŁUŻENIE
NIERUCHOMOŚCI
... i tak dalej, ponieważ spodziewałbym się, że z każdą sekcją zasad będą powiązane różne atrybuty. W przeciwnym razie mógłby istnieć pojedynczy
SECTIONS
stół, a oprócz tegopolicy_id
byłbysection_type_code
...Tak czy inaczej, pozwoli to na obsługę opcjonalnych sekcji według zasad ...
Nie rozumiem, co uważasz za niezadowalające w tym podejściu - w ten sposób przechowujesz dane, zachowując spójność referencyjną i nie powielając danych. Termin jest „znormalizowany” ...
Ponieważ SQL jest oparty na SET, jest raczej obcy pojęciom programowania proceduralnego / OO i wymaga przejścia kodu z jednej dziedziny do drugiej. Często brane są pod uwagę ORM, ale nie działają one dobrze w złożonych systemach o dużej objętości.
źródło
Ponadto w przypadku rozwiązania Daniel Vassallo, jeśli używasz SQL Server 2016+, istnieje inne rozwiązanie, z którego korzystałem w niektórych przypadkach bez znacznej utraty wydajności.
Możesz utworzyć tylko tabelę zawierającą tylko wspólne pole i dodać jedną kolumnę za pomocą JSON łańcuchem który zawiera wszystkie pola specyficzne dla podtypu.
Przetestowałem ten projekt pod kątem zarządzania dziedziczeniem i bardzo się cieszę z elastyczności, jaką mogę zastosować we względnej aplikacji.
źródło
Innym sposobem na to jest użycie
INHERITS
komponentu. Na przykład:W ten sposób można zdefiniować dziedziczenie między tabelami.
źródło
INHERITS
oprócz PostgreSQL ? MySQL na przykład?Opieram się na metodzie nr 1 (ujednolicona tabela sekcji), w celu wydajnego pobierania całych polityk wraz ze wszystkimi ich sekcjami (zakładam, że twój system będzie dużo robił).
Ponadto nie wiem, jakiej wersji programu SQL Server używasz, ale w 2008 r. W rzadkich kolumnach pomagają zoptymalizować wydajność w sytuacjach, w których wiele wartości w kolumnie będzie NULL.
Ostatecznie musisz zdecydować, jak „podobne” są sekcje zasad. Chyba, że różnią się znacznie, myślę, że bardziej znormalizowane rozwiązanie może być większym problemem niż jest warte ... ale tylko Ty możesz wykonać to połączenie. :)
źródło
Alternatywnie, rozważ użycie baz danych dokumentów (takich jak MongoDB), które natywnie obsługują bogate struktury danych i zagnieżdżanie.
źródło
Sprawdź odpowiedź, którą tutaj podałem
Płynne mapowanie NHibernate jeden do jednego z syntetycznymi kluczami
źródło