Те, що ви описуєте, називається Поліморфними асоціаціями. Тобто, стовпець "зовнішній ключ" містить значення id, яке повинно існувати в одній із наборів цільових таблиць. Зазвичай цільові таблиці певним чином пов'язані між собою, наприклад, як екземпляри деяких загальних надкласових даних. Вам також знадобиться інший стовпець уздовж стовпця із зовнішнім ключем, щоб у кожному рядку можна було вказати, на яку цільову таблицю посилається.
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
);
Немає можливості моделювати поліморфні асоціації, використовуючи обмеження SQL. Обмеження зовнішнього ключа завжди посилається на одну цільову таблицю.
Поліморфні асоціації підтримуються рамками, такими як Rails та Hibernate. Але вони прямо говорять, що ви повинні відключити обмеження SQL, щоб використовувати цю функцію. Натомість додаток або фреймворк повинні виконувати еквівалентну роботу, щоб забезпечити задоволення посилання. Тобто значення в зовнішньому ключі присутнє в одній з можливих цільових таблиць.
Поліморфні асоціації слабкі щодо забезпечення узгодженості баз даних. Цілісність даних залежить від усіх клієнтів, які звертаються до бази даних, з такою ж застосованою логікою цілісності референсності, а також виконання повинно бути без помилок.
Ось декілька альтернативних рішень, які використовують перевагу цілісної бази даних, орієнтованої на базу даних:
Створіть одну додаткову таблицю для цілі. Наприклад popular_states
і popular_countries
, яка посилання states
і countries
відповідно. Кожна з цих «популярних» таблиць також посилається на профіль користувача.
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),
);
Це означає, що для отримання всіх улюблених місць користувачем потрібно запитувати обидві ці таблиці. Але це означає, що ви можете покластися на базу даних, щоб забезпечити послідовність.
Створіть places
таблицю як суперфабрикат. Як згадує Ебі, другою альтернативою є те, що ваші популярні місця посилаються на таку таблицю places
, яка є батьківською для обох states
і countries
. Тобто, і держави, і країни також мають закордонний ключ places
(ви навіть можете зробити цей зовнішній ключ також первинним ключем states
і 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)
);
Використовуйте два стовпчики. Замість одного стовпця, який може посилатися на будь-яку з двох цільових таблиць, використовуйте два стовпці. Ці два стовпці можуть бути NULL
; насправді лише одна з них не повинна бути 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)
);
З точки зору реляційної теорії, поліморфні асоціації порушують Першу нормальну форму , оскільки popular_place_id
фактично стовпець має два значення: це або держава, або країна. Ви б не зберігали особи age
та їх особи phone_number
в одному стовпчику, і з тієї ж причини ви не повинні зберігати state_id
і country_id
в одному стовпчику. Той факт, що ці два атрибути мають сумісні типи даних, є випадковим; вони все ще означають різні логічні сутності.
Поліморфні асоціації також порушують Третю нормальну форму , тому що значення стовпця залежить від додаткового стовпчика, який називає таблицю, на яку посилається іноземний ключ. У третій звичайній формі атрибут у таблиці повинен залежати лише від первинного ключа цієї таблиці.
Re коментар від @SavasVedova:
Я не впевнений, що я слідкую за вашим описом, не бачачи визначень таблиці чи прикладу запиту, але це здається, що у вас просто кілька Filters
таблиць, кожна з яких містить іноземний ключ, що посилається на центральну Products
таблицю.
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...
Приєднання продуктів до певного типу фільтру легко, якщо ви знаєте, до якого типу ви хочете приєднатися:
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
Якщо ви хочете, щоб тип фільтра був динамічним, потрібно написати код програми для побудови SQL-запиту. SQL вимагає, щоб таблиця була вказана та зафіксована під час написання запиту. Ви не можете зробити так, щоб об'єднану таблицю вибирали динамічно на основі значень, знайдених в окремих рядках Products
.
Єдиний інший варіант - приєднатись до всіх таблиць фільтрів за допомогою зовнішніх з'єднань. Ті, у яких немає відповідного product_id, будуть просто повернені у вигляді одного ряду нулів. Але вам все одно доведеться жорстко кодувати всі об'єднані таблиці, і якщо ви додасте нові таблиці фільтрів, вам доведеться оновити код.
SELECT * FROM Products
LEFT OUTER JOIN FiltersType1 USING (product_id)
LEFT OUTER JOIN FiltersType2 USING (product_id)
LEFT OUTER JOIN FiltersType3 USING (product_id)
...
Ще один спосіб приєднатися до всіх таблиць фільтрів - це робити це послідовно:
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)
...
Але цей формат все ще вимагає, щоб ви писали посилання на всі таблиці. Цього не обійти.
join
цільові показники також зміняться ...... Я занадто сильно ускладнюю чи що? Довідка!