То, что вы описываете, называется полиморфными ассоциациями. То есть столбец «внешнего ключа» содержит значение идентификатора, которое должно существовать в одной из набора целевых таблиц. Обычно целевые таблицы связаны каким-либо образом, например, являются экземплярами некоторого общего суперкласса данных. Вам также понадобится еще один столбец рядом со столбцом внешнего ключа, чтобы в каждой строке можно было указать, на какую целевую таблицу ссылаются.
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
в одном столбце. Тот факт, что эти два атрибута имеют совместимые типы данных, является случайным; они все еще обозначают разные логические сущности.
Полиморфные ассоциации также нарушают третью нормальную форму , потому что значение столбца зависит от дополнительного столбца, который называет таблицу, к которой относится внешний ключ. В третьей нормальной форме атрибут в таблице должен зависеть только от первичного ключа этой таблицы.
Комментарий от @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
цель также будет меняться ...... Я слишком усложняю или как? Помогите!