Se eu entendo suas especificações corretamente, seu cenário envolve - entre outros aspectos significativos - uma estrutura de supertipo-subtipo .
Vou exemplificar abaixo como (1) modelá-lo no nível conceitual de abstração e subsequentemente (2) representá-lo em um design DDL de nível lógico .
Regras do negócio
As seguintes formulações conceituais estão entre as regras mais importantes no seu contexto de negócios:
- Uma lista de reprodução pertence a exatamente um grupo ou exatamente um usuário em um momento específico
- Uma lista de reprodução pode pertencer a grupos ou usuários um a muitos em momentos distintos
- Um usuário possui Playlists zero-one-or-many
- Um grupo possui Playlists zero-one-or-many
- Um grupo é composto de um para muitos membros (que devem ser usuários )
- Um usuário pode ser um membro de um ou mais grupos .
- Um grupo é composto de um para muitos membros (que devem ser usuários )
Como as associações ou relacionamentos (a) entre Usuário e Lista de reprodução e (b) entre Grupo e Lista de reprodução são bastante parecidas, esse fato revela que Usuário e Grupo são subtipos de entidade mutuamente exclusivos da Parte 1 , que por sua vez é o supertipo de entidade - supertipo- agrupamentos de subtipos são estruturas de dados clássicas que surgem em esquemas conceituais de tipos muito diversos -. Dessa maneira, duas novas regras podem ser afirmadas:
- Uma Parte é categorizada por exatamente um PartyType
- Uma Parte é um Grupo ou um Usuário
E quatro das regras anteriores devem ser reformuladas como apenas três:
- Uma lista de reprodução pertence a exatamente uma parte em um momento específico
- Uma lista de reprodução pode pertencer a várias partes em momentos distintos
- Um partido possui zero ou uma ou várias listas de reprodução
Diagrama do IDEF1X expositivo
O diagrama IDEF1X 2 mostrado na Figura 1 consolida todas as regras de negócios acima mencionadas, juntamente com outras que parecem pertinentes:
Como demonstrado, o Grupo e o Usuário são retratados como subtipos conectados pelas respectivas linhas e pelo símbolo exclusivo da Parte , o supertipo.
A propriedade Party.PartyTypeCode representa o discriminador de subtipo , ou seja, indica que tipo de instância de subtipo deve complementar uma determinada ocorrência de supertipo.
Além disso, o Party está conectado à Playlist por meio da propriedade OwnerId , que é descrita como uma CHAVE ESTRANGEIRA que aponta para Party.PartyId . Dessa maneira, a Parte inter-relaciona (a) Playlist com (b) Group e (c) User .
Consequentemente, como uma instância específica da Parte é um Grupo ou um Usuário , uma Lista de Reprodução específica pode ser vinculada a, no máximo, uma ocorrência de subtipo.
Layout de nível lógico ilustrativo
O diagrama IDEF1X exposto anteriormente serviu como uma plataforma para criar o seguinte arranjo lógico de SQL-DDL (e eu forneci notas como comentários destacando vários pontos de particular relevância - por exemplo, as declarações de restrição -):
-- 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)
);
Obviamente, você pode fazer um ou mais ajustes para que todas as características do seu contexto de negócios sejam representadas com a precisão necessária no banco de dados real.
Nota : Testei o layout lógico acima neste db <> fiddle e também neste SQL Fiddle , ambos "em execução" no PostgreSQL 9.6, para que você possa vê-los "em ação".
As lesmas
Como você pode ver, não incluí Group.Slug
nem Playlist.Slug
como colunas nas declarações DDL. Isso ocorre porque, de acordo com sua explicação a seguir
Estes slug
são versões únicas, minúsculas e hifenizadas de suas respectivas entidades title
. Por exemplo, um group
com o title
'Grupo de Teste' teria o slug
'grupo de teste'. Duplicatas são anexadas com números inteiros incrementais. Isso mudaria a qualquer momento as title
alterações. Acredito que isso significa que eles não seriam excelentes chaves primárias? Sim slugs
e usernames
são exclusivos em suas respectivas tabelas.
pode-se concluir que seus valores são deriváveis (ou seja, eles devem ser calculados ou calculados em termos dos valores correspondentes Group.Title
e Playlist.Title
, às vezes em conjunto com - presumo, algum tipo de INTEGERs gerados pelo sistema) -, portanto, não declararia essas colunas em qualquer uma das tabelas base , pois introduziriam irregularidades na atualização.
Em contraste, eu produziria o Slugs
talvez, em uma visão , que (a) inclua a derivação de tais valores em colunas virtuais e (b) possa ser usada diretamente em outras operações SELECT - a inclusão da parte INTEGER pode ser obtida, por exemplo, combinando o valor de (1) o Playlist.OwnerId
com (2) os hífens intermediários e (3) o valor do Playlist.Title
;
ou, em virtude do código do programa aplicativo, imitando a abordagem descrita anteriormente (talvez proceduralmente), uma vez que os conjuntos de dados pertinentes são recuperados e formatados para a interpretação do usuário final.
Desta forma, qualquer um desses dois métodos evitaria o mecanismo de “sincronização update” que devem ser postas em prática sse o Slugs
são retidas em colunas de tabelas de base.
Considerações sobre integridade e consistência
É fundamental mencionar que (i) cada Party
linha deve ser complementado em todos os momentos por (ii) a respectiva contrapartida em exatamente uma das mesas de pé para os subtipos, que (iii) deve “cumprir” o valor contido na Party.PartyTypeCode
coluna - nomeando o discriminador -.
Seria bastante vantajoso impor esse tipo de situação de maneira declarativa , mas nenhum dos principais sistemas de gerenciamento de banco de dados SQL (incluindo o Postgres) forneceu os instrumentos necessários para proceder dessa maneira; portanto, escrever código processual no ACID TRANSACTIONS é a melhor opção para garantir que as circunstâncias descritas anteriormente sejam sempre atendidas no banco de dados. Outra possibilidade seria recorrer a TRIGGERS, mas eles tendem a deixar as coisas desarrumadas, por assim dizer.
Casos comparáveis
Se você deseja estabelecer algumas analogias, pode estar interessado em dar uma olhada nas minhas respostas para as (mais recentes) perguntas intituladas
já que cenários comparáveis são discutidos.
Notas finais
1 Parte é um termo usado em contextos legais quando se refere a um indivíduo ou a um grupo de indivíduos que compõem uma única entidade ; portanto, essa denominação é adequada para representar os conceitos de Usuário e Grupo em relação ao ambiente de negócios em questão.
2 Definição de Integração para Modelagem de Informações ( IDEF1X ) é uma técnica de modelagem de dados altamente recomendável que foi estabelecida como padrão em dezembro de 1993 pelo Instituto Nacional de Padrões e Tecnologia (NIST)dos Estados Unidos. É solidamente baseado em (a) alguns dos primeiros trabalhos teóricos de autoria do único autor do modelo relacional , ou seja, Dr. EF Codd ; (b) a visão de relacionamento entre entidades , desenvolvida pelo Dr. PP Chen ; e também (c) a Logical Database Design Technique, criada por Robert G. Brown.
Party
linha deve ser complementado em todos os momentos por (ii) a respectiva contrapartida em exatamente uma das mesas de pé para os subtipos, que (iii) must 'cumprir' o valor contido noParty.PartyTypeCode
coluna - indicando o discriminador ”.