Projekt bazy danych: Normalizacja relacji „(wiele do wielu) do wielu”

14

Krótka wersja

Muszę dodać stałą liczbę dodatkowych właściwości do każdej pary w istniejącym złączeniu wiele do wielu. Przechodząc do poniższych schematów, który z wariantów 1-4 jest najlepszym sposobem, jeśli chodzi o zalety i wady, aby to osiągnąć poprzez rozszerzenie przypadku podstawowego? Czy jest lepsza alternatywa, której tutaj nie rozważałem?

Dłuższa wersja

Obecnie mam dwie tabele w relacji wiele do wielu za pośrednictwem pośredniej tabeli łączenia. Teraz muszę dodać dodatkowe linki do właściwości należących do pary istniejących obiektów. Mam ustaloną liczbę tych właściwości dla każdej pary, chociaż jeden wpis w tabeli właściwości może dotyczyć wielu par (lub nawet może być użyty wiele razy dla jednej pary). Próbuję ustalić najlepszy sposób na zrobienie tego i mam problem z ustaleniem, jak myśleć o sytuacji. Semantycznie wydaje się, że równie dobrze mogę to opisać jako jedno z poniższych:

  1. Jedna para połączona z jednym zestawem stałej liczby dodatkowych właściwości
  2. Jedna para powiązana z wieloma dodatkowymi właściwościami
  3. Wiele (dwóch) obiektów powiązanych z jednym zestawem właściwości
  4. Wiele obiektów powiązanych z wieloma właściwościami

Przykład

Mam dwa typy obiektów, X i Y, każdy z unikalnymi identyfikatorami, a łączący tabelę objx_objyz kolumnami x_idi y_id, które razem tworzą klucz podstawowy dla łącza. Każdy X może być powiązany z wieloma Y i odwrotnie. To jest konfiguracja dla mojej istniejącej relacji wiele do wielu.

Podstawowa skrzynka

Podstawa

Teraz dodatkowo mam zestaw właściwości zdefiniowanych w innej tabeli oraz zestaw warunków, w których dana para (X, Y) powinna mieć właściwość P. Liczba warunków jest stała i taka sama dla wszystkich par. Mówią w zasadzie: „W sytuacji C1 para (X1, Y1) ma właściwość P1”, „W sytuacji C2 para (X1, Y1) ma właściwość P2” i tak dalej, dla trzech sytuacji / warunków dla każdej pary w złączeniu stół.

opcja 1

W mojej obecnej sytuacji istnieją dokładnie trzy takie warunki, a nie mam żadnego powodu, by oczekiwać, że w celu zwiększenia, więc jedną z możliwości jest dodanie kolumny c1_p_id, c2_p_idi c3_p_iddo featx_featy, określając dla danego x_ida y_id, którego właściwość p_iddo stosowania w każdym z trzech przypadków .

opcja 1

Nie wydaje mi się to świetnym pomysłem, ponieważ komplikuje on SQL, aby wybrać wszystkie właściwości zastosowane do funkcji i nie łatwo skaluje się do większej liczby warunków. Wymusza jednak wymóg pewnej liczby warunków na parę (X, Y). W rzeczywistości jest to jedyna opcja, która to robi.

Opcja 2

Utwórz tabelę warunków condi dodaj identyfikator warunku do klucza podstawowego tabeli łączenia.

Opcja 2

Jednym minusem tego jest to, że nie określa liczby warunków dla każdej pary. Innym jest to, że gdy rozważam tylko początkowy związek z czymś takim jak

SELECT objx.*, objy.* FROM objx
  INNER JOIN objx_objy ON objx_objy.x_id = objx.id
  INNER JOIN objy ON objy.id = objx_objy.y_id

Następnie muszę dodać DISTINCTklauzulę, aby uniknąć powielania wpisów. Wydaje się, że stracił to fakt, że każda para powinna istnieć tylko raz.

Opcja 3

Utwórz nowy „identyfikator pary” w tabeli łączenia, a następnie umieść drugą tabelę łączy między pierwszą a właściwościami i warunkami.

Opcja 3

Wydaje się, że ma to najmniej wad, oprócz braku wymuszania określonej liczby warunków dla każdej pary. Czy ma sens tworzenie nowego identyfikatora, który identyfikuje jedynie identyfikatory istniejące?

Opcja 4 (3b)

Zasadniczo to samo co opcja 3, ale bez tworzenia dodatkowego pola identyfikatora. Odbywa się to poprzez umieszczenie obu oryginalnych identyfikatorów w nowej tabeli łączenia, aby zawierała x_idi y_idpola zamiast xy_id.

Opcja 4

Dodatkową zaletą tego formularza jest to, że nie zmienia on istniejących tabel (choć nie są one jeszcze w produkcji). Jednak zasadniczo powiela całą tabelę wiele razy (lub tak też się wydaje), więc też nie wydaje się idealny.

streszczenie

Mam wrażenie, że opcje 3 i 4 są na tyle podobne, że mógłbym wybrać jedną z nich. Prawdopodobnie miałbym do tej pory, gdyby nie wymóg małej, stałej liczby linków do właściwości, co sprawia, że ​​Opcja 1 wydaje się bardziej rozsądna niż byłaby w innym przypadku. Opierając się na niektórych bardzo ograniczonych testach, dodanie DISTINCTklauzuli do moich zapytań nie wydaje się wpływać na wydajność w tej sytuacji, ale nie jestem pewien, czy Opcja 2 reprezentuje sytuację, podobnie jak inne, z powodu nieodłącznego duplikowania spowodowanego umieszczeniem te same pary (X, Y) w wielu wierszach tabeli łączy.

Czy jedna z tych opcji jest moim najlepszym rozwiązaniem, czy też jest inna struktura, którą powinienem rozważyć?

Michael Underwood
źródło
Ogólnie zgadzam się, że 1 i 4 to najlepsze opcje. Wymuszenie stałej (3) liczby właściwości za pomocą opcji 4 nie byłoby łatwe, ale myślę, że jest to wykonalne.
ypercubeᵀᴹ
Dla DISTINCTklauzuli, myślałem o zapytaniu jak ten na koniec # 2, który linki xi ydzięki xycale nie odnosi się do c... więc jeśli mam (x_id, y_id, c_id)ograniczone UNIQUEz wierszy (1,1,1)i (1,1,2), następnie SELECT x.id, y.id FROM x JOIN xyc JOIN y, będę wracać dwóch identycznych wiersze (1,1)i (1,1).
Michael Underwood
1
Ach ok. Zrezygnowałbym i tak z opcji 2.
Wybrałbym
Im więcej o tym myślę, tym bardziej czuję, że ograniczenie liczby właściwości do dokładnie trzech jest najmniej ważnym z moich wymagań. Tak więc poza kolejną konstruktywną informacją zwrotną w najbliższym czasie, prawdopodobnie w tym momencie wybiorę # 4. Dziękuję za Twój wkład, @ypercube!
Michael Underwood

Odpowiedzi:

7
  • opcja 1

    * Nie wydaje mi się to świetnym pomysłem, ponieważ komplikuje SQL wybór wszystkich właściwości zastosowanych do funkcji…

    Niekoniecznie komplikuje zapytania SQL (patrz wniosek poniżej).

    … I nie łatwo skaluje się do większej liczby warunków…

    Skaluje się z łatwością do większej liczby warunków, o ile nadal istnieje ustalona liczba warunków i nie ma ich dziesiątek ani setek.

    Wymusza jednak wymóg pewnej liczby warunków na parę (X, Y). W rzeczywistości jest to jedyna opcja, która to robi. *

    Tak, i chociaż w komentarzu powiedziałeś, że jest to „najmniej ważne z moich wymagań”, nie powiedziałeś, że to w ogóle nie ma znaczenia.

  • Opcja 2

    Jednym minusem tego jest to, że nie określa liczby warunków dla każdej pary. Innym jest to, że gdy rozważam tylko początkową relację… muszę dodać klauzulę DISTINCT, aby uniknąć powielania wpisów…

    Myślę, że możesz odrzucić tę opcję z powodu wspomnianych komplikacji. objx_objyStół może być tabela jazdy dla niektórych zapytań (np „wybierz wszystkie właściwości zastosowane do funkcji”, który biorę na myśli wszystkie właściwości stosowane do objxlub objy). Możesz użyć widoku do wstępnego zastosowania, DISTINCTwięc nie jest to kwestia skomplikowanych zapytań, ale skaluje się to bardzo źle pod względem wydajności przy bardzo niewielkim zysku.

  • Opcja 3

    Czy ma sens tworzenie nowego identyfikatora, który identyfikuje jedynie identyfikatory istniejące?

    Nie, nie ma - opcja 4 jest lepsza pod każdym względem.

  • Opcja 4

    … W zasadzie powiela cały stół wiele razy (a przynajmniej tak się wydaje), więc też nie wydaje się idealny.

    Ta opcja jest w porządku - jest to oczywisty sposób konfigurowania relacji, jeśli liczba właściwości jest zmienna lub może ulec zmianie

Wniosek

Moją preferencją byłaby opcja 1, jeśli liczba właściwości na objx_objyjest prawdopodobnie stabilna, i jeśli nie możesz sobie wyobrazić, aby kiedykolwiek dodać więcej niż garść dodatkowych. Jest to również jedyna opcja, która wymusza ograniczenie „liczba właściwości = 3” - wymuszenie podobnego ograniczenia w opcji 4 prawdopodobnie wymagałoby dodania c1_p_id… kolumn do tabeli xy *.

Jeśli naprawdę nie dbasz o ten warunek, a także masz powody, by wątpić, że liczba właściwości będzie stabilna, wybierz opcję 4.

Jeśli nie masz pewności, wybierz opcję 1 - jest to prostsze i zdecydowanie lepsze, jeśli masz taką opcję, jak powiedzieli inni. W przypadku odłożenia opcji 1 „… ponieważ komplikowanie przez SQL wyboru wszystkich właściwości zastosowanych do funkcji…” sugeruję utworzenie widoku zapewniającego te same dane, co dodatkowa tabela w opcji 4:

tabele opcji 1:

create table prop(id integer primary key);
create table objx(id integer primary key);
create table objy(id integer primary key);

create table objx_objy(
  x_id integer references objx
, y_id integer references objy
, c1_p_id integer not null references prop
, c2_p_id integer not null references prop
, c3_p_id integer not null references prop
, primary key (x_id, y_id)
);

insert into prop(id) select generate_series(90,99);
insert into objx(id) select generate_series(10,12);
insert into objy(id) select generate_series(20,22);

insert into objx_objy(x_id,y_id,c1_p_id,c2_p_id,c3_p_id)
select objx.id, objy.id, 90, 91, 90+floor(random()*10)
from objx cross join objy;

zobacz opcję „emuluj” 4:

create view objx_objy_prop as
select x_id
     , y_id
     , unnest(array[1,2,3]) c_id
     , unnest(array[c1_p_id,c2_p_id,c3_p_id]) p_id
from objx_objy;

„wybierz wszystkie właściwości zastosowane do obiektu”:

select distinct p_id from objx_objy_prop where x_id=10 order by p_id;

/*
|p_id|
|---:|
|  90|
|  91|
|  97|
|  98|
*/

dbfiddle tutaj

Jack mówi, że spróbuj topanswers.xyz
źródło
-3

Wierzę, że każda z tych opcji mogłaby działać, ale wybrałbym opcję 1, jeśli liczba warunków jest naprawdę ustalona na 3, a opcję 2, jeśli tak nie jest. Brzytwa Ockhama działa również przy projektowaniu baz danych, przy czym wszystkie inne czynniki są równe, najprostszy projekt jest zwykle najlepszy.

Chociaż jeśli chcesz przestrzegać ścisłych zasad normalizacji bazy danych, uważam, że musisz wybrać 2, niezależnie od tego, czy liczba warunków jest ustalona.

Matthew Sontum
źródło