Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Czy można zrobić klucz obcy MySQL w jednej z dwóch możliwych tabel?

To, co opisujesz, nazywa się Asocjacjami Polimorficznymi. Oznacza to, że kolumna „klucz obcy” zawiera wartość identyfikatora, która musi istnieć w jednej z tabel docelowych. Zazwyczaj tabele docelowe są w jakiś sposób powiązane, na przykład są instancjami jakiejś wspólnej nadklasy danych. Potrzebna byłaby również kolejna kolumna obok kolumny klucza obcego, aby w każdym wierszu można było określić, do której tabeli docelowej się odwołuje.

CREATE TABLE popular_places (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  place_type VARCHAR(10) -- either 'states' or 'countries'
  -- foreign key is not possible
);

Nie ma możliwości modelowania powiązań polimorficznych przy użyciu ograniczeń SQL. Ograniczenie klucza obcego zawsze odwołuje się do jednego tabela docelowa.

Asocjacje polimorficzne są obsługiwane przez frameworki, takie jak Rails i Hibernate. Ale wyraźnie mówią, że musisz wyłączyć ograniczenia SQL, aby korzystać z tej funkcji. Zamiast tego aplikacja lub struktura musi wykonać równoważną pracę, aby zapewnić, że odwołanie jest spełnione. Oznacza to, że wartość w kluczu obcym jest obecna w jednej z możliwych tabel docelowych.

Powiązania polimorficzne są słabe w odniesieniu do wymuszania spójności bazy danych. Integralność danych zależy od tego, czy wszyscy klienci uzyskują dostęp do bazy danych z wymuszeniem tej samej logiki integralności referencyjnej, a egzekwowanie musi być wolne od błędów.

Oto kilka alternatywnych rozwiązań, które wykorzystują integralność referencyjną wymuszaną przez bazę danych:

Utwórz jedną dodatkową tabelę na cel. Na przykład popular_states i popular_countries , które odwołują się do states i countries odpowiednio. Każda z tych „popularnych” tabel odwołuje się również do profilu użytkownika.

CREATE TABLE popular_states (
  state_id INT NOT NULL,
  user_id  INT NOT NULL,
  PRIMARY KEY(state_id, user_id),
  FOREIGN KEY (state_id) REFERENCES states(state_id),
  FOREIGN KEY (user_id) REFERENCES users(user_id),
);

CREATE TABLE popular_countries (
  country_id INT NOT NULL,
  user_id    INT NOT NULL,
  PRIMARY KEY(country_id, user_id),
  FOREIGN KEY (country_id) REFERENCES countries(country_id),
  FOREIGN KEY (user_id) REFERENCES users(user_id),
);

Oznacza to, że aby uzyskać wszystkie popularne ulubione miejsca użytkownika, należy wykonać zapytanie w obu tych tabelach. Ale oznacza to, że możesz polegać na bazie danych, aby wymusić spójność.

Utwórz places stół jako superstół. Jak wspomina Abie, drugą alternatywą jest to, że twoje popularne miejsca odwołują się do tabeli, takiej jak places , który jest rodzicem obu states i countries . Oznacza to, że zarówno stany, jak i kraje mają również klucz obcy do places (możesz nawet ustawić ten klucz obcy jako klucz podstawowy states i countries ).

CREATE TABLE popular_areas (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  PRIMARY KEY (user_id, place_id),
  FOREIGN KEY (place_id) REFERENCES places(place_id)
);

CREATE TABLE states (
  state_id INT NOT NULL PRIMARY KEY,
  FOREIGN KEY (state_id) REFERENCES places(place_id)
);

CREATE TABLE countries (
  country_id INT NOT NULL PRIMARY KEY,
  FOREIGN KEY (country_id) REFERENCES places(place_id)
);

Użyj dwóch kolumn. Zamiast jednej kolumny, która może odwoływać się do jednej z dwóch tabel docelowych, użyj dwóch kolumn. Te dwie kolumny mogą mieć wartość NULL; w rzeczywistości tylko jeden z nich nie powinien mieć wartości NULL .

CREATE TABLE popular_areas (
  place_id SERIAL PRIMARY KEY,
  user_id INT NOT NULL,
  state_id INT,
  country_id INT,
  CONSTRAINT UNIQUE (user_id, state_id, country_id), -- UNIQUE permits NULLs
  CONSTRAINT CHECK (state_id IS NOT NULL OR country_id IS NOT NULL),
  FOREIGN KEY (state_id) REFERENCES places(place_id),
  FOREIGN KEY (country_id) REFERENCES places(place_id)
);

Z punktu widzenia teorii relacji, skojarzenia polimorficzne naruszają Pierwszą postać normalną , ponieważ popular_place_id jest w efekcie kolumną o dwóch znaczeniach:stan lub kraj. Nie zapisałbyś age osoby i ich phone_number w jednej kolumnie iz tego samego powodu nie powinieneś przechowywać obu state_id i country_id w jednej kolumnie. Fakt, że te dwa atrybuty mają kompatybilne typy danych, jest przypadkowy; nadal oznaczają różne jednostki logiczne.

Skojarzenia polimorficzne naruszają także Trzecia postać normalna , ponieważ znaczenie kolumny zależy od dodatkowej kolumny, która nazywa tabelę, do której odnosi się klucz obcy. W trzeciej postaci normalnej atrybut w tabeli musi zależeć tylko od klucza podstawowego tej tabeli.

Ponownie komentarz od @SavasVedova:

Nie jestem pewien, czy postępuję zgodnie z twoim opisem, nie widząc definicji tabeli lub przykładowego zapytania, ale wygląda na to, że masz po prostu wiele Filters tabele, z których każda zawiera klucz obcy, który odwołuje się do centralnego Products stół.

CREATE TABLE Products (
  product_id INT PRIMARY KEY
);

CREATE TABLE FiltersType1 (
  filter_id INT PRIMARY KEY,
  product_id INT NOT NULL,
  FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

CREATE TABLE FiltersType2 (
  filter_id INT  PRIMARY KEY,
  product_id INT NOT NULL,
  FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

...and other filter tables...

Łączenie produktów z określonym typem filtra jest łatwe, jeśli wiesz, do jakiego typu chcesz dołączyć:

SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)

Jeśli chcesz, aby typ filtru był dynamiczny, musisz napisać kod aplikacji w celu skonstruowania zapytania SQL. SQL wymaga, aby tabela była określona i naprawiona w momencie pisania zapytania. Nie możesz sprawić, by połączona tabela była wybierana dynamicznie na podstawie wartości znalezionych w poszczególnych wierszach Products .

Jedyną inną opcją jest dołączenie do wszystkich filtruj tabele za pomocą sprzężeń zewnętrznych. Te, które nie mają pasującego identyfikatora produktu, zostaną zwrócone jako pojedynczy wiersz wartości null. Ale nadal musisz zakodować wszystko połączonych tabel, a jeśli dodasz nowe tabele filtrów, musisz zaktualizować swój kod.

SELECT * FROM Products
LEFT OUTER JOIN FiltersType1 USING (product_id)
LEFT OUTER JOIN FiltersType2 USING (product_id)
LEFT OUTER JOIN FiltersType3 USING (product_id)
...

Innym sposobem dołączenia do wszystkich tabel filtrów jest zrobienie tego szeregowo:

SELECT * FROM Product
INNER JOIN FiltersType1 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType3 USING (product_id)
...

Ale ten format nadal wymaga pisania referencji do wszystkich tabel. Nie da się tego obejść.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. XAMPP — nieoczekiwane zamknięcie MySQL

  2. Jak sprawdzić uprawnienia użytkownika w MySQL Workbench za pomocą GUI

  3. Czy istnieje RZECZYWISTA różnica wydajności między kluczami podstawowymi INT i VARCHAR?

  4. MYSQL sum() dla różnych wierszy

  5. W SQL / MySQL, jaka jest różnica między ON i WHERE w instrukcji join?