Можно ли сделать внешний ключ MySQL для одной из двух возможных таблиц?
Ну вот моя проблема у меня есть три таблицы; регионы, страны, государства. Страны могут быть внутри регионов, государства могут быть внутри регионов. Регионы являются вершиной пищевой цепи.
теперь я добавляю таблицу popular_areas с двумя столбцами; region_id и popular_place_id. Можно ли сделать popular_place_id внешним ключом для любой из стран или государства. Вероятно, мне придется добавить столбец popular_place_type для определения независимо от того, описывает ли идентификатор страну или государство в любом случае.
3 ответа:
то, что вы описываете, называется полиморфными ассоциациями. То есть столбец" внешний ключ " содержит значение идентификатора, которое должно существовать в одной из набора целевых таблиц. Обычно целевые таблицы связаны каким-либо образом, например, являются экземплярами некоторого общего суперкласса данных. Вам также понадобится другой столбец вдоль столбца внешнего ключа, чтобы в каждой строке вы могли указать, на какую целевую таблицу ссылаются.
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) );
In термины реляционной теории, полиморфные Ассоциации нарушает Первая Нормальная Форма, потому что
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) ...
но этот формат по-прежнему требует, чтобы вы писали ссылки на все таблицы. От этого никуда не денешься, что.
Это не самое элегантное решение в мире, но вы могли бы использовать наследование конкретной таблицы чтобы сделать эту работу.
концептуально вы предлагаете понятие класса "вещей, которые могут быть популярными областями", от которых наследуются ваши три типа мест. Вы можете представить это как таблицу с именем, например,
places
где каждая строка имеет отношение один к одному со строкой вregions
,countries
илиstates
. (Атрибуты, которые совместно используются между регионами, страны или государства, если таковые имеются, могут быть помещены в эту таблицу мест.) Вашpopular_place_id
тогда будет ссылка внешнего ключа на строку в таблице places, которая затем приведет вас к региону, стране или государству.решение, которое вы предлагаете со вторым столбцом для описания типа ассоциации, заключается в том, как Rails обрабатывает полиморфные ассоциации, но я не поклонник этого в целом. Билл очень подробно объясняет, почему полиморфные ассоциации не являются вашими друзьями.
вот исправление к подходу Билла Карвина "supertable", используя составной ключ
( place_type, place_id )
для устранения нарушений воспринимаемой нормальной формы:CREATE TABLE places ( place_id INT NOT NULL UNIQUE, place_type VARCHAR(10) NOT NULL CHECK ( place_type = 'state', 'country' ), UNIQUE ( place_type, place_id ) ); CREATE TABLE states ( place_id INT NOT NULL UNIQUE, place_type VARCHAR(10) DEFAULT 'state' NOT NULL CHECK ( place_type = 'state' ), FOREIGN KEY ( place_type, place_id ) REFERENCES places ( place_type, place_id ) -- attributes specific to states go here ); CREATE TABLE countries ( place_id INT NOT NULL UNIQUE, place_type VARCHAR(10) DEFAULT 'country' NOT NULL CHECK ( place_type = 'country' ), FOREIGN KEY ( place_type, place_id ) REFERENCES places ( place_type, place_id ) -- attributes specific to country go here ); CREATE TABLE popular_areas ( user_id INT NOT NULL, place_id INT NOT NULL, UNIQUE ( user_id, place_id ), FOREIGN KEY ( place_type, place_id ) REFERENCES places ( place_type, place_id ) );
что этот дизайн не может гарантировать, что для каждой строки в
places
существует строкуstates
илиcountries
(но не оба). Это ограничение внешних ключей в SQL. В полной СУБД, совместимой со стандартами SQL-92, вы можете определить переносимые межтабличные ограничения, которые позволят вам достичь того же, но это неуклюже, включает в себя транзакцию, и такая СУБД еще не вышла на рынок.