Если я правильно понимаю ваши спецификации, ваш сценарий включает - среди других важных аспектов - структуру подтипа супертипа .
Ниже я приведу пример того, как (1) смоделировать его на концептуальном уровне абстракции и затем (2) представить его в дизайне DDL логического уровня .
Бизнес правила
Следующие концептуальные формулировки являются одними из наиболее важных правил в вашем бизнес-контексте:
- Playlist принадлежит либо ровно одной группы или ровно один пользователь в определенный момент времени
- Playlist может принадлежать один-ко-многим групп или пользователей в различных точках во времени
- Пользователь имеет нулевые один или много- плейлист
- Группы имеют нулевые один или много- плейлист
- Группа состоит из одного ко многим членам (которые должны быть пользователи )
- Пользователь может быть членом нулевого одного или многих- групп .
- Группа состоит из одного ко многим членам (которые должны быть пользователи )
Поскольку ассоциации или отношения (a) между пользователем и списком воспроизведения и (b) между группой и списком воспроизведения довольно похожи, этот факт показывает, что пользователь и группа являются взаимоисключающими подтипами сущностей Стороны 1 , которая, в свою очередь, является их супертипом сущности - superpertype- кластеры подтипов - это классические структуры данных, которые встречаются в концептуальных схемах самых разных видов. Таким образом, можно утверждать два новых правила:
- Партия классифицируется ровно один PartyType
- Сторона является либо группа или пользователь
И четыре из предыдущих правил должны быть переформулированы как только три:
- Playlist принадлежит ровно одна партия в определенный момент времени
- Playlist может принадлежать один-ко-многим Стороны в различных точках во время
- Сторона имеет нулевые один или много- плейлист
Описательная схема IDEF1X
Диаграмма IDEF1X 2 , показанная на рисунке 1, объединяет все вышеупомянутые бизнес-правила наряду с другими, которые кажутся уместными:
Как продемонстрировано, Группа и Пользователь изображаются как подтипы, которые связаны соответствующими линиями и эксклюзивным символом с Стороной , супертипом.
Свойство Party.PartyTypeCode обозначает дискриминатор подтипа , т. Е. Указывает, какой тип экземпляра подтипа должен дополнять данное вхождение супертипа.
Кроме того, Party связан с плейлистом через свойство OwnerId, которое обозначается как КЛЮЧЕВОЙ КЛЮЧ, указывающий на Party.PartyId . Таким образом, Сторона связывает (a) Плейлист с (b) Группой и (c) Пользователем .
Соответственно, поскольку конкретный экземпляр Стороны является либо Группой, либо Пользователем , определенный список воспроизведения может быть связан не более чем с одним вхождением подтипа.
Иллюстративный макет логического уровня
Диаграмма IDEF1X, изложенная ранее, послужила мне платформой для создания следующей логической схемы SQL-DDL (и я предоставил примечания в виде комментариев, выделяющих несколько точек особой актуальности - например, объявления ограничений):
-- 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 domain.
-- As one would expect, you are free to utilize
-- your preferred (or required) naming conventions.
CREATE TABLE PartyType ( -- Represents an independent entity type.
PartyTypeCode CHAR(1) NOT NULL,
Name CHAR(30) NOT NULL,
--
CONSTRAINT PartyType_PK PRIMARY KEY (PartyTypeCode),
CONSTRAINT PartyType_AK UNIQUE (Name)
);
CREATE TABLE Party ( -- Stands for the supertype.
PartyId INT NOT NULL,
PartyTypeCode CHAR(1) NOT NULL, -- Symbolizes the discriminator.
CreatedDateTime TIMESTAMP NOT NULL,
--
CONSTRAINT Party_PK PRIMARY KEY (PartyId),
CONSTRAINT PartyToPartyType_FK FOREIGN KEY (PartyTypeCode)
REFERENCES PartyType (PartyTypeCode)
);
CREATE TABLE UserProfile ( -- Denotes one of the subtypes.
UserId INT NOT NULL, -- To be constrained as both (a) the PRIMARY KEY and (b) a FOREIGN KEY.
UserName CHAR(30) NOT NULL,
FirstName CHAR(30) NOT NULL,
LastName CHAR(30) NOT NULL,
GenderCode CHAR(3) NOT NULL,
BirthDate DATE NOT NULL,
--
CONSTRAINT UserProfile_PK PRIMARY KEY (UserId),
CONSTRAINT UserProfile_AK1 UNIQUE ( -- Multi-column ALTERNATE KEY.
FirstName,
LastName,
GenderCode,
BirthDate
),
CONSTRAINT UserProfile_AK2 UNIQUE (UserName), -- Single-column ALTERNATE KEY.
CONSTRAINT UserProfileToParty_FK FOREIGN KEY (UserId)
REFERENCES Party (PartyId)
);
CREATE TABLE MyGroup ( -- Represents the other subtype.
GroupId INT NOT NULL, -- To be constrained as both (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Title CHAR(30) NOT NULL,
--
CONSTRAINT Group_PK PRIMARY KEY (GroupId),
CONSTRAINT Group_AK UNIQUE (Title), -- ALTERNATE KEY.
CONSTRAINT GroupToParty_FK FOREIGN KEY (GroupId)
REFERENCES Party (PartyId)
);
CREATE TABLE Playlist ( -- Stands for an independent entity type.
PlaylistId INT NOT NULL,
OwnerId INT NOT NULL,
Title CHAR(30) NOT NULL,
CreatedDateTime TIMESTAMP NOT NULL,
--
CONSTRAINT Playlist_PK PRIMARY KEY (PlaylistId),
CONSTRAINT Playlist_AK UNIQUE (Title), -- ALTERNATE KEY.
CONSTRAINT PartyToParty_FK FOREIGN KEY (OwnerId) -- Establishes the relationship with (a) the supertype and (b) through the subtype with (c) the subtypes.
REFERENCES Party (PartyId)
);
CREATE TABLE GroupMember ( -- Denotes an associative entity type.
MemberId INT NOT NULL,
GroupId INT NOT NULL,
IsOwner BOOLEAN NOT NULL,
JoinedDateTime TIMESTAMP NOT NULL,
--
CONSTRAINT GroupMember_PK PRIMARY KEY (MemberId, GroupId), -- Composite PRIMARY KEY.
CONSTRAINT GroupMemberToUserProfile_FK FOREIGN KEY (MemberId)
REFERENCES UserProfile (UserId),
CONSTRAINT GroupMemberToMyGroup_FK FOREIGN KEY (GroupId)
REFERENCES MyGroup (GroupId)
);
Конечно, вы можете внести одну или несколько корректировок, чтобы все характеристики вашего бизнес-контекста были представлены с необходимой точностью в реальной базе данных.
Примечание : я протестировал вышеупомянутую логическую схему на этой db <> fiddle, а также на этой SQL Fiddle , обе «работают» на PostgreSQL 9.6, так что вы можете видеть их «в действии».
Слизняки
Как видите, я не включил Group.Slug
ни Playlist.Slug
столбцы в объявления DDL. Это так, потому что, в соответствии с вашим следующим объяснением
Это slug
уникальные, строчные, дефисные версии соответствующих им сущностей title
. Например, a group
с title
'Test Group' будет иметь slug
'test-group'. Дубликаты добавляются с инкрементными целыми числами. Это изменило бы в любое время их title
изменения. Я полагаю, это означает, что они не будут делать отличные первичные ключи? Да slugs
и usernames
уникальны в своих соответствующих таблицах.
можно сделать вывод, что их значения являются выводными (т. е. они должны быть вычислены или вычислены в терминах соответствующих значений Group.Title
и Playlist.Title
значений, иногда в сочетании с - я предполагаю, какой-то сгенерированной системой - INTEGER), поэтому я бы не объявлял указанные столбцы в любой из базовых таблиц, так как они будут вводить нарушения обновления.
Напротив, я бы произвел Slugs
может быть, в представлении , которое (a) включает в себя вывод таких значений в виртуальных столбцах и (b) может использоваться непосредственно в дальнейших операциях SELECT - добавление части INTEGER может быть получено, например, путем объединения значения (1) Playlist.OwnerId
с (2) промежуточными дефисами и (3) значению Playlist.Title
;
или, в силу кода прикладной программы, имитирующего описанный ранее подход (возможно, процедурный), когда соответствующие наборы данных извлекаются и форматируются для интерпретации конечным пользователем.
Таким образом, любой из этих двух методов позволили бы избежать «механизма синхронизации обновлений» , которые должны быть помещены в месте тогда и только тогдаSlugs
сохраняется в столбцах базовых таблиц.
Целостность и последовательность соображений
Очень важно отметить , что (я) каждая Party
строка должна быть дополнена в любое время с помощью (II) соответствующего аналога в точности одного из столов , стоящих на подтипы, которые (III) должны «соответствовать» значение , содержащееся в Party.PartyTypeCode
столбце Обозначение дискриминатора.
Было бы весьма выгодно применять такую ситуацию декларативным образом, но ни одна из основных систем управления базами данных SQL (включая Postgres) не предоставила необходимых инструментов для такого действия; следовательно, написание процедурного кода в ACID TRANSACTIONS пока является наилучшим вариантом, чтобы гарантировать, что ранее описанные обстоятельства всегда встречаются в вашей базе данных. Другой возможностью было бы прибегнуть к ТРИГГЕРС, но они склонны делать вещи неаккуратными, так сказать.
Сопоставимые случаи
Если вы хотите установить некоторые аналогии, вам может быть интересно взглянуть на мои ответы на (более новые) вопросы, озаглавленные
так как сопоставимые сценарии обсуждаются.
Сноски
1 Сторона - это термин, используемый в правовом контексте, когда он относится к какому-либо лицу или группе лиц, которые составляют единое целое , поэтому данное наименование подходит для представления концепций пользователя и группы в отношении рассматриваемой бизнес-среды.
2 Определение интеграции для информационного моделирования ( IDEF1X ) - это очень рекомендуемый метод моделирования данных, который был установлен в качестве стандарта в декабре 1993 года Национальным институтом стандартов и технологий США (NIST). Он основывается на (а) некоторых ранних теоретических работах, написанных единственным создателем реляционной модели , т. Е. Доктором Е. Ф. Коддом ; (б) взгляд на сущность-отношение , разработанный доктором П.П. Ченом ; а также о (c) методике проектирования логических баз данных, созданной Робертом Г. Брауном.
Party
строка должна быть дополнена в любое время с помощью (II) соответствующего аналога в точности одной из таблиц , стоящих на подтипы, которые (III) должны„соответствовать“значение , содержащееся вParty.PartyTypeCode
колонка «обозначение дискриминатора ».