Supertipo / Subtipo
¿Qué tal mirar en el patrón de supertipo / subtipo? Las columnas comunes van en una tabla principal. Cada tipo distinto tiene su propia tabla con el ID del padre como su propia PK y contiene columnas únicas que no son comunes a todos los subtipos. Puede incluir una columna de tipo en las tablas padre e hijo para asegurarse de que cada dispositivo no puede tener más de un subtipo. Haga un FK entre los hijos y los padres en (ItemID, ItemTypeID). Puede usar FK en las tablas de supertipo o subtipo para mantener la integridad deseada en otro lugar. Por ejemplo, si se permite el ItemID de cualquier tipo, cree el FK en la tabla primaria. Si solo se puede hacer referencia a SubItemType1, cree el FK en esa tabla. Dejaría el TypeID fuera de las tablas de referencia.
Nombrar
Cuando se trata de nombrar, tiene dos opciones como yo lo veo (ya que la tercera opción de solo "ID" es en mi opinión un fuerte antipatrón). Llame a la clave de subtipo ItemID como si estuviera en la tabla principal o llámela al nombre de subtipo como DoohickeyID. Después de pensar un poco y algo de experiencia con esto, abogo por llamarlo DoohickeyID. La razón de esto es que, aunque podría haber confusión sobre la tabla de subtipos realmente disfrazada que contiene elementos (en lugar de Doohickeys), eso es un poco negativo en comparación con cuando se crea un FK para la tabla Doohickey y los nombres de columna no ¡partido!
A EAV o no a EAV: mi experiencia con una base de datos EAV
Si EAV es lo que realmente tienes que hacer, entonces es lo que tienes que hacer. Pero, ¿y si no fuera lo que tenía que hacer?
Construí una base de datos EAV que está en uso en un negocio. Gracias a Dios, el conjunto de datos es pequeño (aunque hay docenas de tipos de elementos), por lo que el rendimiento no es malo. ¡Pero sería malo si la base de datos tuviera más de unos pocos miles de elementos! Además, las tablas son tan DIFÍCILES de consultar. Esta experiencia me ha llevado a desear realmente evitar las bases de datos EAV en el futuro si es posible.
Ahora, en mi base de datos, creé un procedimiento almacenado que crea automáticamente vistas PIVOTADAS para cada subtipo que existe. Solo puedo consultar desde AutoDoohickey. Mis metadatos sobre los subtipos tienen una columna "ShortName" que contiene un nombre seguro para objetos adecuado para usar en los nombres de vista. ¡Incluso hice las vistas actualizables! Desafortunadamente, no puede actualizarlos en una unión, pero PUEDE insertarles una fila ya existente, que se convertirá en una ACTUALIZACIÓN. Desafortunadamente, no puede actualizar solo unas pocas columnas, porque no hay forma de indicar a la VISTA qué columnas desea actualizar con el proceso de conversión INSERT-to-UPDATE: un valor NULL se parece a "actualizar esta columna a NULL" incluso si quería indicar "No actualice esta columna en absoluto".
A pesar de toda esta decoración para hacer que la base de datos EAV sea más fácil de usar, todavía no uso estas vistas en la mayoría de las consultas normales porque es LENTA. Las condiciones de consulta no son un predicado que se devuelve a la Value
tabla, por lo que tiene que generar un conjunto de resultados intermedios de todos los elementos del tipo de esa vista antes de filtrar. Ay. Entonces tengo muchas, muchas consultas con muchas, muchas uniones, cada una de las cuales sale para obtener un valor diferente y así sucesivamente. Se desempeñan relativamente bien, pero ¡ay! Aquí hay un ejemplo. El SP que crea esto (y su activador de actualización) es una bestia gigante, y estoy orgulloso de ello, pero no es algo que quieras intentar mantener.
CREATE VIEW [dbo].[AutoModule]
AS
--This view is automatically generated by the stored procedure AutoViewCreate
SELECT
ElementID,
ElementTypeID,
Convert(nvarchar(160), [3]) [FullName],
Convert(nvarchar(1024), [435]) [Descr],
Convert(nvarchar(255), [439]) [Comment],
Convert(bit, [438]) [MissionCritical],
Convert(int, [464]) [SupportGroup],
Convert(int, [461]) [SupportHours],
Convert(nvarchar(40), [4]) [Ver],
Convert(bit, [28744]) [UsesJava],
Convert(nvarchar(256), [28745]) [JavaVersions],
Convert(bit, [28746]) [UsesIE],
Convert(nvarchar(256), [28747]) [IEVersions],
Convert(bit, [28748]) [UsesAcrobat],
Convert(nvarchar(256), [28749]) [AcrobatVersions],
Convert(bit, [28794]) [UsesDotNet],
Convert(nvarchar(256), [28795]) [DotNetVersions],
Convert(bit, [512]) [WebApplication],
Convert(nvarchar(10), [433]) [IFAbbrev],
Convert(int, [437]) [DataID],
Convert(nvarchar(1000), [463]) [Notes],
Convert(nvarchar(512), [523]) [DataDescription],
Convert(nvarchar(256), [27991]) [SpecialNote],
Convert(bit, [28932]) [Inactive],
Convert(int, [29992]) [PatchTestedBy]
FROM (
SELECT
E.ElementID + 0 ElementID,
E.ElementTypeID,
V.AttrID,
V.Value
FROM
dbo.Element E
LEFT JOIN dbo.Value V ON E.ElementID = V.ElementID
WHERE
EXISTS (
SELECT *
FROM dbo.LayoutUsage L
WHERE
E.ElementTypeID = L.ElementTypeID
AND L.AttrLayoutID = 7
)
) X
PIVOT (
Max(Value)
FOR AttrID IN ([3], [435], [439], [438], [464], [461], [4], [28744], [28745], [28746], [28747], [28748], [28749], [28794], [28795], [512], [433], [437], [463], [523], [27991], [28932], [29992])
) P;
Aquí hay otro tipo de vista generada automáticamente creada por otro procedimiento almacenado a partir de metadatos especiales para ayudar a encontrar relaciones entre elementos que pueden tener múltiples rutas entre ellos (Específicamente: Módulo-> Servidor, Módulo-> Clúster-> Servidor, Módulo-> DBMS- > Servidor, Módulo-> DBMS-> Clúster-> Servidor):
CREATE VIEW [dbo].[Link_Module_Server]
AS
-- This view is automatically generated by the stored procedure LinkViewCreate
SELECT
ModuleID = A.ElementID,
ServerID = B.ElementID
FROM
Element A
INNER JOIN Element B
ON EXISTS (
SELECT *
FROM
dbo.Element R1
WHERE
A.ElementID = R1.ElementID1
AND B.ElementID = R1.ElementID2
AND R1.ElementTypeID = 38
) OR EXISTS (
SELECT *
FROM
dbo.Element R1
INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
WHERE
A.ElementID = R1.ElementID1
AND R1.ElementTypeID = 40
AND B.ElementID = R2.ElementID2
AND R2.ElementTypeID = 38
) OR EXISTS (
SELECT *
FROM
dbo.Element R1
INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
WHERE
A.ElementID = R1.ElementID1
AND R1.ElementTypeID = 38
AND B.ElementID = R2.ElementID2
AND R2.ElementTypeID = 3122
) OR EXISTS (
SELECT *
FROM
dbo.Element R1
INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
INNER JOIN dbo.Element C2 ON R2.ElementID2 = C2.ElementID
INNER JOIN dbo.Element R3 ON R2.ElementID2 = R3.ElementID1
WHERE
A.ElementID = R1.ElementID1
AND R1.ElementTypeID = 40
AND C2.ElementTypeID = 3080
AND R2.ElementTypeID = 38
AND B.ElementID = R3.ElementID2
AND R3.ElementTypeID = 3122
)
WHERE
A.ElementTypeID = 9
AND B.ElementTypeID = 17
El enfoque híbrido
Si DEBE tener algunos de los aspectos dinámicos de una base de datos EAV, podría considerar crear los metadatos como si tuviera una base de datos de este tipo, pero en su lugar usar el patrón de diseño de supertipo / subtipo. Sí, tendría que crear nuevas tablas y agregar, eliminar y modificar columnas. Pero con el preprocesamiento adecuado (como lo hice con las vistas automáticas de mi base de datos EAV), podría tener objetos reales con forma de tabla para trabajar. Solo que no serían tan retorcidos como los míos y el optimizador de consultas podría predicar el empuje hacia abajo a las tablas base (léase: funcionar bien con ellas). Simplemente habría una unión entre la tabla de supertipo y la tabla de subtipo. Su aplicación podría configurarse para leer los metadatos para descubrir qué se supone que debe hacer (o puede usar las vistas generadas automáticamente en algunos casos).
O, si tenía un conjunto de subtipos de varios niveles, solo unas pocas combinaciones. Por multinivel quiero decir que cuando algunos subtipos comparten columnas comunes, pero no todas, podría tener una tabla de subtipos para las que es un supertipo de algunas otras tablas. Por ejemplo, si está almacenando información sobre servidores, enrutadores e impresoras, un subtipo intermedio de "Dispositivo IP" podría tener sentido.
Voy a dar la advertencia de que aún no he creado una base de datos decorativa con supertipo / subtipo híbrido EAV como sugiero aquí para probar en el mundo real. Pero los problemas que he experimentado con EAV no son pequeños, y hacer algo es probablemente una necesidad absoluta si su base de datos va a ser grande y desea un buen rendimiento sin un hardware gigantesco y caro.
En mi opinión, el tiempo dedicado a automatizar el uso / creación / modificación de tablas de subtipos reales sería lo mejor. Centrarse en la flexibilidad impulsada por los datos hace que el EAV suene tan atractivo (y créanme, me encanta cómo, cuando alguien me pide un nuevo atributo en un tipo de elemento, puedo agregarlo en aproximadamente 18 segundos e inmediatamente pueden comenzar a ingresar datos en el sitio web ) ¡Pero la flexibilidad se puede lograr de más de una manera! El preprocesamiento es otra forma de hacerlo. Es un método tan poderoso que muy pocas personas usan, lo que brinda los beneficios de estar totalmente basado en datos pero el rendimiento de estar codificado.
(Nota: Sí, esas vistas realmente están formateadas de esa manera y las PIVOT realmente tienen activadores de actualización. :) Si alguien está realmente interesado en los terribles detalles dolorosos del desencadenante ACTUALIZACIÓN largo y complicado, avíseme y publicaré una muestra para ti)
Y una idea más
Ponga todos sus datos en una tabla. Dé a las columnas nombres genéricos y luego reutilícelos / abátelos para múltiples propósitos. Cree vistas sobre estos para darles nombres razonables. Agregue columnas cuando una columna no utilizada de tipo de datos adecuado no esté disponible y actualice sus vistas. A pesar de mi longitud sobre el subtipo / supertipo, esta puede ser la mejor manera.