Архитектура SQL. Является ли это оправданным случаем, когда в одной таблице хранится несколько типов сущностей?(используя само присоединение) - PullRequest
5 голосов
/ 30 сентября 2010

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

ВОПРОС: Могу ли я сделать это и при этом иметь «звуковую» архитектуру?

Пример следует

Предположим, два типа сущностей, корпорация и личность. Корпорация обычно принадлежит одному человеку, но иногда другая корпорация владеет корпорацией.

Удерживая эту мысль и добавляя к ней, скажем, к каждой корпорации прикреплен зарегистрированный агент, который отвечает за юридическое создание корпорации. Следуя моей иллюстрации, зарегистрированным агентом может быть либо физическое лицо, либо корпорация.

Если вы считаете, что владельцем [родителем] корпорации [ребенка] может быть либо физическое лицо, либо корпорация, вы можете начать видеть проблему в сохранении третьей нормальной формы и во избежание избыточности.

В отличие от моего примера, если бы только люди могли владеть корпорациями, таблица ссылок на владение очень условна и имеет столбцы: OwnershipID (вроде ненужный), CorporationID, PersonID.

Вместо этого вам нужно что-то вроде: OwnershipID, CorporationID, OwnerID, OwnerEntityType (корпоративное или физическое лицо). Не поймите меня неправильно, вы можете сделать эту работу, но это будет не очень весело, если не сказать больше.

Продолжая приведенный мной пример, вам нужно назначить агента для каждой корпорации. Обычно агент является одним из владельцев (человек). В этом случае вы действительно хотите вернуться к одной записи этого человека. Вы не хотите записывать человека как владельца, а затем как агента (в таблице агентов). Это было бы излишним.

Подобно этой «проблеме», зарегистрированный агент также может быть корпорацией, такой как юридическая фирма, CPA или коммерческая компания, чтобы назвать некоторые типичные примеры. Точно так же, как агент-агент, корпорация-агент действительно не должна получать свою собственную запись как агент-субъект. Вместо этого он должен вернуться к своему корпоративному существованию в таблице Корпорации. [за исключением того, что я в конечном итоге говорю, что у вас нет таблицы CorporationEntity]

Точно так же, как таблица ссылок, которая сопоставляет каждую корпорацию с ее владельцем (ами) любого типа, лицом или корпорацией, у вас может быть таблица ссылок агента: AgentRepresentationID, CorporationID, AgentID, AgentType ... но, опять же, это будет будьте уродливы (IMO), когда вам нужно собрать вместе связанных агентов - некоторые из таблицы Person, некоторые из таблицы Corporation.

Вот почему я говорю, что в подобных случаях вы можете видеть, как нейтральный тип сущности может быть выгодным. Было бы что-то вроде этого:

Таблица: EntityAll
Ключевые столбцы: EntityID, EntityType (или EntityTypeID, если вы настаиваете, укажите ссылку, чтобы получить описание), EntityName (существуют проблемы с именами и различными типами ... не по теме в этом посте)

Таблица ссылок: Корпоративное владение
Ключевые столбцы: OwnershipID (опять же, мой комментарий о том, что это как-то не нужно), ChildEntityID (принадлежащий объект; для ясности назван «Дочерний», я бы не назвал это так) ParentEntityID (родительский объект)

Таблица ссылок: AgentRepresentation
Ключевые столбцы: AgentRepresentationID (... я не скажу), CorporationEntityID (представляемая сущность корпорации), AgentEntityID (из таблицы Entity, приравнивая к записи, которая является агентом здесь)

Хотя вы можете быть в порядке с моей архитектурой, вы должны быть немного обеспокоены именами столбцов в таблицах ссылок. Это не дает мне покоя. Обычно имена второго и третьего столбцов в этих таблицах точно совпадают с именами столбцов, к которым вы ПРИСОЕДИНЯЕТЕСЬ в соответствующей таблице каждого объекта (ха-ха, но у каждого объекта нет соответствующей таблицы, поэтому вы не можете иметь имена столбцов таблицы ссылок, соответствующие имена столбцов источника, потому что они являются одним и тем же столбцом). Технически это не имеет значения, но это нарушит ваши соглашения об именах, которые имеют значение, но не настолько, чтобы этого не делать.

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

Список всех корпусов и их владельцев (в T-SQL):

SELECT Corp.EntityName as CorpName, Owner.EntityName as OwnerName
FROM EntityAll as Corp
JOIN CorporationOwnership as Link on (Corp.EntityID = Link.ChildEntityID)
JOIN EntityAll as Owner on (Link.ParentEntityID = Owner.EntityID)

Следовательно, вы бы сделали то же самое, чтобы вернуть агента вместо владельца (ов).

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

P.S. Я недавно предоставил этот пример в качестве ответа на старый вопрос о SO. Будучи старым вопросом, не было никакого диалога. Мне нужно реализовать этот самый пример, и мне любопытно, каковы будут последствия этой архитектуры.

Вот предыдущий вопрос / ответ: Хороший дизайн таблицы базы данных: одна таблица, смешивающая разные сущности или отдельная таблица для каждой сущности

Ответы [ 3 ]

3 голосов
/ 30 сентября 2010

Я думаю, что это был Хью Дарвен, который придумал термины «распределенный ключ» и «распределенные внешние ключи», где единственное значение ссылочного ключа существует ровно в одном из нескольких ссылающихся relvars (таблиц); это потребовало бы связанной концепции «множественного присваивания», чтобы атомарно вставлять как ссылки, так и ссылки на relvars.

Хотя теоретически это может быть достигнуто в SQL-92 с использованием отложенного уровня схемы ASSERTION s (или, возможно, CHECK ограничений, поддерживающих подзапросы), это скорее неуклюжий процесс, процедурный (а не основанный на множествах) и нет продукта SQL, который когда-либо поддерживал бы эту функциональность (или я буду когда-либо поддерживать).

Лучшее, что мы можем сделать с доступными продуктами SQL, - это использовать составной ключ (entity_ID, entity_type) с ограничением CHECK для entity_type в ссылочных таблицах, чтобы обеспечить наличие не более одного значения ссылочного ключа (обратите внимание, что это не то же самое, что «ровно одно значение ключа ссылки»), например

CREATE TABLE LegalPersons
(
 person_ID INTEGER IDENTITY NOT NULL UNIQUE, 
 person_type VARCHAR(14) NOT NULL
    CHECK (person_type IN ('Company', 'Natural Person')), 
 UNIQUE (person_type, person_ID)
);

CREATE TABLE Companies
(
 person_ID INTEGER NOT NULL UNIQUE, 
 person_type VARCHAR(14) NOT NULL
    CHECK (person_type = 'Company'), 
 FOREIGN KEY (person_type, person_ID)
    REFERENCES LegalPersons (person_type, person_ID), 
 companies_house_registered_number VARCHAR(8) NOT NULL UNIQUE
 -- other company columns and constraints here
);

CREATE TABLE NaturalPersons
(
 person_ID INTEGER NOT NULL UNIQUE, 
 person_type VARCHAR(14) NOT NULL
    CHECK (person_type = 'Natural Person'), 
 FOREIGN KEY (person_type, person_ID)
    REFERENCES LegalPersons (person_type, person_ID) 
 -- natural person columns and constraints here
);

Этот паттерн суперкласса-подкласса очень распространен в SQL.

В идеале имя таблицы должно отражать природу набора в целом. Вам нужно думать не только о названиях других наборов; возможно спросите эксперта в определенной области бизнеса, например. бухгалтер может использовать термин «начисление заработной платы», а не «EmployeesSalaries».

Другой идеал - чтобы имя столбца оставалось неизменным во всей схеме, но при подходе к подклассам вам часто нужно их квалифицировать (и это беспокоит меня!), Например.

CREATE TABLE CompanyAgents
(
 company_person_ID INTEGER NOT NULL UNIQUE, 
 company_person_type VARCHAR(14) NOT NULL
    CHECK (company_person_type = 'Company'), 
 FOREIGN KEY (company_person_type, company_person_ID)
    REFERENCES LegalPersons (person_type, person_ID), 
 agent_person_ID INTEGER NOT NULL, 
 agent_person_type VARCHAR(14) NOT NULL, 
 FOREIGN KEY (agent_person_type, agent_person_ID)
    REFERENCES LegalPersons (person_type, person_ID), 
 CHECK (company_person_ID <> agent_person_ID)
);

Примечание. Я бы использовал один столбец для agent_person_ID, например.

 agent_person_ID INTEGER NOT NULL
    REFERENCES LegalPersons (person_ID)

потому что нет ограничений на тип объекта. В принципе, я чувствую себя лучше при сохранении составного ключа из двух столбцов для всех ссылок по всей схеме, и на практике я нахожу так часто, как мне не нужно, теперь, так или иначе, тип сущности, поэтому этот SQL DDL сохраняет JOIN в SQL DML: )

2 голосов
/ 30 сентября 2010

Посмотрите "Обобщение специализации реляционного моделирования".

Я думаю, что есть тип сущности, которую я назову "юридическим лицом". То, что вы назвали «лицом», а некоторые могут назвать «физическим лицом», является специализированным видом юридического лица. То, что вы назвали «корпорацией», а кого-то, возможно, называют «инкорпорированным лицом», является специализированным юридическим лицом другого рода.

С этой точки зрения отношения между «юридическими лицами», «лицами» и «корпорациями» можно рассматривать как ген-спецификацию (обобщение-специализация). В учебниках по моделированию объектов подробно рассматривается gen-spec, и он вполне естественно вписывается в концепцию наследования. gen-spec часто упоминается в руководствах по реляционному моделированию. Но концепция хорошо понятна.

Юридическое лицо может владеть корпорацией независимо от того, какой специализированный тип юридического лица.

Ваша таблица ENTITYALL соответствует некоторым особенностям реляционного дизайна ген-спецификации, но вы можете разработать модель дальше. В частности, если у нас есть тип сущности «автомобиль», нет особой причины, по которой он не получил бы запись в таблице ENTITYALL. Но тот факт, что автомобиль не может владеть корпорацией, теперь скрыт. Я хотел бы иметь какую-то таблицу, которая обобщает «лицо» и «корпорация» в «юридическое лицо», но не настолько распространена, чтобы «автомобиль» классифицировался как «юридическое лицо». ENTITYALL слишком универсален для моих предпочтений.

Рассматривая лучшие примеры gen-spec, мы видим, что первичный ключ для gen-таблицы и первичный ключ для каждой spec-таблицы все взяты из одного домена. Кроме того, первичный ключ таблиц спецификаций действует как ссылка внешнего ключа на таблицу генов, в дополнение к поддержанию целостности объекта для специализированного объекта. Соединения получаются очень хорошими. Ваша схема может извлечь выгоду из этого бита.

1 голос
/ 30 сентября 2010

Имея в виду, что корпорации имеют права людей, и что оба предполагают родительские отношения какого-либо формата ... Если бизнес-правило таково, что может быть только one parent, вы можете использовать независимый внешний ключ для указания родителя.Следующее легче объяснить, если Корпорация и физическое лицо считаются одним и тем же лицом:

Таблица ENTITY

  • entity_id (первичный ключ)
  • entity_type_code (внешний ключ к ENTITY_TYPE_CODE.entity_type_code, содержащий "лицо" и "корпорация" и т. д.)
  • parent_entity_id (отношение внешнего ключа с ENTITY.entity_id, допускает нулевое значение)

Theparent_entity_id также должен иметь значение NULL, поскольку NULL указывает корневой объект для иерархии.Но это также означает использование базы данных, которая имеет поддержку иерархических запросов (IE: не MySQL).

Таблица OWNERSHIP

Таблица OWNERSHIP будет подходить, если бизнес-правила должныподдерживать более одного родителя для объекта или наоборот.Существует множество имен, но это таблица исключительно для поддержки отношений «многие ко многим».Вы правы, это идеальный подход , если бизнес-правила требуют этого.

Заключение

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

...