EAV - é realmente ruim em todos os cenários?


65

Estou pensando em usar um modelo de valor de atributo de entidade (EAV) para algumas das coisas em um dos projetos, mas todas as perguntas sobre isso no Stack Overflow terminam em respostas que chamam o EAV de antipadrão.

Mas eu estou querendo saber se isso é errado em todos os casos.

Digamos que a entidade do produto da loja tenha características comuns, como nome, descrição, imagem e preço, que participam da lógica de muitos lugares e possui características (semi) únicas, como relógio e bola de praia, que seriam descritas por aspectos completamente diferentes. Então eu acho que o EAV seria adequado para armazenar esses recursos (semi) exclusivos.

Tudo isso pressupõe que, para mostrar a lista de produtos, é suficiente informação na tabela de produtos (isso significa que não há EAV envolvido) e apenas ao mostrar um produto / comparar até 5 produtos / etc. os dados salvos usando o EAV são usados.

Eu já vi essa abordagem no comércio Magento e é bastante popular, então existem casos em que o EAV é razoável?


2
@busy_wait Tabelas "Entidade-Atributo-Valor" - consulte Modelo de entidade-atributo-valor na Wikipedia .
Ross Patterson

Para um exemplo do padrão EAV funcionando muito bem, dê uma olhada no banco de dados Datomic. Ele armazena tudo no padrão EAVT (T é um "carimbo de data / hora", na verdade mais como um ID de transação). Sua [documentação de indexação] (docs.datomic.com/indexes.html) parece mostrar melhor. Para um exemplo de EAV mal executado, consulte Wordpress .
Dan Ross

Respostas:


81

https://web.archive.org/web/20140831134758/http://www.dbforums.com/database-concepts-design/1619660-otlt-eav-design-why-do-people-hate.html

O EAV oferece flexibilidade ao desenvolvedor para definir o esquema conforme necessário e isso é bom em algumas circunstâncias.

Por outro lado, apresenta um desempenho muito ruim no caso de uma consulta mal definida e pode suportar outras práticas inadequadas.

Em outras palavras, o EAV fornece corda suficiente para você se enforcar e, nesse setor, as coisas devem ser projetadas para o nível mais baixo de complexidade, porque o cara que o substitui no projeto provavelmente será um idiota.


32
Ame a última frase.
Zohar Peled

2
Link podre. Existe uma versão em cache em algum lugar?
Curinga

11
Não siga o link. A página carrega lentamente e não é útil. Além disso, fóruns de estilo antigo como esse cheiram mal. Use o estouro de pilha! Voto positivo / respostas úteis e empurre o lixo para baixo.
Jess

29

Em poucas palavras, o EAV é útil quando sua lista de atributos cresce frequentemente ou quando é tão grande que a maioria das linhas seria preenchida principalmente por NULLs se você transformasse cada coluna em um atributo. Torna-se um antipadrão quando usado fora desse contexto.


16
Eu substituiria "frequentemente" por "precisa da possibilidade de ser alterada em tempo de execução".
Doc Brown

3
Podemos reduzir ainda mais o Doc Brown usando a palavra bastante dinâmica "dinâmica" - EAV é útil quando sua lista de atributos pode mudar dinamicamente.
Alexander Mills

Ainda mais para "quando seus atributos podem mudar" - "dinâmica" é um pouco redundante neste contexto :)
Wranorn

11
É necessariamente mais útil do que, digamos, que o formulário para alterar um atributo execute a CREATE TABLEpara o novo atributo?
Damian Yerrick

@DamianYerrick abordagem interessante. Você já usou isso na produção?
digout

21

Digamos que a entidade do produto da loja tenha características comuns, como nome, descrição, imagem, preço, etc., que participem da lógica de muitos lugares e tenha características (semi) únicas, como relógio e bola de praia, que seriam descritas por aspectos completamente diferentes . Então eu acho que o EAV seria adequado para armazenar esses recursos (semi) exclusivos?

O uso de uma estrutura EAV para tem várias implicações que são compensações.

Você está trocando um 'menos espaço para a linha porque não possui 100 colunas null' contra 'consultas e modelos mais complexos'.

Ter um EAV normalmente significa que o valor é uma string na qual se pode inserir qualquer dado. Isso, então, tem implicações na verificação de validade e restrição. Considere a situação em que você colocou o número de baterias usadas como algo na tabela EAV. Você deseja encontrar uma lanterna que use baterias do tamanho C, mas menos de 4 delas.

select P.sku
from
  products P
  attrib Ab on (P.sku = Ab.sku and Ab.key = "batteries")
  attrib Ac on (P.sku = Ac.sku and Ac.key = "count")
where
  cast(Ac.value as int) < 4
  and Ab.value = 'C'
  ...

O que você deve perceber aqui é que você não pode usar um índice razoavelmente no valor. Você também não pode impedir que alguém insira algo que não seja um número inteiro lá ou um número inteiro inválido (use baterias '-1') porque a coluna de valor é usada repetidamente para propósitos diferentes.

Isso tem implicações na tentativa de escrever um modelo para o produto. Você terá os bons valores digitados ... mas também ficará Map<String,String>sentado lá com todo o tipo de coisas . Isso então tem implicações adicionais ao serializá-lo para XML ou Json e as complexidades de tentar validar ou consultar essas estruturas.

Algumas alternativas ou modificações no padrão a serem consideradas é, em vez de uma chave de forma livre, ter outra tabela com chaves válidas. Isso significa que, em vez de fazer comparações de strings no banco de dados, você está verificando a igualdade dos IDs de chave estrangeira. Alterar a chave em si é feito em um ponto. Você tem um conjunto conhecido de chaves, o que significa que elas podem ser feitas como uma enumeração.

Você também pode ter tabelas relacionadas que contêm atributos de uma classe específica de produto. Um departamento de mercearia pode ter outra tabela que possui vários atributos associados aos quais os materiais de construção não precisam (e vice-versa).

+----------+    +--------+    +---------+
|Grocery   |    |Product |    |BuildMat |
|id (fk)   +--->|id (pk) |<---+id (fk)  |
|expiration|    |desc    |    |material |
|...       |    |img     |    |...      |
+----------+    |price   |    +---------+
                |...     |               
                +--------+               

Há momentos em que especialmente requerem uma tabela EAV.

Considere a situação em que você não está apenas escrevendo um sistema de inventário para sua empresa, onde conhece todos os produtos e atributos. Agora você está escrevendo um sistema de inventário para vender para outras empresas. Você não pode conhecer todos os atributos de cada produto - eles precisarão defini-los.

Uma idéia que surge é "deixaremos o cliente modificar a tabela" e isso é péssimo (você entra em metaprogramação para estruturas de tabela porque não sabe mais o que é onde, eles podem realmente atrapalhar a estrutura ou corromper o aplicativo, eles têm acesso para fazer coisas erradas e as implicações desse acesso se tornam significativas). Há mais sobre esse caminho no MVC4: como criar modelo em tempo de execução?

Em vez disso, você cria a interface administrativa para uma tabela EAV e permite que ela seja usada. Se o cliente deseja criar uma entrada para 'polkadots', ela entra na tabela EAV e você já sabe como lidar com isso.

Um exemplo disso pode ser visto no modelo de banco de dados do Redmine. Você pode ver a tabela custom_fields e a tabela custom_values ​​- essas são partes do EAV que permite a extensão do sistema.


Observe que, se você achar que toda a estrutura de sua tabela se parece com EAV, em vez de relacional, convém observar o sabor KV do NoSQL (cassandra, redis, Mongo, ...). Perceba que isso geralmente vem com outras desvantagens em seu design que podem ou não ser apropriadas para o que você está usando. No entanto, eles são projetados especificamente com a intenção de uma estrutura de EAV.

Você pode ler SQL vs NoSQL para um sistema de gerenciamento de inventário

Seguindo essa abordagem com um banco de dados NoSQL orientado a documentos (couch, mongo), você pode considerar cada item de inventário como um documento em um disco ... puxar tudo em um único documento é rápido. Além disso, o documento está estruturado para que você possa executar rapidamente qualquer coisa. Por outro lado, procurar em todos os documentos itens que correspondam a um atributo específico pode ter menos desempenho (compare usando 'grep' em todos os arquivos) ... é tudo uma troca.

Outra abordagem seria o LDAP, em que se teria uma base com todos os itens associados, mas também haveria classes de objetos adicionais aplicadas a ele para os outros tipos de itens. (consulte Inventário do sistema usando LDAP )

Depois de seguir esse caminho, você poderá encontrar algo que corresponda exatamente ao que está procurando ... embora tudo venha com algumas desvantagens.


10

6 anos depois

Agora que o JSON no Postgres está aqui, temos outra opção, para aqueles que estão usando o Postgres. Se você deseja apenas anexar alguns dados extras a um produto, suas necessidades são bastante simples. Exemplo:

CREATE TABLE products (sku VARCHAR(30), shipping_weight REAL, detail JSON);
INSERT INTO products ('beachball', 1.0, '{"colors": ["red", "white"], "diameter": "50cm"}');

SELECT * FROM products;
    sku    | weight |               detail               
-----------+--------+------------------------------------
 beachball |      1 | {"colors": ["red", "white"], "diameter": "50cm"}

Aqui está uma introdução mais suave ao JSON no Postgres: https://www.compose.com/articles/is-postgresql-your-next-json-database/ .

Observe que o Postgres realmente armazena JSONB, não JSON em texto sem formatação, e suporta índices em campos dentro de um documento / campo JSONB, caso você descubra que realmente deseja consultar esses dados.

Além disso, observe que os campos em um campo JSONB não podem ser modificados individualmente com uma consulta UPDATE; você precisaria substituir todo o conteúdo do campo JSONB.

Essa resposta pode não abordar diretamente a pergunta, mas oferece uma alternativa ao padrão EAV, que deve ser considerado por qualquer pessoa que esteja pensando na pergunta original.


3
Eu acho que é uma ótima idéia postar uma solução alternativa. Apenas para manter os outros no caminho, o MS SQL suporta colunas XML com capacidade de indexá-las por um tempo e, a partir de 2016, pode fazer o mesmo com JSON (embora JSON não seja um tipo de coluna nativa no MS SQL, você ainda pode indexá-las ) Por outro lado - pelo que li, o suporte JSON do Postgres é melhor, por exemplo, parece que ele suporta índices de dados nas propriedades da matriz JSON.
Giedrius

11
"... os campos dentro de um campo JSONB não podem ser modificados individualmente com uma consulta UPDATE; você teria que substituir todo o conteúdo do campo JSONB." Isso está desatualizado, não é? Existe uma jsonb_set()função no Postgres 9.5 e posterior que é exatamente isso. (O artigo que você vinculou aos links por sua vez, para um artigo mais recente que discute as adições aos recursos da 9.5 ).
Curinga

7

Normalmente, as pessoas olham para o outro lado, se você o estiver usando em tabelas de pesquisa ou em outras situações em que o benefício é evitar criar tabelas para um ou dois valores armazenados. A situação que você está descrevendo, na qual basicamente armazena propriedades de itens, parece perfeitamente normal (e normalizada). Ampliar uma tabela para armazenar um número variável de atributos de itens é uma má idéia.

Para o caso geral de armazenar dados díspares em uma tabela longa e fina ... Você não deve ter medo de criar novas tabelas, se necessário, e ter apenas uma ou duas tabelas longas não é muito melhor do que ter apenas uma ou mais tabelas. duas mesas curtas e grossas.

Dito isto, sou famoso por usar tabelas EAV para log. Eles têm uma boa utilidade.


Por favor, defina "mesa magra" e "mesa gorda".
Tulains Córdova

@ TulainsCórdova: Uma tabela "magra" seria uma com poucas linhas e muitas colunas, enquanto uma tabela gorda é aquela com muitas colunas e poucas linhas. Um exemplo seria a construção de uma tabela de pesquisa na qual você possui propriedades para, por exemplo, livros. Uma tabela gorda teria um registro por livro, com muitas colunas para dados específicos, enquanto uma tabela fina teria talvez quatro colunas id, book, field_name, field_data. A vantagem do primeiro é que há menos registros, mas o negativo é que alguns campos podem ficar em branco e a coisa toda é mais difícil de estender.
23617 Satanicpuppy

@Satanicpuppy Acho que suas definições de magro / gordo estão misturadas - elas são as mesmas. Você quer dizer que uma tabela fina tem poucas colunas e muitas linhas?
Charles Wood

1

O EAV altera o problema da estrutura explícita, para a percepção implícita. Em vez de dizer que X é uma tabela com as colunas A e B. Você implica que as colunas A e B formam a tabela X. É o contrário em um sentido, mas não há um mapeamento individual, necessariamente. Você pode dizer que A e B são mapeados para a tabela (ou digite) X e Y. Isso pode ser importante no domínio mais envolvido, onde o contexto é importante.

Estou estudando Datomic, para esse tipo de abordagem, e acho que é um sistema muito útil e poderoso, com limites para o que você deve fazer com ele (não que não pudesse).

Que o EAV seria lento, ou "lhe dê corda suficiente para se enforcar" não é uma afirmação com a qual eu concordo. Em vez disso, eu colocaria mais ênfase nos pontos fortes do EAV e, se ele se adequar ao seu espaço problemático, considere-o.

Minha experiência é que é uma abordagem maravilhosa e quase sem restrições à modelagem. Especificamente, no caso de Datomic, eles impõem um conjunto semântico em cima de tudo. Qualquer decisão de modelagem que modela um relacionamento pode passar livremente de um para muitos sem precisar redesenhar colunas / tabelas. Você também pode voltar, desde que a restrição não viole a invariável. É tudo a mesma coisa sob o capô.

O problema com o EAV está na minha mente com a falta de uma implementação como a Datomic. Como essa é uma pergunta sobre o EAV, não quero falar sobre o Datomic, mas é uma daquelas coisas em que acho que eles acertaram tudo em relação ao EAV.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.