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:
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.Slug
ni Playlist.Slug
como columnas en las declaraciones DDL. Esto es así porque, de acuerdo con su siguiente explicación
Estas slug
s son versiones únicas, en minúsculas, con guiones de sus respectivas entidades title
. Por ejemplo, a group
con 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 title
cambios. ¿Creo que eso significa que no serían excelentes claves primarias? Sí, slugs
y usernames
son ú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.Title
y Playlist.Title
valores 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.OwnerId
con (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 Slugs
está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.PartyTypeCode
columna —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.
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' el valor contenido en elParty.PartyTypeCode
columna "denotando al discriminador ".