Схема базы данных для сущностей с двумя возможными типами владелец / родитель?


8

Я использую PostgreSQL с Sequelize в качестве ORM.

У меня есть один тип User. Второй тип Group, который может иметь любое количество пользователей, связанных с ним через GroupMembershipsтаблицу. Users также может иметь любое количество Groups.

Мой третий тип, Playlistможет принадлежать либо к UserИЛИ group. Каков наилучший способ разработки схемы для этого типа, чтобы он мог иметь либо один тип владельца, либо один из них?

Моим первым проходом я создал обе ассоциации, но населял только одну за раз. Возможно, это может сработать, но кажется хакерским и затрудняет запросы.

Дополнительная информация

Вот мои ответы на запросы о разъяснениях, опубликованные MDCCL через комментарии:

(1) Если список воспроизведения принадлежит данной группе , можно сказать , что этот список воспроизведения связан один-ко-многим пользователям , до тех пор , как они члены такой группы , не так ли?

Я считаю, что это технически верно, но эта связь один-ко-многим явно не существует.

(2) Итак, возможно ли, чтобы определенный плейлист одновременно принадлежал группе « один ко многим» ?

Нет, не должно быть возможности Playlistвладеть одним-ко-многим Groups.

(3) Можно ли для конкретного списка будет принадлежать один-ко-многим групп и, в то же время, один-ко-многим пользователям , которые не являются членами такой группы ?

Нет, потому что, как и в (2), один-ко-многим от Playlistк Groupне должен существовать. Кроме того, если a Playlistпринадлежит a, то Groupоно не принадлежит a User, и наоборот. Только один владелец одновременно.

(4) Какие свойства используются для однозначной идентификации группы , пользователя и списка воспроизведения ?

Каждый из них имеет суррогатный первичный ключ ( id), а также естественный ключ (хотя и не первичный). Это slugдля Groupи Playlist, и usernameдля User.

(5) Может ли определенный плейлист перенести смену владельца ?

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

(6) В чем смысл атрибутов Group.Slug и Playlist.Slug ? Достаточно ли стабильны их значения для определения в качестве первичных ключей или они меняются очень часто? Значения этих двух свойств вместе с User.Username должны быть уникальными, правильно?

Это slugуникальные, строчные, дефисные версии соответствующих им сущностей title. Например, a groupс title'Test Group' будет иметь slug'test-group'. Дубликаты добавляются с инкрементными целыми числами. Это изменило бы в любое время их titleизменения. Я полагаю, это означает, что они не будут делать отличные первичные ключи? Да slugsи usernamesуникальны в своих соответствующих таблицах.

Ответы:


9

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

Ниже я приведу пример того, как (1) смоделировать его на концептуальном уровне абстракции и затем (2) представить его в дизайне DDL логического уровня .

Бизнес правила

Следующие концептуальные формулировки являются одними из наиболее важных правил в вашем бизнес-контексте:

  • Playlist принадлежит либо ровно одной группы или ровно один пользователь в определенный момент времени
  • Playlist может принадлежать один-ко-многим групп или пользователей в различных точках во времени
  • Пользователь имеет нулевые один или много- плейлист
  • Группы имеют нулевые один или много- плейлист
  • Группа состоит из одного ко многим членам (которые должны быть пользователи )
  • Пользователь может быть членом нулевого одного или многих- групп .
  • Группа состоит из одного ко многим членам (которые должны быть пользователи )

Поскольку ассоциации или отношения (a) между пользователем и списком воспроизведения и (b) между группой и списком воспроизведения довольно похожи, этот факт показывает, что пользователь и группа являются взаимоисключающими подтипами сущностей Стороны 1 , которая, в свою очередь, является их супертипом сущности - superpertype- кластеры подтипов - это классические структуры данных, которые встречаются в концептуальных схемах самых разных видов. Таким образом, можно утверждать два новых правила:

  • Партия классифицируется ровно один PartyType
  • Сторона является либо группа или пользователь

И четыре из предыдущих правил должны быть переформулированы как только три:

  • Playlist принадлежит ровно одна партия в определенный момент времени
  • Playlist может принадлежать один-ко-многим Стороны в различных точках во время
  • Сторона имеет нулевые один или много- плейлист

Описательная схема IDEF1X

Диаграмма IDEF1X 2 , показанная на рисунке 1, объединяет все вышеупомянутые бизнес-правила наряду с другими, которые кажутся уместными:

Рисунок 1 - Диаграмма IDEF1X для владельцев плейлистов

Как продемонстрировано, Группа и Пользователь изображаются как подтипы, которые связаны соответствующими линиями и эксклюзивным символом с Стороной , супертипом.

Свойство 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) методике проектирования логических баз данных, созданной Робертом Г. Брауном.


я не видел каких-либо ограничений для вечеринки в точности одного пользователя или группы
Jasen

Похоже, что ваш комментарий связан со следующими выдержками из вопросов (оба содержатся в разделе, озаглавленном «Соображения целостности и согласованности»):
MDCCL

« Очень важно отметить , что (я) , каждая Partyстрока должна быть дополнена в любое время с помощью (II) соответствующего аналога в точности одной из таблиц , стоящих на подтипы, которые (III) должны„соответствовать“значение , содержащееся в Party.PartyTypeCodeколонка «обозначение дискриминатора ».
MDCCL

« Было бы весьма выгодно применять такую ​​ситуацию декларативным образом, но ни одна из основных систем управления базами данных SQL (включая Postgres) не предоставила необходимых инструментов для такого действия; следовательно, написание процедурного кода в ACID TRANSACTIONS пока является наилучшим вариантом, чтобы гарантировать, что ранее описанные обстоятельства всегда встречаются в вашей базе данных. Другой возможностью было бы прибегнуть к TRIGGERS, но они склонны делать вещи неаккуратными, так сказать ».
MDCCL

Поэтому, как только (надеюсь, скоро) Postgres поставит, скажем, ASSERTIONS, я был бы рад включить применимые (декларативные) ограничения. В настоящее время два доступных подхода, предоставляемых СУБД, предложены выше.
MDCCL
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.