Design Parts DB

8

Estou desenvolvendo uma ferramenta que lida com peças (elétricas). As peças podem ser criadas, visualizadas, modificadas, excluídas, agrupadas e assim por diante ...

Para tornar essa pergunta útil para futuros visitantes, eu gostaria de manter essa pergunta universal, pois o gerenciamento de partes em um banco de dados é muito comum, independentemente de quais partes estejam no banco de dados (CDs, carros, comida, estudantes, ...).

Estou pensando em três projetos diferentes de banco de dados:

  1. Usando uma tabela de peças e tabelas derivadas para atributos de peças especializados.

    Parts      (id, part_type_id, name)
    PartTypes  (id, name)
    Wires      (id, part_id, lenght, diameter, material)
    Contacts   (id, part_id, description, picture)
    
  2. Usando apenas tabelas de peças especializadas.

    Wires      (id, name, lenght, diameter, material)
    Contacts   (id, name, description, picture)
    
  3. Usando uma tabela Parts-, PartTypes-, ValueTypes- e PartValues ​​que contêm todos os valores.

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

Qual prefere e por quê? Ou existe um melhor?
Estou preocupado com as consultas ao banco de dados. Não quero que as consultas se tornem muito lentas ou complicadas.

Atualizar

O número de tipos no banco de dados é praticamente dado e estático, pois se baseia em um padrão internacional e será aprimorado raramente.

juergen d
fonte
Trata-se estritamente de bancos de dados SQL (puramente relacionais) ou o NOSQL DB também é uma opção?
C-smile
@ c-smile: Como ainda não trabalhei com o NOSQL, não sei se é uma opção. Eu estou aberto a tudo.
precisa

Respostas:

16

Opção 3 : (algumas vezes) a
opção 3 é o design "EAV" . Em teoria, é bom porque os campos são retirados da estrutura da tabela e se tornam dados. Mas dá um desempenho terrível. Também não permite o uso de indexação adequada. E isso torna as consultas muito mais complicadas.

Eu usaria apenas o EAV em circunstâncias especiais. Eu usei o EAV para calcular as peças auxiliares necessárias para os pedidos e funcionou bem. Mas esteja muito cansado de usá-lo como design para suas tabelas principais.

Opção 2 : (nunca?) A
opção 2 é um não não. E os campos compartilhados? Você duplicará a estrutura da tabela para cada campo compartilhado? Isso exigiria a inclusão de sindicatos nos relatórios de todo o sistema.

Opção 1 : (vencedor!) A
opção 1 pode parecer um pouco básica demais, mas provavelmente é a melhor aposta para suas tabelas principais. Todas as partes usam a mesma tabela mestre para campos compartilhados, evitando assim uniões em seus relatórios. Possui ótimo desempenho, permitindo o uso adequado da indexação. As consultas são no estilo tradicional e são simples.

A desvantagem da opção 1 é que você não pode adicionar campos dinamicamente. Mas você realmente quer? Ao adicionar campos dinamicamente, você está executando o design do banco de dados em tempo de execução.

mike30
fonte
+1, mas veja minha resposta para ver qual pode ser a lógica por trás da opção 2.
Doc Brown
Depois de pensar um pouco e com base nas observações do OP de que as partes são um padrão fixo absoluto por regulamento, concordo com as opções 1 e 1 para obter uma boa resposta, embora ele deva ter em mente que a opção 3 pode ser uma migração no futuro, também importante porque ninguém mais o mencionou: As junções externas têm características de desempenho ruins em geral e devem ser evitadas sempre que possível. Basta adicionar isso porque a Opção nº 1 envolverá junções externas, mas, nesse caso, provavelmente ainda vale o custo, pois A opção 3 tem suas próprias armadilhas de desempenho.
Jimmy Hoffa
2
A opção 1 pode parecer muito básica? De jeito nenhum, essa é definitivamente a maneira de fazer isso. Jimmy está errado, as junções externas não têm características de desempenho ruins em geral. Contanto que você indexe corretamente, tudo ficará bem.
Rocklan
6

Eu tenderia a não opção # 3.

A opção 3 é a configuração do par nome-valor que viola a normalização.

Idealmente, tenta-se ter algum nível de normalização do banco de dados. Busque a normalização completa e desnormalize conforme necessário quando for identificado para problemas de customização ou desempenho.

Considere a consulta "qual é o nome e o ID da peça para todos os fios de cobre"

A estrutura nº 1 é

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

A estrutura 2 é

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

A estrutura 3 é

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 também a complicação de inserções e exclusões do sistema.

Algumas leituras adicionais sobre por que não # 3 - A maldição do par de valores de nomes


fonte
2
Sim, o par nome-valor é mau, acho que todos concordam, mas continua porque é um mal necessário. Talvez o número 3 seja desnecessário aqui, mas parece muito com as estruturas da tabela que vi tornarem-se insustentáveis ​​e acabaram precisando de desnormalização no formato do par de valores de nomes. Se for no entanto fixa então talvez # 1 é a abordagem correta (assumindo consultas gostaria de agir de acordo com agregados de diferentes partes, caso contrário # 2 é bom)
Jimmy Hoffa
Além disso, você não está usando junções aqui, o que acaba colocando um trabalho indevido na cláusula where que entraria na junção como a part_type_id = part_types.ide value_types.id = part_values.type_value_idsão ambas cláusulas de junção, deixando a onde para onde o tipo de peça é wire, o tipo de valor é material e o valor é cobre que é relativamente sucinta
Jimmy Hoffa
@ JimmyHoffa Eu estava apenas fazendo uma versão abreviada rápida para mostrar como seria, em vez do sql ideal. A terceira opção que eu vi na estrutura da tabela do Redmine, em que pares nome / valor são adicionados ao sistema rapidamente. Ter que fazer atualizações do banco de dados para adicionar um novo campo personalizado é impraticável - portanto, o valor do nome é a estrutura apropriada. No entanto, torna as consultas ao banco de dados um pouco mais lentas (os índices não são tão felizes quanto o tipo se torna uma string para tudo) e as consultas são um pouco feias.
1
Na última vez que fiz a opção 3, estava no MSSQL e usei o tipo SQL_Variant, acredito que índices como esse são um pouco mais do que strings, porque os catalogam por tipo e valor, se não me engano, embora ainda seja mais complexo. abordagem e como você disse que é melhor quando você sabe que haverá um crescimento consistente de novos tipos, da última vez que fiz isso, estava convertendo uma tabela com 60 colunas; 1 para cada chave que cresceu consistentemente, para que esses cenários ocorram obviamente, mas talvez esse não seja um deles, que caberia ao OP identificar.
Jimmy Hoffa
4

Eu vou opção 3

A opção 1 é ruim porque você não deseja que suas junções sejam baseadas em um valor arquivado. (ie If type ="Wire" join to TblWire)

A opção 2 é ruim porque você não tem como relatar seu inventário como um todo

Idiotas
fonte
Observe também que a opção 3 tem as melhores características de manutenção para novos atributos de peças, refiro-me a este formulário (embora eu tenha certeza de que existe um termo comum entre os DBAs para essa estrutura que estou perdendo) como um formulário dinâmico, porque é um eixo dinâmico da estrutura mais comum que você detalhou nos itens 1 e 2 e, muitas vezes, as pessoas criam o número 1 apenas para acabar adicionando novas tabelas / colunas para novos tipos, de modo que muitas vezes precisam girar para o número 3 depois de fazerem uma grande bagunça eles não podem mais manter.
Jimmy Hoffa
Para a opção 1, você nunca precisaria de um "se" no tipo antes de uma associação. Se ingressar com sucesso, é o tipo. As junções em si podem substituir os filtros. Você pode ir tão longe a ponto de não armazenar mais o tipo.
mike30
@ Mike e se ele quiser 2 tipos de produtos? Se o cabo se unir a "Cabos", se os conectores se unirem a "conectores", se ele se unir aos dois, não receberá nada! Se ele sair junta-se, ele recebe duplicados!
Idiotas
@Morons. À esquerda, junte-se ao mestre nas sub-tabelas. Filtre onde calbles.ID não é nulo e connectors.ID não é nulo. Viola! Usando o sucesso da junção como o filtro.
mike30
2
@ Morons: repetir a palavra "pesadelo" não a torna mais verdadeira. Se for necessário modificar "todo o código" quando um novo tipo for criado, nada tem a ver com "opção 1" ou "opção 3". Ele tem que fazer o quão bem o código está estruturado. E que é preciso modificar o código em alguns lugares quando um novo requisito chega não é "um pesadelo", isso é normal (e necessário também para a opção 3). Antes de discutir mais, sugiro que você se informe sobre os casos em que o padrão Entity-Attribute-Value é apropriado e quando não . O EAV às vezes é um anti-padrão.
Doc Brown
4

Eu começaria com um modelo de dados / objetos que permitisse a herança e usaria um mapeamento objeto-relacional padrão . Dessa forma você obtém uma classe base Partse sub-classes como Wires, Contactsetc. Agora, se você aplicar uma estratégia de "mapa-cada-classe-a-própria-table", você tem a opção 1, que é a solução mais "normalizado" e deve ser a estratégia canônica se você não tiver mais informações sobre as consultas que espera.

A opção 2 é o que você obtém ao aplicar uma abordagem "mapear cada classe de concreto para a própria tabela". Isso pode evitar "junções" e pode ter um desempenho melhor de algum tipo se as consultas (especialmente consultas para apenas um "tipo de peça"), por outro lado, tornam o manuseio genérico com todas as peças mais difícil e mais lento. Evite isso se você não tiver motivos especiais para isso.

A opção 3 é o que você precisa apenas se desejar que o usuário altere o número de tipos de peças em tempo de execução - se você não espera esse requisito, a opção 3 será um exemplo perfeito para coisas com excesso de engenharia.

Doc Brown
fonte
2

Com o banco de dados NOSQL DB (como o MongoDB, por exemplo), você precisará apenas de um conjunto denominado "Parts". Cada parte desse conjunto é denominada documento - registro com conjunto de campos variável:

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

Eu acho que esse é o armazenamento de dados mais natural para a tarefa que você descreve.

sorriso
fonte
2

Definitivamente, vá com a opção 1, mas com algumas modificações muito 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)

Em seguida, você pode usar restrições CHECK e valores DEFAULT para garantir que o part_type_id esteja correto e, em seguida, você pode ingressar em part_type_id e part_id. Isso evita ter uma associação condicional com base em apenas uma tabela e, se você precisar adicionar um part_type_id aos fios (digamos que estamos subdividindo essa parte e adicionando outra tabela de atributos estendidos), as restrições padrão e de verificação podem ser alteradas.

Chris Travers
fonte
Você pode também (com segurança - a menos que algum ORM requer chaves primárias de coluna única) remover o wires.ide contacts.idcomo a (part_id, part_type_id)combinação será suficiente para identificar inequivocamente uma parte.
ypercubeᵀᴹ
@ypercube, claro, mas como part_id é único nesse caso, use-a como chave primária, com um índice exclusivo secundário em part_id, part_type_id, se desejar.
Chris Travers
1

A opção 3 é mais genérica e pode acomodar mais casos de uso.

Na opção 3, você pode precisar de mais junções e consultas complexas para recursos simples; na opção 2, você precisa de consultas complexas para recursos "grandes", como inventário e relatórios, e pode precisar de uniões para fazer isso.

Você sempre pode simplificar suas consultas nas opções 3 usando Views, se muitas vezes precisar apenas do Wire ou Contact, faça uma View para cada uma delas. Você pode otimizá-lo se for necessário.

RMalke
fonte