Esquema do banco de dados para entidades com dois possíveis tipos de proprietário / pai?

8

Estou usando o PostgreSQL com Sequelize como meu ORM.

Eu tenho um tipo User. O segundo tipo é Group, que pode ter qualquer número de usuários associado a ele por meio de uma GroupMembershipstabela. Users também pode possuir qualquer número de Groups.

Meu terceiro tipo Playlist,, pode pertencer a um UserOR a group. Qual seria a melhor maneira de projetar um esquema para esse tipo para que ele possa ter um tipo de proprietário ou o outro?

Meu primeiro passe, criei as duas associações, mas apenas preenchi uma de cada vez. Isso pode funcionar, mas parece hacky e dificulta as consultas.

Informação adicional

Aqui estão minhas respostas às solicitações de esclarecimento postadas pelo MDCCL por meio de comentários:

(1) Se uma lista de reprodução pertence a um determinado grupo , pode-se dizer que essa lista de reprodução está relacionada a um para muitos usuários , desde que sejam membros desse grupo , certo?

Acredito que isso seja tecnicamente verdade, mas essa associação um para muitos não existe explicitamente.

(2) Então, é possível que uma lista de reprodução específica seja de propriedade de um para muitos grupos ao mesmo tempo?

Não, não deve ser possível Playlistpertencer a um para muitos Groups.

(3) É possível que uma determinada lista de reprodução seja de propriedade de um para muitos grupos e, ao mesmo tempo, de um para muitos usuários que não são membros desse grupo ?

Não, porque como em (2) um um-para-muitos de Playlistque Groupnão deve existir. Além disso, se a Playlistpertence a um Group, não pertence a Usere vice-versa. Apenas um proprietário de cada vez.

(4) Quais são as propriedades usadas para identificar exclusivamente um grupo , um usuário e uma lista de reprodução ?

Cada um deles possui uma chave primária substituta ( id), assim como uma chave natural (embora não seja primária). Estes são slugpara Groupe Playlist, e usernamepara User.

(5) Uma lista de reprodução específica pode sofrer uma alteração de proprietário ?

Embora eu não planeje que isso seja um recurso (pelo menos inicialmente), suponho que isso possa ocorrer hipoteticamente.

(6) Qual é o significado dos atributos Group.Slug e Playlist.Slug ? Seus valores são estáveis ​​o suficiente para serem definidos como chaves primárias ou mudam com muita frequência? Os valores dessas duas propriedades, juntamente com User.Username devem ser exclusivos, correto?

Estes slugsão versões únicas, minúsculas e hifenizadas de suas respectivas entidades title. Por exemplo, um groupcom 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 titlealterações. Acredito que isso significa que eles não seriam excelentes chaves primárias? Sim slugse usernamessão exclusivos em suas respectivas tabelas.

Jakemmarsh
fonte

Respostas:

9

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:

Figura 1 - Diagrama do IDEF1X dos proprietários da lista de reprodução

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.Slugnem Playlist.Slugcomo colunas nas declarações DDL. Isso ocorre porque, de acordo com sua explicação a seguir

Estes slugsão versões únicas, minúsculas e hifenizadas de suas respectivas entidades title. Por exemplo, um groupcom 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 titlealterações. Acredito que isso significa que eles não seriam excelentes chaves primárias? Sim slugse usernamessã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.Titlee 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.OwnerIdcom (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 Slugssã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.PartyTypeCodecoluna - 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.

MDCCL
fonte
eu não vi nenhuma restrição para a festa ser exatamente de usuário ou grupo #
212 Jasen
Parece que seu comentário tem a ver com os seguintes trechos de perguntas (ambos contidos na seção intitulada "Considerações de integridade e consistência"):
MDCCL
É fundamental mencionar que (i) cada Partylinha 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 no Party.PartyTypeCodecoluna - indicando o discriminador ”.
MDCCL 9/02/19
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 ”.
MDCCL 9/02/19
Portanto, uma vez (espero que em breve) o Postgres forneça, digamos, ASSERTIONS, ficaria feliz em incluir as restrições (declarativas) aplicáveis. Por enquanto, as duas abordagens disponíveis fornecidas pelo DBMS são sugeridas acima.
MDCCL 9/02/19