Представляем супертипы и подтипы
Я предлагаю вам использовать супертипы и подтипы. Сначала создайте PartyType
и Party
таблицы:
CREATE TABLE dbo.PartyType (
PartyTypeID int NOT NULL identity(1,1) CONSTRAINT PK_PartyType PRIMARY KEY CLUSTERED
Name varchar(32) CONSTRAINT UQ_PartyType_Name UNIQUE
);
INSERT dbo.PartyType VALUES ('Person'), ('Business');
Supertype
CREATE TABLE dbo.Party (
PartyID int identity(1,1) NOT NULL CONSTRAINT PK_Party PRIMARY KEY CLUSTERED,
FullName varchar(64) NOT NULL,
BeginDate smalldatetime, -- DOB for people or creation date for business
PartyTypeID int NOT NULL
CONSTRAINT FK_Party_PartyTypeID FOREIGN KEY REFERENCES dbo.PartyType (PartyTypeID)
);
подтипы
Затем, если есть столбцы, которые являются уникальными для человека, создайте таблицу Person
, содержащую только эти столбцы:
CREATE TABLE dbo.Person (
PersonPartyID int NOT NULL
CONSTRAINT PK_Person PRIMARY KEY CLUSTERED
CONSTRAINT FK_Person_PersonPartyID FOREIGN KEY REFERENCES dbo.Party (PartyID)
ON DELETE CASCADE,
-- add columns unique to people
);
А если есть столбцы, которые являются уникальными для предприятий, создайте таблицу Business
, содержащую только эти столбцы:
CREATE TABLE dbo.Business (
BusinessPartyID int NOT NULL
CONSTRAINT PK_Business PRIMARY KEY CLUSTERED
CONSTRAINT FK_Business_BusinessPartyID FOREIGN KEY REFERENCES dbo.Party (PartyID)
ON DELETE CASCADE,
-- add columns unique to businesses
);
Использование и примечания
Наконец, ваша таблица Asset
будет выглядеть примерно так:
CREATE TABLE dbo.Asset (
AssetID int NOT NULL identity(1,1) CONSTRAINT PK_Asset PRIMARY KEY CLUSTERED,
PartyID int NOT NULL
CONSTRAINT FK_Asset_PartyID FOREIGN KEY REFERENCES dbo.Party (PartyID),
AssetTag varchar(64) CONSTRAINT UQ_Asset_AssetTag UNIQUE
);
Отношение, которое разделяет таблица сторон супертипа с таблицами подтипов Business и Person, - «один к нулю или один». Теперь, хотя подтипы обычно не имеют соответствующей строки в другой таблице, в этой схеме есть возможность иметь Сторону, которая попадает в обе таблицы. Тем не менее, вам действительно может понравиться это: иногда человек и бизнес почти взаимозаменяемы. Если это бесполезно, в то время как триггер для реализации этого будет довольно легко сделать, лучшее решение, вероятно, состоит в том, чтобы добавить столбец PartyTypeID
в таблицы подтипов, сделав его частью PK & FK, и наложить ограничение CHECK на PartyTypeID
.
Прелесть этой модели в том, что когда вы хотите создать столбец, который имеет ограничение для бизнеса или человека, вы устанавливаете ограничение для соответствующей таблицы вместо таблицы партии.
Кроме того, при желании может быть полезно включить каскадное удаление ограничений, а также триггер INSTEAD OF DELETE
для таблиц подтипов, который вместо этого удаляет соответствующие идентификаторы из таблицы супертипа (это гарантирует отсутствие строк супертипа, которые не имеют подтип строк присутствует). Эти запросы очень просты и работают на уровне всей строки-существует-или-не существует, что, на мой взгляд, является гигантским улучшением по сравнению с любой схемой, требующей проверки согласованности значений столбцов.
Также обратите внимание, что во многих случаях столбцы, которые, по вашему мнению, должны находиться в одной из таблиц подтипов, действительно могут быть объединены в таблицу супертипов, например номер социального страхования. Назовите его TIN (идентификационный номер налогоплательщика), и он подходит как для бизнеса, так и для людей.
Имя столбца ID
Вопрос о том, вызывать или нет столбец в таблице Person PartyID
, PersonID
или PersonPartyID
, является вашим собственным предпочтением, но я думаю, что лучше всего назвать эти PersonPartyID
или BusinessPartyID
- Принимая во внимание стоимость более длинного имени, это позволяет избежать двух типов путаницы. Например, кто-то, кто не знаком с базой данных, видит BusinessID
и не знает, что это PartyID
, или видит PartyID
и не знает, что он ограничен внешним ключом только теми, которые находятся в таблице Business
.
Если вы хотите создать представления для таблиц Party
и Business
, они могут даже быть материализованными представлениями, поскольку это простое внутреннее соединение, и там вы можете переименовать столбец PersonPartyID
в PersonID
если бы вы были действительно так склонны (хотя я бы не стал). Если это имеет для вас большое значение, вы даже можете сделать триггеры INSTEAD OF INSERT
и INSTEAD OF UPDATE
для этих представлений, чтобы обрабатывать вставки в эти две таблицы для вас, чтобы представления выглядели совершенно как их собственные таблицы для многих прикладных программ.
Создание предложенного проекта как есть
Кроме того, я не хочу упоминать об этом, но если вы хотите иметь ограничение в предложенном проекте, который обеспечивает заполнение только одного столбца, вот код для этого:
ALTER TABLE dbo.Assets
ADD CONSTRAINT CK_Asset_PersonOrBusiness CHECK (
CASE WHEN PersonID IS NULL THEN 0 ELSE 1 END
+ CASE WHEN BusinessID IS NULL THEN 0 ELSE 1 END = 1
);
Однако я не рекомендую это решение.
Заключительные мысли
Естественным третьим подтипом, который нужно добавить, является организация, в смысле чего-то, в чем могут участвовать люди и компании. Супертип и подтип также элегантно решают проблемы клиента / сотрудника, клиента / продавца и другие проблемы, аналогичные представленной вами.
Будьте осторожны, чтобы не перепутать "Is-A" с "Acts-As-A". Вы можете сказать, что сторона является клиентом, просмотрев таблицу заказов или посчитав количество заказов, и вам может вообще не понадобиться таблица клиентов. Также не путайте идентичность с жизненным циклом: арендованный автомобиль в конечном итоге может быть продан, но это прогресс в жизненном цикле, и его следует обрабатывать с помощью данных столбца, а не наличия таблицы - автомобиль не запускается как RentalCar
и превращается в ForSaleCar
позже, это все время машина. Или, может быть, RentalItem
, возможно, бизнес арендует и другие вещи. Вы поняли.
Может даже не быть необходимости иметь таблицу PartyType
. Тип партии можно определить по наличию строки в соответствующей таблице подтипов. Это также позволило бы избежать потенциальной проблемы с PartyTypeID
, не соответствующей наличию таблицы подтипов. Одна из возможных реализаций - сохранить таблицу PartyType
, но удалить PartyTypeID
из таблицы Party, а затем в представлении таблицы Party вернуть правильный PartyTypeID
в зависимости от того, какая таблица подтипа имеет соответствующую строку. Это не сработает, если вы разрешите сторонам использовать оба подтипа. Тогда вы просто придерживаетесь представлений подтипа и знаете, что одинаковые значения BusinessID
и PersonID
относятся к одной и той же стороне.
Дальнейшее чтение по этому шаблону
См. Универсальная модель данных о личности и организации для более полной и теоретической обработки.
Недавно я обнаружил, что следующие статьи полезны для описания некоторых альтернативных подходов к моделированию наследования в базе данных. Хотя это специфично для инструмента Microsoft Entity Framework ORM, нет причин, по которым вы не могли бы реализовать их самостоятельно при разработке БД:
P.S. Я неоднократно менял свое мнение о присвоении имен столбцам идентификаторов в таблицах подтипов из-за того, что у меня больше опыта.