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 Value
tabela, 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.