Estructura de la base de datos de inventario cuando los artículos de inventario tienen atributos variables


10

Estoy construyendo una base de datos de inventario para almacenar información de hardware empresarial. Los dispositivos de la base de datos realizan un seguimiento del alcance desde estaciones de trabajo, computadoras portátiles, conmutadores, enrutadores, teléfonos móviles, etc. Estoy usando los números de serie del dispositivo como clave principal. El problema que tengo es que los otros atributos para estos dispositivos varían y no quiero tener campos en la tabla de inventario que no estén relacionados con otros dispositivos. A continuación se muestra un enlace a un ERD de parte de la base de datos (algunas relaciones FK no se muestran). Estoy tratando de configurarlo, por ejemplo, para que un dispositivo con un tipo de dispositivo de estación de trabajo no se pueda poner en la mesa de los teléfonos. Esto parece requerir el uso de muchos desencadenantes para validar el tipo o clase de dispositivo, y nuevas tablas cada vez que se rastrea un dispositivo diferente con diferentes atributos;

ERD1

Analicé la configuración de tablas de atributos que pueden asignarse a números de serie, pero que permitirían asignar atributos que no se aplican a un tipo de dispositivo a un dispositivo, por ejemplo, alguien podría asignar un atributo de número de teléfono a una estación de trabajo si quisiera . Encontré una explicación en este sitio que daba la siguiente estructura:

Muestra de widget ERD

Esta estructura funcionaría muy bien si todos los atributos fueran aplicables a los elementos que estoy almacenando. Por ejemplo, si la base de datos almacenaba solo teléfonos móviles, los atributos podrían ser cosas como pantalla táctil, panel táctil, teclado, 4G, 3G ... lo que sea. En ese caso, todos se aplican a los teléfonos. Mi base de datos tendría atributos como hostname, circuitType, phoneNumber, que solo se aplican a tipos específicos de dispositivos.

Quiero configurarlo para que solo los atributos que se aplican a un tipo de dispositivo determinado puedan asignarse a un dispositivo de ese tipo. ¿Alguna sugerencia sobre cómo configurar esta base de datos? No estoy seguro de si este es un uso adecuado de las relaciones uno a uno, o si hay una mejor manera de hacerlo. Gracias de antemano por tomarse el tiempo para investigar esto.

Estos son algunos de los otros hilos que leí. Me dieron una buena idea, pero no creo que realmente se apliquen:

/programming/9335548/how-to-structure-database-for-inventory-of-unlike-items

/programming/1249632/database-structure-for-items-with-varying-attributes

/programming/5559587/product-inventory-with-multiple-attributes

/programming/6613802/question-about-setting-up-inventory-database

/programming/514111/how-to-best-represent-items-with-variable-of-attributes-in-a-database

Respuestas:


6

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 Valuetabla, 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.


Pensé en este diseño donde cada tabla de subtipo tenía el PK del padre y los campos poco comunes. Pensé que podría poner el campo de tipo en el padre y en cada tabla de subtipo y luego poner una restricción CHECK en ellos. Decidí evitar este diseño porque requeriría una nueva tabla cada vez que se necesitara rastrear un nuevo tipo de dispositivo y muchas relaciones uno a uno. Parecía desordenado e inflexible. Aunque aprecio tu aporte.
TheSecretSquad

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 lo sería si la base de datos tuviera más de unos pocos miles de elementos. Esta experiencia me ha llevado a desear realmente evitar las bases de datos EAV en el futuro si es posible, porque son muy DIFÍCILES de consultar.
ErikE

Además, el tiempo dedicado a automatizar el uso / creación / modificación de tablas de subtipos reales sería, en mi opinión, en última instancia, el mejor.
ErikE

Después de examinar el patrón EAV, me di cuenta de que los valores de los atributos están obligados a compartir un tipo de datos (todas las cadenas en este caso). Además, consultar la configuración de EAV será una tarea difícil. Supertipo / subtipo se ve mejor. Mi pregunta ahora es si ciertas tablas solo permiten tipos de dispositivos específicos. ¿Valido esto colocando una ID de clase de dispositivo (teléfono, computadora, enrutador) en cada tabla y pongo una restricción de verificación en ese campo, o excluyo ese campo de las tablas de subtipo y uso un activador en cada una de ellas? Consulte ERD3 para referencia.
TheSecretSquad

1
Para consultar datos de EAV, no es raro construir un datamart de tablas relacionales para los datos que desea consultar y luego llenarlos con algún script. Las consultas se ejecutarán más rápido, pero solo con respecto a los datos que ingresó en el datamart, y la configuración requiere un poco de planificación.
FrustratedWithFormsDesigner

6

En su caso, el mejor enfoque es una variación del modelo Entity-Attribute-Value (EAV). Hay muchas personas que evitan el EAV porque no es útil de alguna manera y se usa mal muchas veces. Sin embargo, EAV es una solución que funciona bien para sus requisitos específicos.

La variación que desea incluir para su situación es abstraer los atributos a un nivel de distancia de sus entidades (es decir, sus artículos de inventario). Esencialmente, desea definir los tipos de dispositivos que tienen una lista de atributos. Luego, defina instancias de dispositivos que tengan valores para cada uno de los atributos que se supone que tienen los dispositivos de ese tipo.

Aquí hay un boceto ERD:

ERD

DEVICE_ATTRIBUTEcontiene los valores para cada tipo de atributo genérico. DEVICE_TYPEdefine la lista de atributos genéricos que se aplican a un tipo de dispositivo dado (estos son los TYPICAL_DEVICE_ATTRIBUTEs.

Esto le permite controlar qué atributos deben completarse para un dispositivo, al tiempo que permite que los dispositivos de diferentes tipos tengan diferentes listas de atributos. También le facilita la comparación entre dispositivos al alinear sus atributos entre sí.


Esto se parece a lo que recomienda ssmusoke. Cambié mi ERD usando su recomendación y parece que coincide con la tuya. No dude en consultar el nuevo RD en http://www.dividegraphics.com/ERD2.jpg y enviar sus comentarios.
TheSecretSquad

@reallythecrash: tiene razón, sugiero el mismo enfoque básico que ssmusoke, simplemente tomé un rumbo diferente en mi respuesta con la esperanza de facilitar la comprensión tanto de la estructura del modelo como de las razones para usar EAV que Mucha gente (injustamente) denuncia que es un antipatrón.
Joel Brown

Después de investigar un poco, veo por qué las personas podrían considerar el EAV como un antipatrón. Creo que es simple almacenar datos usando EAV, pero especialmente complejo para consultar y mantener tipos de datos. Creo que es un patrón con un propósito limitado, y debería ser utilizado por desarrolladores experimentados que puedan implementarlo adecuadamente, es decir, yo no. Probablemente optaré por el paradigma de supertipo / subtipo.
TheSecretSquad

@JoelBrown: ¿qué software usaste para dibujar ese diagrama ?, se ve genial.
Vidar

@Vidar: utilicé Visio con formas inteligentes ERD que creé para usar las convenciones visuales de James Martin y dibujé con un patrón de línea personalizado que es incompleto. Creo que esta es una buena herramienta para usar con modelos de datos rápidos / borradores. Cuando el diagrama es demasiado formal, puede hacer que algunas personas piensen que está terminado, por lo que algo incompleto ayuda a evitar que la gente salte a conclusiones sobre qué tan firme / terminado es un modelo de datos.
Joel Brown

1
  1. El enfoque general es el siguiente:

a) Un enfoque modelo Entidad-Atributo-Valor para abordar los atributos de los diferentes dispositivos a un tipo de dispositivo. Cada tipo de dispositivo tendrá una lista de atributos cuyos valores rastrea

b) Para cada tipo de dispositivo, realiza un seguimiento de los detalles del inventario por número de serie que corresponde a un solo dispositivo.

  1. Entonces terminarías con las siguientes tablas:

a) Atributos: defina los atributos para todas las columnas de los dispositivos (todo lo que figura en esta tabla): id, nombre, descripción

b) Atributos del artículo: define los atributos permitidos para un dispositivo específico: itemid, attributeid

c) Definición del elemento: define un elemento como Black Berry Torch 4500, Iphone 4S, Iphone 3S, etc. - id, nombre, descripción, categoryid (si desea agregar categorías como teléfonos móviles, interruptores, etc.)

d) Dispositivos: los dispositivos individuales: id, itemid, fecha de inventario, fecha de desactivación, número de serie ... (básicamente todos los demás atributos para un dispositivo)

Si desea realizar un seguimiento de cualquier otra información sobre las transiciones del dispositivo, puede agregar más tablas vinculadas al dispositivo según lo necesite.


Gracias por su aporte. Esto está en línea con lo que estoy buscando, simplemente no podía entender cómo hacerlo. Cambié mi ERD para reflejar sus especificaciones. Parece que requiere más trabajo ingresar todos los atributos permitidos para cada tipo de dispositivo, pero también parece que ofrece la máxima flexibilidad. Voy a hacer un pequeño prototipo para ver si funciona como creo. Gracias de nuevo. Subí un ERD con los cambios si quieres echar un vistazo y avisarme si estoy en el camino correcto. http://www.dividegraphics.com/ERD2.jpg
TheSecretSquad

Sí, estás en el camino correcto.
Stephen Senkomago Musoke

EAV ofrecerá mucha flexibilidad, pero también tiene muchos más metadatos para mantenerlo funcionando.
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner parece inevitable cuando el sistema almacena una amplia gama de artículos, teléfonos, interruptores, PC, computadoras portátiles, etc. Mejor diría más metadatos que más tablas
Stephen Senkomago Musoke

1
@ssmusoke: De acuerdo, pero quería enfatizar ese punto porque he visto que la gente no se da cuenta de la importancia de los metadatos, y luego su implementación EAV se convierte en una pesadilla.
FrustratedWithFormsDesigner
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.