Согласно вашему описанию рассматриваемой бизнес-среды, существует структура супертип-подтип, которая включает Item - супертип - и каждую из его категорий , т. Е. Автомобиль , катер и самолет (наряду с еще двумя, которые не были известны) - подтипы
Я опишу ниже метод, которым я буду следовать, чтобы управлять таким сценарием.
Бизнес правила
Чтобы начать разграничение соответствующей концептуальной схемы, некоторые из наиболее важных бизнес-правил, определенных до сих пор (ограничив анализ только тремя раскрытыми категориями , чтобы сделать вещи как можно более краткими), можно сформулировать следующим образом:
- Пользователь имеет нулевой один или много- товары
- Пункт принадлежит точно-один пользователь в определенный момент времени
- Пункт может принадлежать один-ко-многим пользователям в различных точках во времени
- Пункт классифицируется ровно одной категории
- Пункт есть, во все времена,
- либо автомобиль
- или лодка
- или самолет
Иллюстративная схема IDEF1X
На рисунке 1 показана диаграмма IDEF1X 1 , которую я создал для группировки предыдущих формулировок вместе с другими бизнес-правилами, которые кажутся уместными:
Supertype
С одной стороны, Item , супертип, представляет свойства † или атрибуты, которые являются общими для всех категорий , т. Е.
- CategoryCode -specified в качестве внешнего ключа (FK) , что ссылки Category.CategoryCode и функции в качестве подтипа дискриминатора , то есть, это указывает на точную категорию подтипа , с которой данный Пункт должен быть визит-,
- OwnerId - выделяется как FK, который указывает на User.UserId , но я назначил ему имя роли 2 , чтобы точнее отразить его особые последствия -,
- Фу ,
- Бар ,
- Баз и
- CreatedDateTime .
Подтипы
С другой стороны, свойства ‡, которые относятся к каждой конкретной категории , т. Е.
- Qux и Corge ;
- Grault , Garply и Plugh ;
- Xyzzy , Thud , Wibble и Flob ;
показаны в соответствующем поле подтипа.
Идентификаторы
Затем первичный ключ Item.ItemId (PK) перенес 3 в подтипы с разными именами ролей, т.е.
- CarId ,
- BoatId и
- PlaneId .
Взаимоисключающие ассоциации
Как изображено, существует связь или взаимосвязь кардинальности один к одному (1: 1) между (a) каждым вхождением супертипа и (b) его дополнительным экземпляром подтипа.
Эксклюзивный подтип символ изображает тот факт , что подтипы являются взаимоисключающими, т.е. конкретный Item явление может быть дополнен только одним экземпляром подтипа: либо один автомобиль , или один Plane , или одна лодка (никогда на два или более).
† , ‡ Я использовал классические имена местозаполнителей для обозначения некоторых свойств типа сущности, поскольку их фактические наименования не были указаны в вопросе.
Описательное расположение логического уровня
Следовательно, чтобы обсудить объяснительный логический дизайн, я вывел следующие операторы SQL-DDL на основе отображенной и описанной выше диаграммы IDEF1X:
-- You should determine which are the most fitting
-- data types and sizes for all your table columns
-- depending on your business context characteristics.
-- Also, you should make accurate tests to define the
-- most convenient INDEX strategies based on the exact
-- data manipulation tendencies of your business context.
-- As one would expect, you are free to utilize
-- your preferred (or required) naming conventions.
CREATE TABLE UserProfile (
UserId INT NOT NULL,
FirstName CHAR(30) NOT NULL,
LastName CHAR(30) NOT NULL,
BirthDate DATE NOT NULL,
GenderCode CHAR(3) NOT NULL,
Username CHAR(20) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT UserProfile_PK PRIMARY KEY (UserId),
CONSTRAINT UserProfile_AK1 UNIQUE ( -- Composite ALTERNATE KEY.
FirstName,
LastName,
GenderCode,
BirthDate
),
CONSTRAINT UserProfile_AK2 UNIQUE (Username) -- ALTERNATE KEY.
);
CREATE TABLE Category (
CategoryCode CHAR(1) NOT NULL, -- Meant to contain meaningful, short and stable values, e.g.; 'C' for 'Car'; 'B' for 'Boat'; 'P' for 'Plane'.
Name CHAR(30) NOT NULL,
--
CONSTRAINT Category_PK PRIMARY KEY (CategoryCode),
CONSTRAINT Category_AK UNIQUE (Name) -- ALTERNATE KEY.
);
CREATE TABLE Item ( -- Stands for the supertype.
ItemId INT NOT NULL,
OwnerId INT NOT NULL,
CategoryCode CHAR(1) NOT NULL, -- Denotes the subtype discriminator.
Foo CHAR(30) NOT NULL,
Bar CHAR(30) NOT NULL,
Baz CHAR(30) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Item_PK PRIMARY KEY (ItemId),
CONSTRAINT Item_to_Category_FK FOREIGN KEY (CategoryCode)
REFERENCES Category (CategoryCode),
CONSTRAINT Item_to_User_FK FOREIGN KEY (OwnerId)
REFERENCES UserProfile (UserId)
);
CREATE TABLE Car ( -- Represents one of the subtypes.
CarId INT NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Qux CHAR(30) NOT NULL,
Corge CHAR(30) NOT NULL,
--
CONSTRAINT Car_PK PRIMARY KEY (CarId),
CONSTRAINT Car_to_Item_FK FOREIGN KEY (CarId)
REFERENCES Item (ItemId)
);
CREATE TABLE Boat ( -- Stands for one of the subtypes.
BoatId INT NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Grault CHAR(30) NOT NULL,
Garply CHAR(30) NOT NULL,
Plugh CHAR(30) NOT NULL,
--
CONSTRAINT Boat_PK PRIMARY KEY (BoatId),
CONSTRAINT Boat_to_Item_FK FOREIGN KEY (BoatId)
REFERENCES Item (ItemId)
);
CREATE TABLE Plane ( -- Denotes one of the subtypes.
PlaneId INT NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Xyzzy CHAR(30) NOT NULL,
Thud CHAR(30) NOT NULL,
Wibble CHAR(30) NOT NULL,
Flob CHAR(30) NOT NULL,
--
CONSTRAINT Plane_PK PRIMARY KEY (PlaneId),
CONSTRAINT Plane_to_Item_PK FOREIGN KEY (PlaneId)
REFERENCES Item (ItemId)
);
Как продемонстрировано, тип сверхчувствительности и каждый из типов подчиненности представлены соответствующей базовой таблицей.
Столбцы CarId
, BoatId
и PlaneId
, сдерживаются как ПКС соответствующих таблиц, помощь в представлении концептуального уровня один-к-одному ассоциации путем ограничения FK § , что указывает на ItemId
колонку, которая стесненного как PK в Item
таблице. Это означает, что в фактической «паре» строки как супертипа, так и подтипа идентифицируются одним и тем же значением PK; таким образом, более чем уместно упомянуть, что
- (а) прикрепление дополнительной колонки , чтобы держать под контролем система-суррогатных значения ‖ к (б) таблицам , стоящие для подтипов (с) полностью излишним .
§ Чтобы предотвратить проблемы и ошибки, касающиеся (в частности, FOREIGN) определений ограничений KEY - ситуации, на которую вы ссылались в комментариях, - очень важно учитывать зависимость существования, которая имеет место между различными таблицами, как показано в примере порядок объявления таблиц в описательной структуре DDL, который я также предоставил в этой скрипте SQL .
‖ Например, добавление дополнительного столбца со свойством AUTO_INCREMENT к таблице базы данных, построенной на MySQL.
Целостность и последовательность соображений
Важно отметить, что в вашей бизнес-среде вы должны (1) убедиться, что каждая строка «супертипа» всегда дополняется соответствующим аналогом «подтипа», и, в свою очередь, (2) гарантировать, что Строка «подтип» совместима со значением, содержащимся в столбце «дискриминатор» строки «супертип».
Было бы очень элегантно применять такие обстоятельства декларативным образом, но, к сожалению, ни одна из основных платформ SQL, насколько я знаю, не предоставила надлежащих механизмов для этого. Поэтому, прибегая к процедурному коду в рамках ACID TRANSACTIONS, довольно удобно, чтобы эти условия всегда выполнялись в вашей базе данных. Другим вариантом будет использование TRIGGERS, но они, как говорится, делают вещи неопрятными.
Объявление полезных просмотров
Имея логический дизайн, подобный описанному выше, было бы очень полезно создать одно или несколько представлений, то есть производных таблиц, которые содержат столбцы, которые принадлежат двум или более соответствующим базовым таблицам. Таким образом, вы можете, например, ВЫБРАТЬ напрямую из этих представлений без необходимости записывать все СОЕДИНЕНИЯ каждый раз, когда вам нужно получить «объединенную» информацию.
Пример данных
В этом отношении предположим, что базовые таблицы «заполнены» приведенными ниже примерами данных:
--
INSERT INTO UserProfile
(UserId, FirstName, LastName, BirthDate, GenderCode, Username, CreatedDateTime)
VALUES
(1, 'Edgar', 'Codd', '1923-08-19', 'M', 'ted.codd', CURDATE()),
(2, 'Michelangelo', 'Buonarroti', '1475-03-06', 'M', 'michelangelo', CURDATE()),
(3, 'Diego', 'Velázquez', '1599-06-06', 'M', 'd.velazquez', CURDATE());
INSERT INTO Category
(CategoryCode, Name)
VALUES
('C', 'Car'), ('B', 'Boat'), ('P', 'Plane');
-- 1. ‘Full’ Car INSERTion
-- 1.1
INSERT INTO Item
(ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
(1, 1, 'C', 'This datum', 'That datum', 'Other datum', CURDATE());
-- 1.2
INSERT INTO Car
(CarId, Qux, Corge)
VALUES
(1, 'Fantastic Car', 'Powerful engine pre-update!');
-- 2. ‘Full’ Boat INSERTion
-- 2.1
INSERT INTO Item
(ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
(2, 2, 'B', 'This datum', 'That datum', 'Other datum', CURDATE());
-- 2.2
INSERT INTO Boat
(BoatId, Grault, Garply, Plugh)
VALUES
(2, 'Excellent boat', 'Use it to sail', 'Everyday!');
-- 3 ‘Full’ Plane INSERTion
-- 3.1
INSERT INTO Item
(ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
(3, 3, 'P', 'This datum', 'That datum', 'Other datum', CURDATE());
-- 3.2
INSERT INTO Plane
(PlaneId, Xyzzy, Thud, Wibble, Flob)
VALUES
(3, 'Extraordinary plane', 'Traverses the sky', 'Free', 'Like a bird!');
--
Тогда предпочтительным видом является тот, который собирает столбцы из Item
, Car
и UserProfile
:
--
CREATE VIEW CarAndOwner AS
SELECT C.CarId,
I.Foo,
I.Bar,
I.Baz,
C.Qux,
C.Corge,
U.FirstName AS OwnerFirstName,
U.LastName AS OwnerLastName
FROM Item I
JOIN Car C
ON C.CarId = I.ItemId
JOIN UserProfile U
ON U.UserId = I.OwnerId;
--
Естественно, можно использовать аналогичный подход, так что вы также можете ВЫБРАТЬ «полную» Boat
и Plane
информацию прямо из одной таблицы (производной, в этих случаях).
После этого -Если вы не возражаете о наличии NULL знаков в результате sets- со следующим определением VIEW, вы можете, например, «собирать» столбцы из таблиц Item
, Car
, Boat
, Plane
и UserProfile
:
--
CREATE VIEW FullItemAndOwner AS
SELECT I.ItemId,
I.Foo, -- Common to all Categories.
I.Bar, -- Common to all Categories.
I.Baz, -- Common to all Categories.
IC.Name AS Category,
C.Qux, -- Applies to Cars only.
C.Corge, -- Applies to Cars only.
--
B.Grault, -- Applies to Boats only.
B.Garply, -- Applies to Boats only.
B.Plugh, -- Applies to Boats only.
--
P.Xyzzy, -- Applies to Planes only.
P.Thud, -- Applies to Planes only.
P.Wibble, -- Applies to Planes only.
P.Flob, -- Applies to Planes only.
U.FirstName AS OwnerFirstName,
U.LastName AS OwnerLastName
FROM Item I
JOIN Category IC
ON I.CategoryCode = IC.CategoryCode
LEFT JOIN Car C
ON C.CarId = I.ItemId
LEFT JOIN Boat B
ON B.BoatId = I.ItemId
LEFT JOIN Plane P
ON P.PlaneId = I.ItemId
JOIN UserProfile U
ON U.UserId = I.OwnerId;
--
Код представленных здесь представлений является только иллюстративным. Конечно, выполнение некоторых тестовых упражнений и модификаций может помочь ускорить (физическое) выполнение запросов. Кроме того, вам может потребоваться удалить или добавить столбцы к указанным представлениям в соответствии с потребностями бизнеса.
Образцы данных и все определения представлений включены в эту скрипту SQL, чтобы их можно было наблюдать «в действии».
Обработка данных: псевдонимы кода и столбцов прикладных программ
Использование кода прикладной программы (программ) (если вы это подразумеваете под «специфичным для сервера кодом») и псевдонимы столбцов - это другие важные моменты, которые вы затронули в следующих комментариях:
Мне удалось обойти проблему [JOIN] с серверным кодом, но я действительно не хочу этого делать - И добавление псевдонимов ко всем столбцам может быть «напряженным».
Очень хорошо объяснил, большое спасибо. Однако, как я и подозревал, мне придется манипулировать результирующим набором при перечислении всех данных из-за сходства с некоторыми столбцами, так как я не хочу использовать несколько псевдонимов для поддержания чистоты оператора.
Уместно указать, что, хотя использование прикладного программного кода является очень подходящим ресурсом для обработки особенностей представления (или графических) наборов результатов, исключение извлечения данных построчно является первостепенным для предотвращения проблем со скоростью выполнения. Задача должна состоять в том, чтобы «извлечь» соответствующие наборы данных в совокупности с помощью надежных инструментов манипулирования данными, предоставляемых (точно) механизмом установки платформы SQL, чтобы вы могли оптимизировать поведение вашей системы.
Кроме того, использование псевдонимов для переименования одного или нескольких столбцов в определенной области может показаться напряженным, но лично я считаю такой ресурс очень мощным инструментом, который помогает (i) контекстуализировать и (ii) устранять неоднозначность значения и намерения, приписываемых колонны; следовательно, это аспект, который следует тщательно обдумать в отношении манипулирования интересующими данными.
Подобные сценарии
Вам также может пригодиться эта серия постов и эта группа постов, в которых содержится мой взгляд на два других случая, которые включают ассоциации супертип-подтип со взаимоисключающими подтипами.
Я также предложил решение для бизнес-среды, включающее кластер супертип-подтип, где подтипы не являются взаимоисключающими в этом (более новом) ответе .
Сноски
1 Определение интеграции для информационного моделирования ( IDEF1X ) - это очень рекомендуемый метод моделирования данных, который был установлен в качестве стандарта в декабре 1993 года Национальным институтом стандартов и технологий США (NIST). Оно прочно основано на (а) некоторые из теоретических работавтором которого является единственным оригинатора в реляционной модели , т.е. д - р Ф. Кодда ; (б) взгляд на сущность-отношение , разработанный доктором П.П. Ченом ; а также о (c) методике проектирования логических баз данных, созданной Робертом Г. Брауном.
2 В IDEF1X имя роли - это отличительная метка, назначенная свойству (или атрибуту) FK, чтобы выразить значение, которое оно содержит в области видимости соответствующего типа объекта.
3 Стандарт IDEF1X определяет миграцию ключей как «процесс моделирования размещения первичного ключа родительского или общего объекта в его дочернем объекте или объекте категории в качестве внешнего ключа».
Item
таблица содержитCategoryCode
столбец. Как упомянуто в разделе, озаглавленном «Соображения целостности и согласованности»:CategoryColumn
таблицы, обозначающие подтипы (со всеми последствиями для логических [например, аномалий модификации] и физических уровней абстракции [например, дополнительные индексы, большие структуры и т. д.]).Позволяет назвать основной стол Продукты. Здесь размещены общие атрибуты. Тогда давайте скажем, что у нас есть Стол для автомобилей, Стол для самолетов и Стол для лодок. Эти три таблицы будут иметь ключ ProductID с ограничением FK в строке идентификатора таблицы Product. Если вы хотите их всех - присоединяйтесь к ним. Если вам нужны только автомобили, присоединяйтесь слева к Cars with Products (или объединяйте товары и автомобили справа, но я предпочитаю всегда использовать левосторонние объединения).
Это называется иерархической моделью данных. Для небольшого количества вложенных таблиц это может иметь смысл в длинной таблице (миллионы продуктов).
источник