Wzorzec projektowy - jedna z wielu tabel nadrzędnych

13

Często spotykam się z sytuacją w bazie danych, w której dana tabela może przejść do jednej z wielu różnych tabel nadrzędnych. Widziałem dwa rozwiązania problemu, ale żadne z nich osobiście nie jest satysfakcjonujące. Jestem ciekawy, jakie inne wzory tam widziałeś? Czy jest na to lepszy sposób?

Przemyślany przykład
Załóżmy, że mój system ma Alerts. Alerty mogą być odbierane dla różnych obiektów - klientów, wiadomości i produktów. Dany alert może dotyczyć tylko jednego elementu. Z jakiegokolwiek powodu klienci, artykuły i produkty szybko się przemieszczają (lub lokalizują), dlatego nie można pobrać niezbędnych tekstów / danych do alertów po utworzeniu alertu. Biorąc pod uwagę tę konfigurację, widziałem dwa rozwiązania.

Uwaga: poniżej DDL dotyczy SQL Server, ale moje pytanie powinno dotyczyć dowolnego DBMS.

Rozwiązanie 1 - Wiele kluczy zerowalnych FKeys

W tym rozwiązaniu tabela, która prowadzi do jednej z wielu tabel, ma wiele kolumn FK (ze względu na zwięzłość poniższy DDL nie pokazuje tworzenia FK). DOBRY - W tym rozwiązaniu fajnie jest mieć klucze obce. Brak optyki FK sprawia, że ​​jest to wygodne i stosunkowo łatwe do dodawania dokładnych danych. ZŁE Zapytanie nie jest świetne, ponieważ wymaga N LEWEJ ŁĄCZENIA lub N UNII instrukcji, aby uzyskać powiązane dane. W SQL Server konkretnie LEWE POŁĄCZENIA wykluczają tworzenie widoku indeksowanego.

CREATE TABLE Product (
    ProductID    int identity(1,1) not null,
    CreateUTC    datetime2(7) not null,
     Name        varchar(100) not null
    CONSTRAINT   PK_Product Primary Key CLUSTERED (ProductID)
)
CREATE TABLE Customer (
    CustomerID  int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
     Name       varchar(100) not null
    CONSTRAINT  PK_Customer Primary Key CLUSTERED (CustomerID)
)
CREATE TABLE News (
    NewsID      int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    Name        varchar(100) not null
    CONSTRAINT  PK_News Primary Key CLUSTERED (NewsID)
)

CREATE TABLE Alert (
    AlertID     int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    ProductID   int null,
    NewsID      int null,
    CustomerID  int null,
    CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

ALTER TABLE Alert WITH CHECK ADD CONSTRAINT CK_OnlyOneFKAllowed 
CHECK ( 
    (ProductID is not null AND NewsID is     null and CustomerID is     null) OR 
    (ProductID is     null AND NewsID is not null and CustomerID is     null) OR 
    (ProductID is     null AND NewsID is     null and CustomerID is not null) 
)

Rozwiązanie 2 - Jeden FK w każdej tabeli nadrzędnej
W tym rozwiązaniu każda tabela „nadrzędna” ma FK do tabeli alertów. Ułatwia pobieranie alertów powiązanych z rodzicem. Z drugiej strony nie ma prawdziwego łańcucha od alertu do tego, kto się do niego odwołuje. Ponadto model danych pozwala na osierocone ostrzeżenia - w przypadku gdy alert nie jest powiązany z produktem, wiadomością lub klientem. Ponownie wiele LEWYCH DOŁĄCZ, aby dowiedzieć się o powiązaniu.

CREATE TABLE Product (
    ProductID    int identity(1,1) not null,
    CreateUTC    datetime2(7) not null,
     Name        varchar(100) not null
    AlertID     int null,
    CONSTRAINT   PK_Product Primary Key CLUSTERED (ProductID)
)
CREATE TABLE Customer (
    CustomerID  int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
     Name       varchar(100) not null
    AlertID     int null,
    CONSTRAINT  PK_Customer Primary Key CLUSTERED (CustomerID)
)
CREATE TABLE News (
    NewsID      int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    Name        varchar(100) not null
    AlertID     int null,
    CONSTRAINT  PK_News Primary Key CLUSTERED (NewsID)
)

CREATE TABLE Alert (
    AlertID     int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

Czy to tylko życie w bazie danych relacji? Czy istnieją alternatywne rozwiązania, które uważasz za bardziej satysfakcjonujące?

EBarr
źródło
1
Czy możesz utworzyć jedną tabelę nadrzędną, Alertable, z następującymi kolumnami: ID, CreateDate, Name, Type. Możesz mieć na nim swój stolik FK dla trzech dzieci, a twój ALert będzie FK tylko dla jednego stolika, co jest możliwe.
AK
W niektórych przypadkach masz rację - skuteczne rozwiązanie nr 3. Jednak za każdym razem, gdy dane szybko się przenoszą lub lokalizują, nie będzie działać. Powiedzmy na przykład, że produkt, klient i aktualności mają odpowiednią tabelę „Lang” do obsługi nazwy w kilku językach. Jeśli muszę podać „imię” w języku ojczystym użytkownika, nie mogę go zapisać Alertable. Czy to ma jakiś sens?
EBarr
1
@EBarr: „Jeśli muszę podać„ imię ”w języku ojczystym użytkownika, nie mogę zapisać go w formie Alertable. Czy to ma jakiś sens?” Nie, nie ma. Czy w przypadku obecnego schematu musisz podać „imię” w języku ojczystym użytkownika, czy możesz przechowywać go w tabeli Produkt, Klient lub Wiadomości?
ypercubeᵀᴹ
@ypercube Dodałem, że do każdej z tych tabel jest przypisana tabela językowa. Próbowałem stworzyć scenariusz, w którym tekst „nazwa” może się różnić w zależności od żądania, a zatem nie można go zapisać w Alertable.
EBarr
Jeśli nie ma ono już przyjętej nazwy, proponuję termin „przyłączenie ośmiornicy” dla zapytania, które można wykonać, aby wyświetlić wszystkie alerty i powiązanych z nimi rodziców. :)
Nathan Long,

Odpowiedzi:

4

Rozumiem, że drugie rozwiązanie nie ma zastosowania, ponieważ nie oferuje relacji jeden (obiekt) z wieloma (alert).

Trzymasz się tylko dwóch rozwiązań ze względu na ścisłą zgodność 3NF.

Zaprojektowałbym mniejszy schemat sprzęgania:

CREATE TABLE Product  (ProductID  int identity(1,1) not null, ...)
CREATE TABLE Customer (CustomerID int identity(1,1) not null, ...)
CREATE TABLE News     (NewsID     int identity(1,1) not null, ...)

CREATE TABLE Alert (
  -- See (1)
  -- AlertID     int identity(1,1) not null,

  AlertClass char(1) not null, -- 'P' - Product, 'C' - Customer, 'N' - News
  ForeignKey int not null,
  CreateUTC  datetime2(7) not null,

  -- See (2)
  CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertClass, ForeignKey)
)

-- (1) you don't need to specify an ID 'just because'. If it's meaningless, just don't.
-- (2) I do believe in composite keys

Lub, jeśli relacja uczciwości będzie obowiązkowa, mogę zaprojektować:

CREATE TABLE Product  (ProductID  int identity(1,1) not null, ...)
CREATE TABLE Customer (CustomerID int identity(1,1) not null, ...)
CREATE TABLE News     (NewsID     int identity(1,1) not null, ...)

CREATE TABLE Alert (
  AlertID     int identity(1,1) not null,
  AlertClass char(1) not null, /* 'P' - Product, 'C' - Customer, 'N' - News */
  CreateUTC  datetime2(7) not null,
  CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

CREATE TABLE AlertProduct  (AlertID..., ProductID...,  CONSTRAINT FK_AlertProduct_X_Product(ProductID)    REFERENCES Product)
CREATE TABLE AlertCustomer (AlertID..., CustomerID..., CONSTRAINT FK_AlertCustomer_X_Customer(CustomerID) REFERENCES Customer)
CREATE TABLE AlertNews     (AlertID..., NewsID...,     CONSTRAINT FK_AlertNews_X_News(NewsID)             REFERENCES News)

Tak czy siak...

Trzy prawidłowe rozwiązania plus inne do rozważenia dla wielu relacji (obiektów) z jednym (alertem) ...

Przedstawione, jaka jest moralność?

Różnią się subtelnie i mają taką samą wagę według kryteriów:

  • wydajność wstawiania i aktualizacji
  • złożoność zapytań
  • przestrzeń magazynowa

Więc wybierz ten program dla ciebie.

Marcus Vinicius Pompeu
źródło
1
Dzięki za wkład; Zgadzam się z większością twoich komentarzy. Prawdopodobnie zręcznie opisałem wymagany związek (z wyjątkiem rozwiązania 2 w twoich oczach), ponieważ był to wymyślony przykład. fn1 - zrozumiałem, po prostu bardzo upraszczałem tabele, aby skupić się na problemie. fn2 - klucze kompozytowe i wracam! Jeśli chodzi o mniejszy schemat sprzęgania, rozumiem prostotę, ale osobiście staram się projektować za pomocą DRI tam, gdzie to możliwe.
EBarr
Przeglądając to, zacząłem wątpić w poprawność mojego rozwiązania ... w każdym razie głosowano dwukrotnie, za co dziękuję. Chociaż uważam, że mój projekt jest ważny, ale nie nadaje się do podanego problemu, ponieważ związki / złączenia „n” nie są adresowane ...
Marcus Vinicius Pompeu
Akronim DRI mnie dopadł. Dla was wszystkich oznacza Deklaratywną integralność referencyjną, technikę związaną z integralnością referencyjną, która jest powszechnie stosowana jako ... (rolka bębna) ... instrukcje DDL FOREIGN KEY. Więcej na en.wikipedia.org/wiki/Declarative_Referential_Integrity i msdn.microsoft.com/en-us/library/…
Marcus Vinicius Pompeu
1

Użyłem utrzymywanych przez wyzwalacze tabel łączenia. rozwiązanie działa całkiem dobrze w ostateczności, jeśli refaktoryzacja bazy danych nie jest możliwa lub pożądana. Chodzi o to, że masz tabelę, która jest po to, aby poradzić sobie z problemami RI, a wszystkie DRI są temu przeciwne.

Chris Travers
źródło