¿Esquema de base de datos para entidades con dos posibles tipos de propietario / padre?

8

Estoy usando PostgreSQL con Sequelize como mi ORM.

Tengo un tipo, User. El segundo tipo es Group, que puede tener cualquier número de usuarios asociados a través de una GroupMembershipstabla. Users también puede poseer cualquier número de Groups.

Mi tercer tipo, Playlistpuede pertenecer a un UserOR a group. ¿Cuál sería la mejor manera de diseñar un esquema para este tipo para que pueda tener un tipo de propietario o uno?

En mi primer pase, creé ambas asociaciones, pero solo llené una a la vez. Esto podría funcionar, pero parece hacky y dificulta las consultas.

Información Adicional

Aquí están mis respuestas a las solicitudes de aclaración publicadas por MDCCL a través de comentarios:

(1) Si una lista de reproducción es propiedad de un grupo determinado , se puede decir que esta lista de reproducción está relacionada con uno a muchos usuarios , siempre que sean miembros de dicho grupo , ¿verdad?

Creo que esto es técnicamente cierto, pero esta asociación uno a muchos no existe explícitamente.

(2) Entonces, ¿es posible que una Lista de reproducción específica sea ​​propiedad de Grupos de uno a muchos al mismo tiempo?

No, no debería ser posible que un Playlistsea ​​propiedad de uno a muchos Groups.

(3) ¿Es posible que una Lista de reproducción en particular sea ​​propiedad de Grupos uno a muchos y, al mismo tiempo, de Usuarios uno a muchos que no sean Miembros de dicho Grupo ?

No, porque como en (2) no debería existir un uno a muchos de Playlista Group. Además, si un Playlistes propiedad de un Groupno es propiedad de un User, y viceversa. Solo un propietario a la vez.

(4) ¿Cuáles son las propiedades utilizadas para identificar de forma exclusiva un grupo , un usuario y una lista de reproducción ?

Cada uno tiene una clave primaria sustituta ( id), así como una clave natural (aunque no primaria). Estos son slugpara Groupy Playlist, y usernamepara User.

(5) ¿Podría una lista de reproducción en particular sufrir un cambio de propietario ?

Aunque no planeo que esto sea una característica (al menos inicialmente), supongo que esto podría ocurrir hipotéticamente.

(6) ¿Cuál es el significado de los atributos Group.Slug y Playlist.Slug ? ¿Son sus valores lo suficientemente estables como para definirse como claves principales o cambian con mucha frecuencia? Los valores de estas dos propiedades, junto con User.Username deben ser únicos, ¿correcto?

Estas slugs son versiones únicas, en minúsculas, con guiones de sus respectivas entidades title. Por ejemplo, a groupcon el title'Grupo de prueba' tendría el slug'grupo de prueba'. Los duplicados se agregan con enteros incrementales. Esto cambiaría en cualquier momento sus titlecambios. ¿Creo que eso significa que no serían excelentes claves primarias? Sí, slugsy usernamesson únicos en sus respectivas tablas.

Jakemmarsh
fuente

Respuestas:

9

Si entiendo sus especificaciones correctamente, su escenario involucra —entre otros aspectos importantes— una estructura de supertipo-subtipo .

A continuación ejemplificaré cómo (1) modelarlo en el nivel conceptual de abstracción y posteriormente (2) representarlo en un diseño DDL de nivel lógico .

Reglas del negocio

Las siguientes formulaciones conceptuales se encuentran entre las reglas más importantes en su contexto empresarial:

  • Una lista de reproducción es propiedad de un grupo o un usuario exactamente en un momento específico
  • Una lista de reproducción puede ser propiedad de grupos o usuarios de uno a muchos en distintos momentos
  • Un usuario posee listas de reproducción de cero uno o muchos
  • A Grupos posee listas de reproducción de cero uno o muchos
  • Un grupo está formado por miembros de uno a muchos (que deben ser usuarios )
  • Un usuario puede ser miembro de uno o varios grupos .
  • Un grupo está formado por miembros de uno a muchos (que deben ser usuarios )

Como las asociaciones o relaciones (a) entre Usuario y Lista de reproducción y (b) entre Grupo y Lista de reproducción son bastante parecidas, este hecho revela que Usuario y Grupo son subtipos de entidad mutuamente excluyentes de la Parte 1 , que a su vez es su supertipo de entidad —supertipo— Los grupos de subtipos son estructuras de datos clásicas que surgen en esquemas conceptuales de muy diversos tipos. De esta manera, se pueden afirmar dos nuevas reglas:

  • Una fiesta se clasifica exactamente por un PartyType
  • Una parte es un grupo o un usuario

Y cuatro de las reglas anteriores deben reformularse como solo tres:

  • Una Lista de reproducción es propiedad de exactamente una Parte en un momento específico
  • Una lista de reproducción puede ser propiedad de una a muchas partes en distintos momentos
  • Una fiesta posee listas de reproducción de cero uno o muchos

Diagrama IDEF1X expositivo

El diagrama IDEF1X 2 que se muestra en la Figura 1 consolida todas las reglas comerciales antes mencionadas junto con otras que parecen pertinentes:

Figura 1 - Diagrama IDEF1X de propietarios de listas de reproducción

Como se demostró, Grupo y Usuario se representan como subtipos que están conectados por las líneas respectivas y el símbolo exclusivo con Party , el supertipo.

La propiedad Party.PartyTypeCode representa el discriminador de subtipo , es decir, indica qué tipo de instancia de subtipo debe complementar una ocurrencia de supertipo dada.

Además, Party está conectado con Playlist a través de la propiedad OwnerId , que se representa como una CLAVE EXTRANJERA que apunta a Party.PartyId . De esta manera, la Parte interrelaciona (a) Lista de reproducción con (b) Grupo y (c) Usuario .

En consecuencia, dado que una instancia de Parte en particular es un Grupo o un Usuario , una Lista de reproducción específica se puede vincular con, como máximo, una ocurrencia de subtipo.

Diseño ilustrativo de nivel lógico

El diagrama IDEF1X expuesto anteriormente me ha servido como una plataforma para crear la siguiente disposición lógica SQL-DDL (y he proporcionado notas como comentarios que destacan varios puntos de particular relevancia, por ejemplo, las declaraciones de restricciones):

-- 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)  
);

Por supuesto, puede realizar uno o más ajustes para que todas las características de su contexto empresarial estén representadas con la precisión necesaria en la base de datos real.

Nota : He probado el diseño lógico anterior en este violín db <> y también en este violín SQL , ambos "en ejecución" en PostgreSQL 9.6, para que pueda verlos "en acción".

Las babosas

Como puede ver, no incluí Group.Slugni Playlist.Slugcomo columnas en las declaraciones DDL. Esto es así porque, de acuerdo con su siguiente explicación

Estas slugs son versiones únicas, en minúsculas, con guiones de sus respectivas entidades title. Por ejemplo, a groupcon el title'Grupo de prueba' tendría el slug'grupo de prueba'. Los duplicados se agregan con enteros incrementales. Esto cambiaría en cualquier momento sus titlecambios. ¿Creo que eso significa que no serían excelentes claves primarias? Sí, slugsy usernamesson únicos en sus respectivas tablas.

se puede concluir que sus valores son derivables (es decir, deben calcularse o calcularse en términos de los valores Group.Titley Playlist.Titlevalores correspondientes , a veces en conjunción con —Tengo en cuenta, algún tipo de INTEGER generado por el sistema), por lo que no declararía dichas columnas en cualquiera de las tablas base ya que introducirían irregularidades de actualización.

Por el contrario, produciría el Slugs

  • tal vez, en una vista , que (a) incluye la derivación de tales valores en columnas virtuales y (b) se puede usar directamente en operaciones SELECT adicionales; si se obtiene la parte INTEGER, p. ej., combinando el valor de (1) el Playlist.OwnerIdcon (2) los guiones intermedios y (3) el valor de Playlist.Title;

  • o, en virtud del código del programa de aplicación, imitando el enfoque descrito anteriormente (quizás de forma procesal), una vez que los conjuntos de datos pertinentes se recuperan y formatean para la interpretación del usuario final.

De esta manera, cualquiera de esos dos métodos evitaría el mecanismo de “sincronización de actualización” que debe ser puesto en su lugar si y sólo si la Slugsestán retenidas en columnas de tablas base.

Consideraciones de integridad y consistencia

Es fundamental mencionar que (i) cada Party fila debe complementarse en todo momento con (ii) la contraparte respectiva en exactamente una de las tablas que representan los subtipos, que (iii) deben "cumplir" con el valor contenido en la Party.PartyTypeCodecolumna —Denotando al discriminador—.

Sería bastante ventajoso hacer cumplir ese tipo de situación de manera declarativa , pero ninguno de los principales sistemas de administración de bases de datos SQL (incluido Postgres) ha suministrado los instrumentos necesarios para proceder de esa manera; por lo tanto, escribir código de procedimiento dentro de ACID TRANSACTIONS es hasta ahora la mejor opción para garantizar que las circunstancias descritas anteriormente siempre se cumplan en su base de datos. Otra posibilidad sería recurrir a DISPARADORES, pero son propensos a poner las cosas en orden, por así decirlo.

Casos comparables

Si desea establecer algunas analogías, puede interesarle echar un vistazo a mis respuestas a las preguntas (más recientes) tituladas

ya que se discuten escenarios comparables.


Notas finales

1 Parte es un término utilizado en contextos legales cuando se refiere a un individuo o un grupo de individuos que componen una sola entidad , por lo que esta denominación es adecuada para representar los conceptos de Usuario y Grupo con respecto al entorno empresarial en cuestión.

2 La definición de integración para el modelado de información ( IDEF1X ) es una técnica de modelado de datos muy recomendable que fue establecida como estándar en diciembre de 1993 por el Instituto Nacional de Estándares y Tecnología de los Estados Unidos(NIST). Se basa sólidamente en (a) algunos de los primeros trabajos teóricos escritos por el único autor del modelo relacional , es decir, el Dr. EF Codd ; en (b) la vista entidad-relación , desarrollada por el Dr. PP Chen ; y también en (c) la Técnica de diseño de base de datos lógica, creada por Robert G. Brown.

MDCCL
fuente
no he visto ninguna restricción para la fiesta es exactamente una de usuario o grupo
Jasen
Parece que su comentario tiene que ver con los siguientes extractos de preguntas (ambos contenidos en la sección titulada “Consideraciones de integridad y consistencia”):
MDCCL
" Es fundamental mencionar que (i) cada Partyfila debe complementarse en todo momento con (ii) la contraparte respectiva en exactamente una de las tablas que representan los subtipos, que (iii) deben 'cumplir' el valor contenido en el Party.PartyTypeCodecolumna "denotando al discriminador ".
MDCCL
" Sería bastante ventajoso hacer cumplir ese tipo de situación de manera declarativa , pero ninguno de los principales sistemas de gestión de bases de datos SQL (incluido Postgres) ha suministrado los instrumentos necesarios para proceder así; por lo tanto, escribir código de procedimiento dentro de ACID TRANSACTIONS es hasta ahora la mejor opción para garantizar que las circunstancias descritas anteriormente siempre se cumplan en su base de datos. Otra posibilidad sería recurrir a DISPARADORES, pero son propensos a desordenar las cosas, por así decirlo ”.
MDCCL
Por lo tanto, una vez (espero que pronto) Postgres suministre, por ejemplo, ASSERTIONS, me complacería incluir las restricciones aplicables (declarativas). Por el momento, los dos enfoques disponibles proporcionados por DBMS se sugieren anteriormente.
MDCCL