Estrutura do banco de dados de inventário quando itens de inventário têm atributos variados

10

Estou criando um banco de dados de inventário para armazenar informações de hardware da empresa. Os dispositivos que o banco de dados controla o alcance das estações de trabalho, laptops, comutadores, roteadores, telefones celulares, etc. Estou usando os números de série do dispositivo como chave primária. O problema que estou enfrentando é que os outros atributos desses dispositivos variam e não quero ter campos na tabela de inventário que não sejam relacionados a outros dispositivos. Abaixo está um link para um ERD de parte do banco de dados (algumas relações FK não são mostradas). Estou tentando configurá-lo, por exemplo, para que um dispositivo com um tipo de estação de trabalho não possa ser colocado na tabela de telefones. Isso parece exigir o uso de muitos gatilhos para validar o tipo ou classe de dispositivo, e novas tabelas a qualquer momento serão rastreadas um dispositivo diferente com atributos diferentes;

ERD1

Examinei a configuração de tabelas de atributos que podem ser mapeadas para números de série, mas isso permitiria que atributos que não se aplicam a um tipo de dispositivo sejam atribuídos a um dispositivo, por exemplo, alguém poderia atribuir um atributo de número de telefone a uma estação de trabalho, se quisesse . Encontrei uma explicação neste site que forneceu a seguinte estrutura:

Exemplo de widget ERD

Essa estrutura funcionaria muito bem se todos os atributos fossem aplicáveis ​​aos itens que estou armazenando. Por exemplo, se o banco de dados estivesse armazenando apenas telefones celulares, os atributos poderiam ser coisas como tela sensível ao toque, trackpad, teclado, 4G, 3G ... o que for. Nesse caso, todos eles se aplicam aos telefones. Meu banco de dados teria atributos como nome do host, circuitType, phoneNumber, que se aplicam apenas a tipos específicos de dispositivos.

Quero configurá-lo para que apenas os atributos que se aplicam a um determinado tipo de dispositivo possam ser atribuídos a um dispositivo desse tipo. Alguma sugestão sobre como configurar esse banco de dados? Não tenho certeza se esse é um uso adequado dos relacionamentos pessoais ou se existe uma maneira melhor de fazer isso. Agradecemos antecipadamente por reservar um tempo para analisar isso.

Aqui estão alguns dos outros tópicos que li. Eles me deram uma boa ideia, mas não acho que eles realmente se apliquem:

/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

TheSecretSquad
fonte

Respostas:

6

Supertipo / Subtipo

Que tal olhar para o padrão de supertipo / subtipo? Colunas comuns vão em uma tabela pai. Cada tipo distinto possui sua própria tabela com o ID do pai como sua própria PK e contém colunas exclusivas não comuns a todos os subtipos. Você pode incluir uma coluna de tipo nas tabelas pai e filho para garantir que cada dispositivo não possa ter mais de um subtipo. Faça um FK entre os filhos e o pai em (ItemID, ItemTypeID). Você pode usar FKs para as tabelas de supertipo ou subtipo para manter a integridade desejada em outro lugar. Por exemplo, se o ItemID de qualquer tipo for permitido, crie o FK para a tabela pai. Se apenas SubItemType1 puder ser referenciado, crie o FK para essa tabela. Eu deixaria o TypeID fora das tabelas de referência.

Nomeação

Quando se trata de nomear, você tem duas opções, como eu a vejo (uma vez que a terceira opção de apenas "ID" é, na minha opinião, um forte anti-padrão). Chame a chave do subtipo ItemID como está na tabela pai ou o nome do subtipo, como DoohickeyID. Depois de pensar um pouco e ter alguma experiência com isso, defendo o nome de DoohickeyID. A razão para isso é que, embora possa haver confusão sobre a tabela de subtipos realmente disfarçada que contém itens (em vez de chaves Doohickeys), isso é um pequeno negativo em comparação com quando você cria um FK para a tabela Doohickey e os nomes das colunas não Combine!

Para EAV ou não para EAV - Minha experiência com um banco de dados EAV

Se o EAV é o que você realmente precisa fazer, é o que você deve fazer. Mas e se não fosse o que você tinha que fazer?

Criei um banco de dados EAV que está sendo usado em um negócio. Graças a Deus, o conjunto de dados é pequeno (embora haja dezenas de tipos de itens), portanto o desempenho não é ruim. Mas seria ruim se o banco de dados tivesse mais de alguns milhares de itens! Além disso, as tabelas são tão difíceis de consultar. Essa experiência me levou a realmente desejar evitar bancos de dados EAV no futuro, se possível.

Agora, no meu banco de dados, criei um procedimento armazenado que cria automaticamente modos de exibição PIVOT para cada subtipo existente. Eu posso apenas consultar a partir de AutoDoohickey. Meus metadados sobre os subtipos possuem uma coluna "ShortName" que contém um nome seguro para objetos, adequado para uso em nomes de exibição. Eu até fiz as visualizações atualizáveis! Infelizmente, você não pode atualizá-los em uma associação, mas PODE inserir neles uma linha já existente, que será convertida em um UPDATE. Infelizmente, você não pode atualizar apenas algumas colunas, porque não há como indicar à VIEW quais colunas você deseja atualizar com o processo de conversão INSERT em UPDATE: um valor NULL se parece com "atualizar esta coluna para NULL", mesmo que você queria indicar "Não atualize esta coluna".

Apesar de toda essa decoração para facilitar o uso do banco de dados EAV, ainda não uso essas visualizações na maioria das consultas normais, porque é LENTO. As condições da consulta não são predicadas enviadas de volta para a Valuetabela, portanto, é necessário criar um conjunto de resultados intermediários de todos os itens do tipo dessa exibição antes de filtrar. Ai. Então, eu tenho muitas, muitas consultas com muitas, muitas junções, cada uma saindo para obter um valor diferente e assim por diante. Eles têm um desempenho relativamente bom, mas ai! Aqui está um exemplo. O SP que cria isso (e seu gatilho de atualização) é uma fera gigante, e tenho orgulho disso, mas não é algo que você queira tentar manter.

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;

Aqui está outro tipo de exibição gerada automaticamente criada por outro procedimento armazenado a partir de metadados especiais para ajudar a encontrar relacionamentos entre itens que podem ter vários caminhos entre eles (Especificamente: Módulo-> Servidor, Módulo-> Cluster-> Servidor, Módulo-> DBMS- > Servidor, Módulo-> DBMS-> Cluster-> 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

A abordagem híbrida

Se você DEVE ter alguns dos aspectos dinâmicos de um banco de dados EAV, considere criar os metadados como se tivesse um banco de dados, mas, na verdade, usar o padrão de design de supertipo / subtipo. Sim, você precisaria criar novas tabelas, adicionar, remover e modificar colunas. Mas com o pré-processamento adequado (como fiz com as visualizações automáticas do meu banco de dados EAV), você pode ter objetos reais em forma de tabela para trabalhar. Só que eles não seriam tão complicados quanto o meu e o otimizador de consultas poderia predizer o push down para as tabelas base (leia-se: tenha um bom desempenho com elas). Haveria apenas uma junção entre a tabela de supertipos e a tabela de subtipos. Seu aplicativo pode ser configurado para ler os metadados para descobrir o que ele deve fazer (ou pode usar as visualizações geradas automaticamente em alguns casos).

Ou, se você tivesse um conjunto de subtipos de vários níveis, apenas algumas associações. Por vários níveis, quero dizer quando alguns subtipos compartilham colunas comuns, mas não todas, você pode ter uma tabela de subtipos para aquelas que são elas próprias um supertipo de algumas outras tabelas. Por exemplo, se você estiver armazenando informações sobre servidores, roteadores e impressoras, um subtipo intermediário de "Dispositivo IP" poderá fazer sentido.

Darei a ressalva de que ainda não criei um banco de dados híbrido e decorado com meta-EAV supertipo / subtipo, como sugeri aqui para experimentar no mundo real. Mas os problemas que experimentei com EAV não são pequenos, e fazendo algo é provavelmente um absoluto must se o seu banco de dados vai ser grande e você quer um bom desempenho sem algum hardware gigantesca cara louco.

Na minha opinião, o tempo gasto automatizando o uso / criação / modificação de tabelas reais de subtipos seria o melhor. O foco na flexibilidade gerada pelos dados faz com que o EAV pareça tão atraente (e acredite, eu amo como quando alguém me pede um novo atributo em um tipo de elemento, eu posso adicioná-lo em cerca de 18 segundos e eles podem começar imediatamente a inserir dados no site ) Mas a flexibilidade pode ser realizada de mais de uma maneira! O pré-processamento é outra maneira de fazer isso. É um método tão poderoso que poucas pessoas usam, oferecendo os benefícios de ser totalmente orientado a dados, mas o desempenho de ser codificado.

(Nota: Sim, essas visualizações são realmente formatadas dessa forma e as do PIVOT realmente têm gatilhos de atualização. :) Se alguém estiver realmente interessado nos horríveis detalhes dolorosos do longo e complicado gatilho UPDATE, avise-me e publicarei uma amostra para você.)

E mais uma ideia

Coloque todos os seus dados em uma tabela. Atribua nomes genéricos às colunas e, em seguida, reutilize / abuse deles para várias finalidades. Crie visualizações sobre elas para fornecer nomes sensíveis. Adicione colunas quando uma coluna não utilizada do tipo de dados adequado não estiver disponível e atualize suas visualizações. Apesar do meu comprimento sobre subtipo / supertipo, esse pode ser o melhor caminho.

ErikE
fonte
Pensei nesse design em que cada tabela de subtipo tinha a PK do pai e os campos incomuns. Eu pensei que poderia colocar o campo de tipo no pai e cada tabela de subtipo e, em seguida, colocar uma restrição CHECK neles. Decidi evitar esse design, pois exigiria uma nova tabela sempre que um novo tipo de dispositivo precisar ser rastreado e muitos relacionamentos individuais. Parecia confuso e inflexível. Agradeço sua opinião.
TheSecretSquad
Criei um banco de dados EAV que está sendo usado em um negócio. Graças a Deus, o conjunto de dados é pequeno (embora haja dezenas de tipos de itens), portanto o desempenho não é ruim. Mas seria se o banco de dados tivesse mais de alguns milhares de itens. Essa experiência me levou a realmente desejar evitar bancos de dados EAV no futuro, se possível, porque eles são muito difíceis de consultar.
ErikE
Além disso, o tempo gasto automatizando o uso / criação / modificação de tabelas reais de subtipos seria, em minha opinião, o melhor.
ErikE
Depois de examinar o padrão EAV, percebi que os valores para os atributos são forçados a compartilhar um tipo de dados (todas as strings nesse caso). Além disso, consultar a configuração do EAV será uma tarefa árdua. O supertipo / subtipo está melhor. Minha pergunta agora é que determinadas tabelas permitem apenas tipos específicos de dispositivos. Valido isso colocando um ID de classe de dispositivo (telefone, computador, roteador) em todas as tabelas e colocando uma restrição de verificação nesse campo ou excluo esse campo das tabelas de subtipos e uso um gatilho em cada uma delas? Por favor, consulte ERD3 para referência.
TheSecretSquad
11
Para consultar dados EAV, não é incomum criar um datamart de tabelas relacionais para os dados que você deseja consultar e preenchê-los usando algum script. As consultas serão executadas mais rapidamente, mas somente com os dados que você coloca no datamart, e a configuração requer um pouco de planejamento.
FrustratedWithFormsDesigner
6

No seu caso, a melhor abordagem é uma variação no modelo Entity-Attribute-Value (EAV). Há muitas pessoas que evitam o EAV porque é inútil em alguns aspectos e mal utilizado muitas vezes. No entanto, o EAV é uma solução que funciona bem para seus requisitos específicos.

A variação que você deseja incluir para sua situação é abstrair os atributos a um nível de distância de suas entidades (ou seja, seus itens de inventário). Essencialmente, você deseja definir os tipos de dispositivos que possuem uma lista de atributos. Em seguida, você define instâncias de dispositivo que possuem valores para cada um dos atributos que dispositivos desse tipo devem ter.

Aqui está um esboço do ERD:

ERD

DEVICE_ATTRIBUTEcontém os valores para cada tipo de atributo genérico. DEVICE_TYPEdefine a lista de atributos genéricos que se aplicam a um determinado tipo de dispositivo (esses são os TYPICAL_DEVICE_ATTRIBUTEs.

Isso permite controlar quais atributos precisam ser preenchidos para um dispositivo e permitir que dispositivos de tipos diferentes tenham listas diferentes de atributos. Também facilita a comparação entre dispositivos alinhando seus atributos um contra o outro.

Joel Brown
fonte
É semelhante ao recomendado pela ssmusoke. Mudei meu ERD usando sua recomendação e parece que ele corresponde ao seu. Sinta-se livre para conferir o novo RD em http://www.dividegraphics.com/ERD2.jpg e fornecer qualquer comentário.
TheSecretSquad
@reallythecrash - Você está correto, estou sugerindo a mesma abordagem básica que o ssmusoke, apenas adotei uma resposta diferente na minha resposta, na esperança de facilitar o entendimento da estrutura do modelo e da lógica do uso do EAV que Muitas pessoas (injustamente) denunciam como anti-padrão.
Joel Brown
Após algumas pesquisas, vejo por que as pessoas podem considerar o EAV um antipadrão. Eu acho que é simples armazenar dados usando o EAV, mas especialmente complexo para consultar e manter tipos de dados. Eu acho que é um padrão com um propósito restrito e deve ser usado por desenvolvedores experientes que podem implementá-lo adequadamente, ou seja, não eu. Provavelmente vou optar pelo paradigma de supertipo / subtipo.
TheSecretSquad
@JoelBrown - que software você usou para esboçar esse diagrama ?, parece legal.
Vidar
@Vidar - usei o Visio com smartshapes do ERD que criei para usar as convenções visuais de James Martin e desenhei com um padrão de linha personalizado que é superficial. Acho que essa é uma boa ferramenta para usar em modelos de dados rápidos / preliminares. Quando o diagrama é muito formal, pode levar algumas pessoas a pensarem que está terminado; portanto, algo superficial ajuda a impedir que as pessoas tirem conclusões precipitadas sobre a firmeza / conclusão de um modelo de dados.
Joel Brown
1
  1. A abordagem geral é a seguinte:

a) Uma abordagem de modelo de valor de atributo de entidade para abordar os atributos dos diferentes dispositivos em um tipo de dispositivo. Cada tipo de dispositivo terá uma lista de atributos cujos valores você rastreia

b) Para cada tipo de dispositivo, você monitora os detalhes do inventário pelo número de série que corresponde a um único dispositivo.

  1. Então, você terminaria com as seguintes tabelas:

a) Atributos - defina os atributos para todos os dispositivos (tudo nesta tabela) colunas: id, nome, descrição

b) Atributos do item - define os atributos permitidos para um dispositivo específico - itemid, attributeid

c) Definição do item - define um item como Black Berry Torch 4500, Iphone 4S, Iphone 3S etc - id, nome, descrição, categoryid (se você deseja adicionar categorias como telefones celulares, comutadores etc.)

d) Dispositivos - os dispositivos individuais - id, itemid, data do inventário, desativado, número de série ... (basicamente todos os outros atributos de um dispositivo)

Se você deseja rastrear qualquer outra informação sobre as transcensões do dispositivo, pode adicionar mais tabelas vinculadas ao dispositivo conforme necessário.

Stephen Senkomago Musoke
fonte
Obrigdo por sua contribuição. Isso está de acordo com o que estou procurando, simplesmente não consegui descobrir como fazê-lo. Mudei meu ERD para refletir suas especificações. Parece que requer mais trabalho para inserir todos os atributos permitidos para cada tipo de dispositivo, mas também parece oferecer flexibilidade máxima. Vou fazer um pequeno protótipo para ver se funciona da maneira que acho que funcionará. Obrigado novamente. Fiz upload de um ERD com as alterações, se você quiser dar uma olhada e me informar se estou no caminho certo. http://www.dividegraphics.com/ERD2.jpg #
TheSecretSquad
Sim, você está no caminho certo.
Stephen Senkomago Musoke 18/03/12
O EAV oferecerá muita flexibilidade, mas você também terá muito mais metadados para mantê-lo funcionando.
FrustratedWithFormsDesigner
@FrustratedWithFormsDesigner parece inevitável quando o sistema armazena uma grande variedade de itens, telefones, switches, PCs, laptops, etc ... Melhor mais metadados de mais mesas eu diria
Stephen Senkomago Musoke
11
@ssmusoke: Concordo, mas eu queria enfatizar esse ponto porque vi pessoas não perceberem a importância dos metadados e, em seguida, a implementação do EAV deles se torna um pesadelo.
FrustratedWithFormsDesigner