Извините за заголовок трудно объяснить.
Мне нужна модель данных, подобная этому:
Как Вы видите, набор может принадлежать обоим пользователь или школа. Моя проблема: нужно только позволить принадлежать или пользователю ИЛИ школе. Но никогда оба одновременно.
Как я могу решить эту проблему?
Ваш текущий проект называется исключающими дугами , где устанавливает
таблицу, имеющую два внешних ключа, и требуется, чтобы ровно один из них был ненулевым. Это один из способов реализации полиморфных ассоциаций, поскольку данный внешний ключ может ссылаться только на одну целевую таблицу.
Другое решение - создать общую «супертаблицу», на которую ссылаются как пользователи
, так и школы
, а затем использовать ее как родительский для наборов
.
create table set_owner
create table users
PK is also FK --> set_owner
create table schools
PK is also FK --> set_owner
create table sets
FK --> set_owner
Вы можете думать об этом как об аналоге интерфейса в объектно-ориентированном моделировании:
interface SetOwner { ... }
class User implements SetOwner { ... }
class School implements SetOwner { ... }
class Set {
SetOwner owner;
}
По вашим комментариям:
Итак, таблица SetOwner содержит как UserID, так и SchoolID, верно? Это означало бы, что мне не разрешили бы иметь один и тот же идентификатор для пользователя и школы. Как я могу добиться этого?
Разрешить таблице SetOwners генерировать значения идентификаторов. Вы должны вставить в SetOwners, прежде чем сможете вставить в Users или Schools. Так что сделайте идентификаторы в Users and Schools без автоматического увеличения ; просто используйте значение, сгенерированное SetOwners:
INSERT INTO SetOwners DEFAULT VALUES; -- generates an id
INSERT INTO Schools (id, name, location) VALUES (LAST_INSERT_ID(), 'name', 'location');
Таким образом, ни одно значение идентификатора не будет использоваться ни для школы, ни для пользователя.
Если я хочу получить тип владельца для набора, нужен ли мне атрибут ownerType в моей таблице SetOwner?
Вы, конечно, можете это сделать. Фактически, могут быть и другие столбцы, общие как для пользователей, так и для школ, и вы можете поместить эти столбцы в надтаблицу SetOwners. Это входит в паттерн Мартина Фаулера Наследование таблицы классов .
И если я хочу получить название школы или пользователя (в зависимости от того, какой это тип), могу ли я сделать это с помощью одного запроса или мне нужно два запроса (первый для получения типа, а второй для получения name)?
Вам нужно выполнить соединение. Если вы запрашиваете из заданного набора и знаете, что он принадлежит пользователю (а не школе), вы можете пропустить присоединение к SetOwners и присоединиться непосредственно к пользователям. Соединения не обязательно должны происходить по внешним ключам.
SELECT u.name FROM Sets s JOIN Users u ON s.SetOwner_id = u.id WHERE ...
Если вы не знаете, принадлежит ли данный набор пользователю или школе, вам придется выполнить внешнее соединение с обоими:
SELECT COALESCE(u.name, sc.name) AS name
FROM Sets s
LEFT OUTER JOIN Users u ON s.SetOwner_id = u.id
LEFT OUTER JOIN Schools sc ON s.SetOwner_id = sc.id
WHERE ...
Вы знаете, что SetOwner_id должен соответствовать одной или другой таблице, Users или школы, но не то и другое вместе.
Вам нужно будет использовать триггер для применения этого бизнес-правила, потому что MySQL имеет, но не применяет ограничения CHECK (на любом движке).
С оговоркой: не совсем понятно, что такое Set в вашей модели данных.
Я сомневаюсь в вашей модели данных.
Почему Пользователь -> Наборы и Школа -> Наборы должны указывать на одну и ту же таблицу. Это не более нормализовано, если это действительно независимые наборы данных. Тот факт, что у них есть некоторые общие черты в отслеживаемых столбцах, не означает, что они должны храниться в одной таблице. Являются ли они логически одним и тем же?
Просто создайте отдельные таблицы для школьных наборов и для пользовательских наборов. Это также упростит обратные запросы, потому что вам не нужно будет проверять нулевые отношения в таблице Sets, чтобы узнать, действительно ли это UserSet или SchoolSet.