Проектирование условных отношений базы данных в SQL Server - PullRequest
7 голосов
/ 22 января 2010

У меня есть три основных типа организаций: люди, бизнес и активы. Каждый актив может принадлежать одному и только одному лицу или бизнесу. Каждый человек и бизнес может иметь от 0 до нескольких активов. Каков наилучший способ хранения условных отношений этого типа в Microsoft SQL Server?

Мой первоначальный план состоит в том, чтобы в таблице «Активы» было два внешних ключа, допускающих обнуление, один для сотрудников и один для предприятий. Одно из этих значений будет нулевым, а другое будет указывать на владельца. Проблема, которую я вижу с этой настройкой, состоит в том, что для ее интерпретации и применения требуется логика приложения. Это действительно лучшее из возможных решений или есть другие варианты?

Ответы [ 7 ]

8 голосов
/ 22 января 2010

Представляем супертипы и подтипы

Я предлагаю вам использовать супертипы и подтипы. Сначала создайте 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. Я неоднократно менял свое мнение о присвоении имен столбцам идентификаторов в таблицах подтипов из-за того, что у меня больше опыта.

2 голосов
/ 22 января 2010

Вам не нужна логика приложения для обеспечения этого. Самый простой способ - с проверочным ограничением :

(PeopleID is null and BusinessID is not null) or (PeopleID is not null and BusinessID is null)
1 голос
/ 07 июля 2016

Ответ ErikE дает хорошее объяснение о том, как поступить в отношении отношений супертип / подтип в таблицах, и, скорее всего, я бы пошел в вашей ситуации, однако на самом деле он не решает вопрос (s) вы задали, что также интересно, а именно:

  1. Что было бы лучшим способом хранения условных отношений этого типа в Microsoft SQL Server?
  2. ... есть другие варианты?

Для тех, кто рекомендует эту запись в блоге на TechTarget , в которой есть выдержка из выдержки из «Руководства разработчика по моделированию данных для SQL Server, охватывающих SQL Server 2005 и 2008» Эрика Джонсона и Джошуа Джонса, который адреса 3 возможных варианта.

В итоге это:

  1. Таблица супертипов - Почти совпадает с тем, что вы предложили, есть таблица с одними полями, которые всегда будут нулевыми, когда заполнятся другие. Хорошо, когда только несколько полей не являются общими. Таким образом, в зависимости от того, насколько различны люди и бизнес, вы можете объединить их в одну таблицу, возможно, Владельцы, а затем просто указать OwnerID в своей таблице активов.
  2. Таблицы подтипов - По сути, это противоположность таблицам супертипов и то, что у вас есть только сейчас. Здесь у нас есть много уникальных полей и, может быть, одно или два одинаковых, поэтому у нас просто повторяющиеся поля появляются в каждой таблице. Поскольку вы обнаружите, что это не совсем подходит для вашей ситуации.
  3. Таблицы супертипа и подтипа - Комбинация обоих вышеперечисленных случаев, когда совпадающие поля размещаются в одной таблице, и уникальные в отдельных таблицах и совпадающие идентификаторы используются для объединения записей из одной таблицы к другому. Это соответствует предложенному ErikE решению и, как уже упоминалось, я бы тоже предпочел.

К сожалению, это не объясняет, что, если таковые имеются, являются наилучшей практикой, но это, безусловно, хорошее чтение, чтобы получить представление о возможных вариантах.

1 голос
/ 22 января 2010

У вас может быть другая сущность, от которой человек и бизнес "расширяются". Мы называем это лицо стороной в нашем текущем проекте. И Человек, и Бизнес имеют FK to Party (это отношения). И Актив может также иметь FK для партии (принадлежит к отношениям).

С учетом сказанного, если в будущем актив может совместно использоваться несколькими экземплярами, лучше создать отношения m: n, это дает гибкость, но усложняет логику приложения и запросы немного больше.

0 голосов
/ 12 декабря 2018

Table People, предприятия могут использовать UUID в качестве первичного ключа и объединять оба в представление для целей объединения SQL.

так что вы можете просто использовать один столбец внешнего ключа в отношении активов как для людей, так и для предприятий, поскольку UUID практически уникален. И вы можете просто запросить как:

select * from Assets
join view_People_Businesses as v on v.id = Assets.fk
0 голосов
/ 22 января 2010

Актив будет иметь внешний ключ для владельца, и вам следует настроить таблицу связей для связи активов и предприятий. Как сказано в других комментариях, вы можете использовать триггеры и / или ограничения, чтобы гарантировать, что данные остаются в согласованном состоянии. то есть. при удалении бизнеса удалите строки в таблице сопоставлений.

0 голосов
/ 22 января 2010

Вместо этого вы можете применить логику с помощью триггера. Тогда независимо от того, как будет изменена запись, будет заполнено только одно из полей.

Вы могли бы также иметь таблицу PeopleAsset и таблицу BusinessAsset, но все равно у вас будет проблема с обеспечением того, чтобы только у одного из них была запись.

...