De acuerdo con su descripción del entorno empresarial en consideración, existe una estructura de subtipo de supertipo que abarca el elemento , el supertipo, y cada una de sus categorías , es decir, automóvil , barco y avión (junto con dos más que no se dieron a conocer): los subtipos
A continuación detallaré el método que seguiría para administrar tal escenario.
Reglas del negocio
Para comenzar a delinear el esquema conceptual relevante , algunas de las reglas comerciales más importantes determinadas hasta ahora (restringiendo el análisis solo a las tres Categorías divulgadas , para mantener las cosas lo más breve posible) pueden formularse de la siguiente manera:
- Un usuario posee cero uno o muchos artículos
- Un artículo es propiedad de exactamente un usuario en un instante específico
- Un artículo puede ser propiedad de uno a muchos usuarios en distintos momentos
- Un artículo se clasifica por exactamente una categoría
- Un artículo es, en todo momento,
- ya sea un auto
- o un barco
- o un avión
Diagrama ilustrativo de IDEF1X
La Figura 1 muestra un diagrama IDEF1X 1 que creé para agrupar las formulaciones anteriores junto con otras reglas comerciales que parecen pertinentes:
Supertipo
Por un lado, Item , el supertipo, presenta las propiedades † o atributos que son comunes a todas las Categorías , es decir,
- CategoryCode —especificado como una LLAVE EXTRANJERA (FK) que hace referencia a Category.CategoryCode y funciona como un discriminador de subtipo , es decir, indica la categoría exacta de subtipo con la que se debe conectar un elemento dado—,
- OwnerId, que se distingue como un FK que apunta a User.UserId , pero le asigné un nombre de rol 2 para reflejar sus implicaciones especiales con mayor precisión,
- foo ,
- Bar ,
- Baz y
- CreatedDateTime .
Subtipos
Por otro lado, las propiedades ‡ que pertenecen a cada categoría en particular , es decir,
- Qux y Corge ;
- Grault , Garply y Plugh ;
- Xyzzy , Thud , Wibble y Flob ;
se muestran en el cuadro de subtipo correspondiente.
Identificadores
Luego, Item.ItemId PRIMARY KEY (PK) ha migrado 3 a los subtipos con diferentes nombres de roles, es decir,
- CarId ,
- BoatId y
- PlaneId .
Asociaciones mutuamente excluyentes
Como se muestra, existe una asociación o relación de cardinalidad uno a uno (1: 1) entre (a) cada supertipo y (b) su instancia de subtipo complementario.
El símbolo de subtipo exclusivo retrata el hecho de que los subtipos son mutuamente excluyentes, es decir, la aparición de un Elemento concreto puede complementarse con una sola instancia de subtipo solo: un Coche , o un Avión , o un Barco (nunca por dos o más).
† , ‡ Empleé nombres de marcador de posición clásicos para titular algunas de las propiedades de tipo de entidad, ya que sus denominaciones reales no se proporcionaron en la pregunta.
Diseño de nivel lógico expositivo
En consecuencia, para analizar un diseño lógico expositivo, deduje las siguientes instrucciones SQL-DDL basadas en el diagrama IDEF1X mostrado y descrito anteriormente:
-- 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 context.
-- As one would expect, you are free to utilize
-- your preferred (or required) naming conventions.
CREATE TABLE UserProfile (
UserId INT NOT NULL,
FirstName CHAR(30) NOT NULL,
LastName CHAR(30) NOT NULL,
BirthDate DATE NOT NULL,
GenderCode CHAR(3) NOT NULL,
Username CHAR(20) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT UserProfile_PK PRIMARY KEY (UserId),
CONSTRAINT UserProfile_AK1 UNIQUE ( -- Composite ALTERNATE KEY.
FirstName,
LastName,
GenderCode,
BirthDate
),
CONSTRAINT UserProfile_AK2 UNIQUE (Username) -- ALTERNATE KEY.
);
CREATE TABLE Category (
CategoryCode CHAR(1) NOT NULL, -- Meant to contain meaningful, short and stable values, e.g.; 'C' for 'Car'; 'B' for 'Boat'; 'P' for 'Plane'.
Name CHAR(30) NOT NULL,
--
CONSTRAINT Category_PK PRIMARY KEY (CategoryCode),
CONSTRAINT Category_AK UNIQUE (Name) -- ALTERNATE KEY.
);
CREATE TABLE Item ( -- Stands for the supertype.
ItemId INT NOT NULL,
OwnerId INT NOT NULL,
CategoryCode CHAR(1) NOT NULL, -- Denotes the subtype discriminator.
Foo CHAR(30) NOT NULL,
Bar CHAR(30) NOT NULL,
Baz CHAR(30) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Item_PK PRIMARY KEY (ItemId),
CONSTRAINT Item_to_Category_FK FOREIGN KEY (CategoryCode)
REFERENCES Category (CategoryCode),
CONSTRAINT Item_to_User_FK FOREIGN KEY (OwnerId)
REFERENCES UserProfile (UserId)
);
CREATE TABLE Car ( -- Represents one of the subtypes.
CarId INT NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Qux CHAR(30) NOT NULL,
Corge CHAR(30) NOT NULL,
--
CONSTRAINT Car_PK PRIMARY KEY (CarId),
CONSTRAINT Car_to_Item_FK FOREIGN KEY (CarId)
REFERENCES Item (ItemId)
);
CREATE TABLE Boat ( -- Stands for one of the subtypes.
BoatId INT NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Grault CHAR(30) NOT NULL,
Garply CHAR(30) NOT NULL,
Plugh CHAR(30) NOT NULL,
--
CONSTRAINT Boat_PK PRIMARY KEY (BoatId),
CONSTRAINT Boat_to_Item_FK FOREIGN KEY (BoatId)
REFERENCES Item (ItemId)
);
CREATE TABLE Plane ( -- Denotes one of the subtypes.
PlaneId INT NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Xyzzy CHAR(30) NOT NULL,
Thud CHAR(30) NOT NULL,
Wibble CHAR(30) NOT NULL,
Flob CHAR(30) NOT NULL,
--
CONSTRAINT Plane_PK PRIMARY KEY (PlaneId),
CONSTRAINT Plane_to_Item_PK FOREIGN KEY (PlaneId)
REFERENCES Item (ItemId)
);
Como se demostró, el tipo de superentidad y cada uno de los tipos de subentidad están representados por la tabla base correspondiente .
Las columnas CarId
, BoatId
y PlaneId
, con restricciones como las PK de las tablas adecuadas, ayuda en la representación del nivel conceptual uno-a-uno asociación por medio de restricciones FK § ese punto de la ItemId
columna, que está limitado como el PK de la Item
tabla. Esto significa que, en un "par" real, tanto las filas de supertipo como de subtipo están identificadas por el mismo valor PK; Por lo tanto, es más que oportuno mencionar que
- (a) adjuntar una columna adicional para contener los valores sustitutos controlados por el sistema ‖ a (b) las tablas que representan los subtipos es (c) completamente superfluo .
§ Para evitar problemas y errores relacionados con las definiciones de restricciones CLAVE (particularmente EXTRANJERAS), situación a la que se refirió en los comentarios, es muy importante tener en cuenta la dependencia de la existencia que tiene lugar entre las diferentes tablas disponibles, como se ejemplifica en el orden de declaración de las tablas en la estructura DDL expositiva, que también proporcioné en este SQL Fiddle .
‖ Por ejemplo, agregar una columna adicional con la propiedad AUTO_INCREMENT a una tabla de una base de datos construida en MySQL.
Consideraciones de integridad y consistencia
Es fundamental señalar que, en su entorno empresarial, debe (1) asegurarse de que cada fila de "supertipo" esté en todo momento complementada por su correspondiente contraparte "subtipo" y, a su vez, (2) garantizar que dicho La fila "subtipo" es compatible con el valor contenido en la columna "discriminador" de la fila "supertipo".
Sería muy elegante hacer cumplir tales circunstancias de manera declarativa , pero, desafortunadamente, ninguna de las principales plataformas SQL ha proporcionado los mecanismos adecuados para hacerlo, por lo que sé. Por lo tanto, recurrir al código de procedimiento dentro de ACID TRANSACTIONS es bastante conveniente para que estas condiciones siempre se cumplan en su base de datos. Otra opción sería emplear DISPARADORES, pero tienden a desordenar las cosas, por así decirlo.
Declarando vistas útiles
Con un diseño lógico como el explicado anteriormente, sería muy práctico crear una o más vistas, es decir, tablas derivadas que comprendan columnas que pertenezcan a dos o más de las tablas base relevantes . De esta manera, puede, por ejemplo, SELECCIONAR directamente desde esas vistas sin tener que escribir todas las UNIONES cada vez que tenga que recuperar información "combinada".
Data de muestra
A este respecto, digamos que las tablas base están "pobladas" con los datos de muestra que se muestran a continuación:
--
INSERT INTO UserProfile
(UserId, FirstName, LastName, BirthDate, GenderCode, Username, CreatedDateTime)
VALUES
(1, 'Edgar', 'Codd', '1923-08-19', 'M', 'ted.codd', CURDATE()),
(2, 'Michelangelo', 'Buonarroti', '1475-03-06', 'M', 'michelangelo', CURDATE()),
(3, 'Diego', 'Velázquez', '1599-06-06', 'M', 'd.velazquez', CURDATE());
INSERT INTO Category
(CategoryCode, Name)
VALUES
('C', 'Car'), ('B', 'Boat'), ('P', 'Plane');
-- 1. ‘Full’ Car INSERTion
-- 1.1
INSERT INTO Item
(ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
(1, 1, 'C', 'This datum', 'That datum', 'Other datum', CURDATE());
-- 1.2
INSERT INTO Car
(CarId, Qux, Corge)
VALUES
(1, 'Fantastic Car', 'Powerful engine pre-update!');
-- 2. ‘Full’ Boat INSERTion
-- 2.1
INSERT INTO Item
(ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
(2, 2, 'B', 'This datum', 'That datum', 'Other datum', CURDATE());
-- 2.2
INSERT INTO Boat
(BoatId, Grault, Garply, Plugh)
VALUES
(2, 'Excellent boat', 'Use it to sail', 'Everyday!');
-- 3 ‘Full’ Plane INSERTion
-- 3.1
INSERT INTO Item
(ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
(3, 3, 'P', 'This datum', 'That datum', 'Other datum', CURDATE());
-- 3.2
INSERT INTO Plane
(PlaneId, Xyzzy, Thud, Wibble, Flob)
VALUES
(3, 'Extraordinary plane', 'Traverses the sky', 'Free', 'Like a bird!');
--
Entonces, una vista ventajoso es uno que reúne las columnas de Item
, Car
y UserProfile
:
--
CREATE VIEW CarAndOwner AS
SELECT C.CarId,
I.Foo,
I.Bar,
I.Baz,
C.Qux,
C.Corge,
U.FirstName AS OwnerFirstName,
U.LastName AS OwnerLastName
FROM Item I
JOIN Car C
ON C.CarId = I.ItemId
JOIN UserProfile U
ON U.UserId = I.OwnerId;
--
Naturalmente, se puede seguir un enfoque similar para que también pueda SELECCIONAR la información "completa" Boat
y Plane
directamente desde una sola tabla (una derivada, en estos casos).
Después de que -si no me importa acerca de la presencia de marcas NULL en consecuencia Conjuntos- con la siguiente definición de vista, se puede, por ejemplo, “recoger” las columnas de las tablas Item
, Car
, Boat
, Plane
y UserProfile
:
--
CREATE VIEW FullItemAndOwner AS
SELECT I.ItemId,
I.Foo, -- Common to all Categories.
I.Bar, -- Common to all Categories.
I.Baz, -- Common to all Categories.
IC.Name AS Category,
C.Qux, -- Applies to Cars only.
C.Corge, -- Applies to Cars only.
--
B.Grault, -- Applies to Boats only.
B.Garply, -- Applies to Boats only.
B.Plugh, -- Applies to Boats only.
--
P.Xyzzy, -- Applies to Planes only.
P.Thud, -- Applies to Planes only.
P.Wibble, -- Applies to Planes only.
P.Flob, -- Applies to Planes only.
U.FirstName AS OwnerFirstName,
U.LastName AS OwnerLastName
FROM Item I
JOIN Category IC
ON I.CategoryCode = IC.CategoryCode
LEFT JOIN Car C
ON C.CarId = I.ItemId
LEFT JOIN Boat B
ON B.BoatId = I.ItemId
LEFT JOIN Plane P
ON P.PlaneId = I.ItemId
JOIN UserProfile U
ON U.UserId = I.OwnerId;
--
El código de las vistas aquí mostradas es solo ilustrativo. Por supuesto, hacer algunos ejercicios de prueba y modificaciones podría ayudar a acelerar la ejecución (física) de las consultas en cuestión. Además, es posible que deba eliminar o agregar columnas a dichas vistas según lo requiera la empresa.
Los datos de muestra y todas las definiciones de vista se incorporan a este Fiddle de SQL para que puedan observarse "en acción".
Manipulación de datos: código de programa (s) de aplicación y alias de columna
El uso del código de los programas de aplicación (si eso es lo que quiere decir con "código específico del lado del servidor") y los alias de columna son otros puntos importantes que mencionó en los siguientes comentarios:
Me las arreglé para solucionar el problema [unir] con código específico del lado del servidor, pero realmente no quiero hacer eso -Y- agregar alias a todas las columnas podría ser "estresante".
Muy bien explicado, muchas gracias. Sin embargo, como sospechaba, tendré que manipular el conjunto de resultados al enumerar todos los datos debido a las similitudes con algunas columnas, ya que no quiero usar varios alias para mantener limpia la declaración.
Es oportuno indicar que, si bien el uso del código del programa de aplicación es un recurso muy apropiado para manejar las características de presentación (o gráficas) de los conjuntos de resultados, es fundamental evitar la recuperación de datos fila por fila para evitar problemas de velocidad de ejecución. El objetivo debe ser "buscar" los conjuntos de datos pertinentes in toto por medio de los instrumentos de manipulación de datos robustos proporcionados por el motor de conjuntos (precisamente) de la plataforma SQL para que pueda optimizar el comportamiento de su sistema.
Además, el uso de alias para cambiar el nombre de una o más columnas dentro de un determinado alcance puede parecer estresante, pero, personalmente, veo ese recurso como una herramienta muy poderosa que ayuda a (i) contextualizar y (ii) desambiguar el significado y la intención atribuidos a lo concerniente columnas; por lo tanto, este es un aspecto que debe ser meditado a fondo con respecto a la manipulación de los datos de interés.
Escenarios similares
También podría encontrar ayuda en esta serie de publicaciones y este grupo de publicaciones que contienen mi opinión sobre otros dos casos que incluyen asociaciones de supertipo-subtipo con subtipos mutuamente excluyentes.
También he propuesto una solución para un entorno empresarial que involucra un clúster de supertipo-subtipo donde los subtipos no son mutuamente excluyentes en esta respuesta (más reciente) .
Notas finales
1 La definición de integración para el modelado de información ( IDEF1X ) es una técnica de modelado de datos altamente recomendable que fue establecida como estándar en diciembre de 1993 por el Instituto Nacional de Estándares y Tecnología de EE. UU. (NIST). Se basa sólidamente en (a) algunos de los 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.
2 En IDEF1X, un nombre de rol es una etiqueta distintiva asignada a una propiedad FK (o atributo) para expresar el significado que tiene dentro del alcance de su tipo de entidad respectivo.
3 El estándar IDEF1X define la migración de claves como "El proceso de modelado de colocar la clave primaria de una entidad primaria o genérica en su entidad secundaria o de categoría como clave externa".
Item
tabla incluye unaCategoryCode
columna. Como se menciona en la sección titulada “Consideraciones de integridad y consistencia”:CategoryColumn
tablas que representan los subtipos (con todas las implicaciones en los niveles lógicos [por ejemplo, anomalías de modificación] y físicos de abstracción [por ejemplo, índices adicionales, estructuras más grandes, etc.]).Llamemos a la mesa principal Productos. Esto aloja los atributos compartidos. Entonces, digamos que tenemos una mesa de coche, una mesa de avión y una mesa de barco. Estas tres tablas tendrían una clave ProductID con una restricción FK en la fila ID de la tabla Product. Si los quieres a todos, únete a ellos. Si solo quieres los autos, únete a la izquierda Cars con productos (o une a la derecha los productos y autos, pero prefiero usar siempre las combinaciones del lado izquierdo).
Esto se llama un modelo de datos jerárquico. Para un bajo número de sub-tablas, podría tener sentido en una tabla larga (millones de productos).
fuente