Ваш текущий дизайн называется исключительные дуги , где таблица sets
имеет два внешних ключа, и для них необходим только один из них, чтобы быть ненулевым. Это один из способов реализации полиморфных ассоциаций, поскольку данный внешний ключ может ссылаться только на одну целевую таблицу.
Другое решение состоит в создании общего "суперпользователя", на который ссылаются и users
, и schools
, а затем используют его в качестве родителя sets
.
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 содержит и идентификаторы пользователей, и идентификаторы SchoolID, верно? Это означало бы, что у меня не будет одинакового идентификатора для пользователя и школы. Как я могу обеспечить это?
Пусть таблица SetOwners генерирует значения идентификаторов. Вы должны вставить в SetOwners, прежде чем вы сможете вставить в пользователей или школ. Поэтому сделайте идентификаторы в «Пользователи и школы» , а не с автоматическим увеличением; просто используйте значение, сгенерированное SetOwners:
INSERT INTO SetOwners DEFAULT VALUES; -- generates an id
INSERT INTO Schools (id, name, location) VALUES (LAST_INSERT_ID(), 'name', 'location');
Таким образом, никакое заданное значение идентификатора не будет использоваться ни для школы, ни для пользователя.
Если я хочу получить тип владельца для набора, нужен ли мне атрибут ownerType в моей таблице SetOwner?
Вы, конечно, можете это сделать. На самом деле, могут быть другие столбцы, которые являются общими как для пользователей, так и для школ, и вы можете поместить эти столбцы в суперпопулярные SetOwners. Это входит в паттерн наследования таблиц классов Мартина Фаулера.
И если я хочу получить название школы или пользователя (в зависимости от того, какой это тип), могу ли я сделать это с помощью одного запроса, или мне нужно два запроса (первый для получения типа и второй для получения имя)?
Вам нужно присоединиться. Если вы запрашиваете у данного набора и знаете, что он принадлежит пользователю (не школе), вы можете пропустить присоединение к 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 должен соответствовать одной или другой таблице, Пользователи или Школы, но не обоим.