Design Parts DB

8

Estoy desarrollando una herramienta que maneja piezas (eléctricas). Las partes se pueden crear, ver, modificar, eliminar, agrupar, etc.

Para que esta pregunta sea útil para futuros visitantes, me gusta mantener esta pregunta universal ya que administrar partes en un DB es muy común, sin importar qué partes estén en el DB (CD, autos, comida, estudiantes, ...).

Estoy pensando en 3 diseños diferentes de DB:

  1. Usar una tabla de partes y tablas derivadas para atributos de parte especializados.

    Parts      (id, part_type_id, name)
    PartTypes  (id, name)
    Wires      (id, part_id, lenght, diameter, material)
    Contacts   (id, part_id, description, picture)
    
  2. Utilizando solo tablas de piezas especializadas.

    Wires      (id, name, lenght, diameter, material)
    Contacts   (id, name, description, picture)
    
  3. Usando una tabla Parts-, PartTypes-, ValueTypes- y PartValues ​​que contiene todos los valores.

    PartTypes  (id, name)
    ValueTypes (id, part_type_id, name)
    Parts      (id, part_type_id, name)
    PartValues (part_id, value_type_id, value)
    

¿Cuál preferir y por qué? ¿O hay uno mejor?
Estoy preocupado por las consultas DB. No quiero que las consultas se vuelvan demasiado lentas o complicadas.

Actualizar

El número de tipos en la base de datos es bastante dado y estático, ya que se basa en un estándar internacional y rara vez se mejorará.

juergen d
fuente
¿Se trata estrictamente de bases de datos SQL (puramente relacionales) o NOSQL DB también es una opción?
c-smile
@ c-smile: Como todavía no he trabajado con NOSQL, no sé si es una opción. Estoy abierto a todo.
juergen d

Respuestas:

16

Opción 3 : (a veces) La
opción 3 es el diseño "EAV" . En teoría es bueno porque los campos se sacan de la estructura de la tabla y se convierten en datos. Pero da un rendimiento terrible. También prohíbe el uso de una indexación adecuada. Y hace que las consultas sean mucho más complicadas.

Solo usaría EAV en circunstancias especiales. He usado EAV para calcular las piezas auxiliares necesarias para los pedidos y funcionó bien. Pero esté muy cansado de usarlo como diseño para sus tablas principales.

Opción 2 : (¿nunca?) La
opción 2 es un no no. ¿Qué pasa con los campos compartidos? ¿Va a duplicar la estructura de la tabla para cada campo compartido? Requeriría que incluya sindicatos en los informes de todo el sistema.

Opción 1 : (¡ganador!) La
opción 1 puede parecer un poco demasiado básica, pero probablemente sea la mejor apuesta para sus mesas principales. Todas las partes usan la misma tabla maestra para los campos compartidos, por lo que se evitan las uniones en sus informes. Tiene un gran rendimiento que permite el uso adecuado de la indexación. Las consultas son al estilo tradicional y son simples.

La desventaja de la opción 1 es que no puede agregar campos dinámicamente. ¿Pero realmente quieres? Al agregar campos dinámicamente, está realizando el diseño de la base de datos en tiempo de ejecución.

mike30
fuente
+1, pero mira mi respuesta para ver cuál es la razón detrás de la opción # 2.
Doc Brown
Después de pensar un poco y de acuerdo con las notas del OP que las partes son un estándar fijo absoluto por regulación, acepto la Opción # 1 y +1 para la buena respuesta, aunque definitivamente debe tener en cuenta que la Opción # 3 puede ser una migración punto en el futuro, también importante porque nadie más lo mencionó: las uniones externas tienen características de bajo rendimiento en general y deben evitarse siempre que sea posible Solo agregue eso porque la Opción # 1 involucrará uniones externas, pero en este caso probablemente valga la pena el costo. La opción n. ° 3 tiene sus propias dificultades de rendimiento.
Jimmy Hoffa
2
¿La opción 1 puede parecer demasiado básica? De ninguna manera, esa es definitivamente la forma de hacerlo. Jimmy está equivocado, las uniones externas no tienen características de bajo rendimiento en general. Mientras indexes correctamente, estará bien.
Rocklan
6

Tendería a no tener la opción # 3.

La opción n. ° 3 es la configuración del par nombre-valor que viola la normalización.

Idealmente, uno intenta tener algún nivel de normalización de la base de datos. Esforzarse por la normalización completa y luego desnormalizar según sea necesario cuando se identifica por problemas de personalización o rendimiento.

Considere la consulta "¿Cuál es el nombre y las ID de piezas para todos los cables de cobre"

La estructura n. ° 1 es

select
  name, parts.id
from
  wire, parts
where
  wire.material = 'copper'
  and wire.part_id = parts.id

Estructura # 2 es

select id, name from wire where material = 'copper'

Estructura # 3 es

select
  parts.name,
  parts.id,
from
  parts, part_types, part_values, value_types
where
  part_types.name = "wire"
  and parts.part_type_id = part_types.id
  and value_types.name = "material"
  and value_types.id = part_values.type_value_id
  and part_values.value = "copper"

Considere también la complicación de las inserciones y eliminaciones del sistema.

Algunas lecturas adicionales sobre por qué no # 3 - La maldición del par de valores de nombre


fuente
2
Sí, el nombre valor par es malo, creo que todos están de acuerdo, pero continúa porque es un mal necesario. Tal vez el n. ° 3 sea innecesario aquí, pero parece que las estructuras de la tabla que he visto se vuelven insostenibles y terminaron necesitando desnormalización en la forma del par de nombre y valor. Sin embargo, si se soluciona, tal vez el n. ° 1 sea el enfoque correcto (suponiendo que las consultas deseen actuar sobre agregados de diferentes partes, de lo contrario, el n. ° 2 está bien)
Jimmy Hoffa
Además, no está usando uniones aquí, lo que termina poniendo un trabajo indebido en la cláusula where que iría a la unión como el part_type_id = part_types.idy value_types.id = part_values.type_value_idambas son cláusulas de unión que dejan el lugar donde el tipo de parte es alambre, el tipo de valor es material y el valor es cobre que es relativamente sucinto
Jimmy Hoffa
@JimmyHoffa Estaba haciendo una versión abreviada rápida para mostrar cómo se vería en lugar de sql ideal. La tercera opción que he visto en la estructura de la tabla de Redmine, donde se agregan pares de nombre / valor al sistema sobre la marcha. Tener que hacer actualizaciones de la base de datos para agregar un nuevo campo personalizado no es práctico, por lo que el valor del nombre es la estructura adecuada. Sin embargo, hace que las consultas de la base de datos sean un poco más lentas (los índices no son tan felices como el tipo se convierte en cadenas para todo) y las consultas son un poco feas.
1
La última vez que hice la opción # 3 estaba en MSSQL y usé el tipo SQL_Variant, creo que los índices como ese son un poco más que cadenas porque los cataloga por tipo y luego por valor si no me equivoco, aunque aún es más complejo enfoque y, como dijiste, es mejor cuando sabes que habrá un crecimiento constante de nuevos tipos, la última vez que hice esto fue convertir una tabla con 60 columnas; 1 para cada clave que creció constantemente, por lo que estos escenarios obviamente suceden, pero quizás este no sea uno de ellos, eso dependería del OP para identificarlo.
Jimmy Hoffa
4

Voy opción 3

La opción 1 es mala porque no desea que sus combinaciones se basen en un valor archivado. (es decir If type ="Wire" join to TblWire)

La opción 2 es mala porque no tiene forma de informar sobre su inventario en su conjunto

Imbéciles
fuente
También tenga en cuenta que la opción 3 tiene las mejores características de mantenimiento para los nuevos atributos de parte, me refiero a este formulario (aunque estoy seguro de que hay un término común entre los DBA para esa estructura que me falta) como un formulario pivotado porque es un pivote de la estructura más común que detallaste en el n. ° 1 y n. ° 2, y muchas veces las personas crean el n. ° 1 solo para terminar agregando nuevas tablas / columnas para nuevos tipos con tanta frecuencia que tienen que pasar al n. ° 3 después de haber hecho un gran desastre ya no pueden mantener.
Jimmy Hoffa
Para la opción 1, nunca necesitaría un "si" en el tipo antes de una unión. Si se une con éxito, entonces es del tipo. Se une a sí mismo podría reemplazar los filtros. Podrías ir tan lejos como para no almacenar más el tipo.
mike30
@mike ¿y si quiere 2 tipos de productos? Si el cable se une a "Cables", si los conectores se unen a "conectores", si se une a ambos, ¡no recibe nada! Si él se une, ¡obtiene Duplicados!
Morons
@Lorons. Izquierda unirse al maestro con las sub-tablas. Filtre donde calbles.ID no es nulo y conectores.ID no es nulo. ¡Viola! Usando el éxito de la unión como filtro.
mike30
2
@Morons: repetir la palabra "pesadilla" no lo hace más cierto. Si uno tiene que modificar "todo el código" cuando se crea un nuevo tipo, no tiene nada que ver con "opción 1" u "opción 3". Tiene que ver qué tan bien está estructurado el código. Y eso tiene que modificar el código en algunos lugares cuando llega un nuevo requisito no es "una pesadilla", eso es normal (y necesario también para la opción 3). Antes de seguir discutiendo, le sugiero que se informe sobre los casos en que el patrón Entidad-Atributo-Valor es apropiado, y cuándo no . EAV es a veces un antipatrón.
Doc Brown
4

Comenzaría con un modelo de datos / objetos que permite la herencia, y luego usaría un mapeo relacional de objetos estándar . De esta forma, obtienes una clase base Partsy subclases como Wires, Contactsetc. Ahora, si aplicas una estrategia de "asignar cada clase a la propia tabla", obtienes la opción 1, que es la solución más "normalizada" y debería ser la estrategia canónica si no tiene más información sobre las consultas que espera.

La opción 2 es lo que obtienes al aplicar un enfoque de "mapa-cada-clase-concreta-a-propia-tabla". Esto puede evitar "uniones" y puede funcionar mejor para algún tipo si las consultas (especialmente consultas para un solo "tipo de parte"), por otro lado, hace que el manejo genérico con todas las partes sea más difícil y más lento. Evita esto si no tienes razones especiales para ello.

La opción 3 es lo que necesita solo si desea que el usuario cambie la cantidad de tipos de piezas en tiempo de ejecución; si no espera ese requisito, la opción 3 será un ejemplo perfecto para cosas de ingeniería excesiva.

Doc Brown
fuente
2

Con la base de datos NOSQL DB (como MongoDB, por ejemplo) solo necesitará un conjunto llamado "Partes". Cada parte de ese conjunto se denomina documento - registro con un conjunto variable de campos:

{
   "_id": ObjectId("4efa8d2b7d284dea1"),
   "partType": "wire",
   "length": 102.5,
   "diameter": 1.5,
   "material": "silver"
}, 
{
   "_id": ObjectId("4efa8d2b7d284sjsq23d"),
   "partType": "contact",
   "description": "something",
   "picture": Binary(...)
}, 

Creo que este es el almacenamiento de datos más natural para la tarea que describe.

c-smile
fuente
2

Definitivamente vaya con la opción 1 pero con algunas modificaciones muy simples:

Parts      (id, part_type_id, name)
PartTypes  (id, name)
Wires      (id, part_id, part_type_id, lenght, diameter, material)
Contacts   (id, part_id, part_type_id, description, picture)

Luego puede usar las restricciones CHECK y los valores DEFAULT para asegurarse de que part_type_id sea correcto, y luego puede unirse tanto en part_type_id como en part_id. Esto evita tener una unión condicional basada en una sola tabla, y si necesita agregar un part_type_id a los cables (digamos que estamos subdividiendo esa parte y agregando otra tabla de atributos extendidos), las restricciones predeterminadas y de verificación se pueden cambiar.

Chris Travers
fuente
También puede (de forma segura, a menos que algunos ORM requieran claves primarias de una sola columna) eliminar el wires.idy contacts.idya que la (part_id, part_type_id)combinación será suficiente para identificar de forma exclusiva una parte.
ypercubeᵀᴹ
@ypercube, claro, pero dado que part_id es único en este caso, simplemente úselo como clave principal, con un índice único secundario en part_id, part_type_id si lo desea.
Chris Travers
1

La opción 3 es más genérica y puede acomodar más casos de uso.

Al ir a la opción 3, es posible que necesite más uniones y consultas complejas para funciones simples, en la opción 2 necesitaría consultas complejas para características "grandes" como inventario e informes, y es posible que necesite usar uniones para lograr eso.

Siempre puede simplificar sus consultas en las opciones 3 usando Vistas, si a menudo solo necesita el Cable o el Contacto, haga una Vista para cada uno de ellos. Puede optimizarlo si es necesario.

RMalke
fuente