O armazenamento de uma lista delimitada em uma coluna do banco de dados é realmente tão ruim?

363

Imagine um formulário da web com um conjunto de caixas de seleção (qualquer uma ou todas elas podem ser selecionadas). Eu escolhi salvá-los em uma lista separada por vírgula de valores armazenados em uma coluna da tabela do banco de dados.

Agora, eu sei que a solução correta seria criar uma segunda tabela e normalizar adequadamente o banco de dados. Foi mais rápido implementar a solução fácil, e eu queria ter uma prova de conceito desse aplicativo rapidamente e sem ter que gastar muito tempo nele.

Eu pensei que o tempo economizado e o código mais simples valiam a pena na minha situação, isso é uma opção de design defensável ou deveria ter normalizado desde o início?

Um pouco mais de contexto, este é um pequeno aplicativo interno que substitui essencialmente um arquivo do Excel que foi armazenado em uma pasta compartilhada. Também estou perguntando, porque estou pensando em limpar o programa e torná-lo mais sustentável. Há algumas coisas lá em que não estou totalmente feliz, uma delas é o tópico desta pergunta.

Cientista maluco
fonte
21
Nesse caso, por que incomodar o banco de dados ?, salvar em um arquivo serve.
thavan 12/02/2013
6
Concordou com @thavan. Por que salvar os dados para uma prova de conceito? Depois de concluir a prova, adicione um banco de dados corretamente. Você está se saindo bem como prova de conceito, apenas não faça coisas que você precisa desfazer mais tarde.
91113 Jeff Davis
11
No Postgres, uma coluna de matriz deve ser preferida a uma lista separada por vírgula. Isso assegura pelo menos o tipo de dados adequado, não tem problemas em distinguir o delimitador dos dados reais e pode ser indexado com eficiência.
A_horse_with_no_name 30/08/19

Respostas:

568

Além de violar a Primeira forma normal por causa do grupo de valores repetidos armazenados em uma única coluna, as listas separadas por vírgula têm muitos outros problemas mais práticos:

  • Não é possível garantir que cada valor seja o tipo de dados correto: nenhuma maneira de impedir 1,2,3, banana, 5
  • Não é possível usar restrições de chave estrangeira para vincular valores a uma tabela de pesquisa; nenhuma maneira de impor integridade referencial.
  • Não é possível impor exclusividade: não há como impedir 1,2,3,3,3,5
  • Não é possível excluir um valor da lista sem buscar a lista inteira.
  • Não é possível armazenar uma lista por mais tempo do que aquilo que cabe na coluna da string.
  • Difícil pesquisar todas as entidades com um determinado valor na lista; você precisa usar uma varredura de tabela ineficiente. Pode ser necessário recorrer a expressões regulares, por exemplo no MySQL:
    idlist REGEXP '[[:<:]]2[[:>:]]' *
  • Difícil de contar elementos na lista ou fazer outras consultas agregadas.
  • Difícil associar os valores à tabela de pesquisa a que eles se referem.
  • Difícil buscar a lista em ordem classificada.

Para resolver esses problemas, você precisa escrever toneladas de código de aplicativo, reinventando a funcionalidade que o RDBMS já fornece com muito mais eficiência .

As listas separadas por vírgula estão erradas o suficiente para tornar este o primeiro capítulo do meu livro: Antipatterns SQL: Evitando as Armadilhas da Programação de Banco de Dados .

Há momentos em que você precisa empregar desnormalização, mas, como o @OMG Ponies menciona , esses são casos de exceção. Qualquer “otimização” não relacional beneficia um tipo de consulta em detrimento de outros usos dos dados; portanto, saiba quais das suas consultas precisam ser tratadas de modo que elas mereçam desnormalização.


* O MySQL 8.0 não suporta mais esta sintaxe de expressão de limite de palavras.

Bill Karwin
fonte
8
Um ARRAY (de qualquer tipo de dado) pode corrigir a exceção, basta verificar PostgreSQL: postgresql.org/docs/current/static/arrays.html (@Bill: ótimo livro, que você deve ler para qualquer desenvolvedor ou dba)
Frank Heikens
4
+1 fatura Karwin Ótima resposta! Pontos de bala concisos adoráveis. Parece um ótimo livro também. Também adoro a capa +1 NullUserException. Estou no processo de projetar o esquema para um banco de dados MySQL para substituir um sistema baseado em texto de arquivo simples. Eu encontrei vários dilemas até agora. Portanto, vale a pena comprar este livro.
Therobyouknow 30/01/12
2
O site pragprog.com também parece bom: estilo agradável, layout, limpeza fácil de usar. Isso deve ser bastante novo, não pude comprar seus e-books no passado. PS. Eu não trabalho para eles têm qualquer conexão com os autores. Gosto de celebrar bons produtos, serviços e ajuda quando o vejo.
Therobyouknow 30/01/12
2
No lado sério, eu acrescentaria à sua lista: difícil de pesquisar. Digamos que você queira todos os registros que incluem "2". Claro que você não pode simplesmente procurar foobar = '2', porque isso seria errado se houvesse outros valores. Você não pode pesquisar foobar como '% 2%' porque isso causaria falsos hits para 12 e 28 e assim por diante. Você não pode pesquisar foobar como '%, 2,%' porque 2 pode ser o primeiro ou o último elemento da lista e, portanto, possui apenas uma dessas vírgulas.
21415 Jay
2
Eu sei que isso não é recomendado, mas jogar com os diabos é um defensor: a maioria deles pode ser removida se houver uma interface do usuário que lide com tipos de dados e exclusividade (caso contrário, seria um erro ou se comportaria mal), a interface do usuário cai e a cria mesmo assim, há uma tabela de drivers em que os valores vêm para torná-los únicos, campos como '% P%' podem ser usados, sendo valores P, R, S, T, a contagem não importa e a classificação não importa. Dependendo da interface do usuário, os valores podem ser divididos [], por exemplo, para marcar as caixas de seleção em uma lista da tabela de drivers no cenário menos comum sem ter que ir para outra tabela para obtê-los.
precisa saber é o seguinte
44

"Um dos motivos foi a preguiça".

Isso soa um alarme. A única razão pela qual você deve fazer algo assim é que você sabe como fazê-lo "da maneira certa", mas chegou à conclusão de que há uma razão tangível para não fazê-lo dessa maneira.

Dito isto: se os dados que você optar por armazenar dessa maneira forem aqueles que você nunca precisará consultar, pode haver um motivo para armazená-los da maneira que você escolheu.

(Alguns usuários contestariam a afirmação no meu parágrafo anterior, dizendo que "você nunca pode saber quais requisitos serão adicionados no futuro". Esses usuários estão equivocados ou afirmam uma convicção religiosa. Às vezes é vantajoso trabalhar com os requisitos que você tem antes de você.)

Hammerite
fonte
Eu sempre ouço algumas pessoas dizendo que "meu design é mais flexível que o seu" quando as confronto sobre coisas como não configurar restrições de chave estrangeira ou armazenar listas em um único campo. Para mim, flexibilidade (em tais casos) == nenhuma disciplina == preguiça.
foresightyj
41

Existem inúmeras perguntas sobre o SO perguntando:

  • como obter uma contagem de valores específicos da lista separada por vírgula
  • como obter registros que tenham apenas o mesmo valor específico 2/3 / etc dessa lista separada por vírgula

Outro problema com a lista separada por vírgula é garantir que os valores sejam consistentes - armazenar texto significa a possibilidade de erros de digitação ...

Todos esses são sintomas de dados não-normalizados e destacam por que você deve sempre modelar para dados normalizados. A desnormalização pode ser uma otimização de consulta, a ser aplicada quando a necessidade realmente se apresentar .

Pôneis OMG
fonte
19

Em geral, qualquer coisa pode ser defensável se atender aos requisitos do seu projeto. Isso não significa que as pessoas vão concordar ou querer defender sua decisão ...

Em geral, armazenar dados dessa maneira é subótimo (por exemplo, mais difícil de realizar consultas eficientes) e pode causar problemas de manutenção se você modificar os itens em seu formulário. Talvez você possa ter encontrado um meio termo e usado um número inteiro representando um conjunto de sinalizadores de bits?

bobbymcr
fonte
10

Sim, eu diria que é realmente tão ruim assim. É uma escolha defensável, mas isso não a torna correta ou boa.

Quebra a primeira forma normal.

Uma segunda crítica é que colocar resultados brutos diretamente em um banco de dados, sem nenhuma validação ou vinculação, deixa você aberto a ataques de injeção de SQL.

O que você chama de preguiça e falta de conhecimento de SQL é o material de que os neófitos são feitos. Eu recomendo reservar um tempo para fazê-lo corretamente e encará-lo como uma oportunidade de aprender.

Ou deixe como está e aprenda a dolorosa lição de um ataque de injeção de SQL.

duffymo
fonte
19
Não vejo nada nesta pergunta que sugira que ele esteja vulnerável à injeção de SQL. A injeção de SQL e a normalização do banco de dados são tópicos ortogonais, e sua digressão na injeção é irrelevante para a pergunta.
Hammerite 6/09/10
5
@Paul: E talvez a mesma atitude o leve a ser atropelado por um ônibus quando ele não olha para os dois lados antes de atravessar a rua, mas você não o alertou sobre isso. Edit: Eu pensei que você era o cartaz desta resposta, meu erro.
Hammerite 6/09/10
11
@ Hammerite - sua extrapolação para ônibus é ridícula.
duffymo 6/09/10
4
Sim, era para ser ridículo. Seu ridículo ilustra o argumento que estou argumentando, que é que não faz sentido avisá-lo contra algo que você não tem motivos para pensar que ele precisa ser avisado.
Hammerite 6/09/10
11
Sim eu entendo. Eu acho que tinha muito mais razão do que seu aviso sobre ônibus.
Duffymo 6/09/10
7

Bem, eu tenho usado uma lista separada por tabulação par de chave / valor em uma coluna NTEXT no SQL Server há mais de 4 anos e funciona. Você perde a flexibilidade de fazer consultas, mas por outro lado, se você tem uma biblioteca que persiste / derpersista no par de valores-chave, não é uma má ideia.

Raj
fonte
13
Não, é uma ideia horrível. Você conseguiu se safar, mas o custo de seus poucos minutos de tempo de desenvolvimento custou muito desempenho, flexibilidade e capacidade de manutenção de seu código.
Paul Tomblin
5
Paul, eu concordo. Mas como eu disse, usei se para um propósito específico, e isso é para uma operação de entrada de dados em que você tem muitos tipos de formulários. Estou revisando o design agora que aprendi o NHibernate, mas naquela época eu precisava da flexibilidade para criar o formulário no ASP.NET e usar os IDs da caixa de texto como chave no par chave / valor.
Raj
28
+1 apenas para combater os votos negativos. Contar a alguém que mantém o aplicativo há 4 anos sobre problemas de manutenção é um pouco presunçoso. Existem muito poucas idéias "horríveis" no desenvolvimento de sw - principalmente são apenas idéias com aplicabilidade muito limitada. É razoável alertar as pessoas sobre as limitações, mas castigar aqueles que fizeram e viveram isso me parece uma atitude mais santa do que você.
22813 Mark Brackett
7

Eu precisava de uma coluna de vários valores, que poderia ser implementada como um campo xml

Pode ser convertido em uma vírgula delimitada conforme necessário

consultando uma lista XML no servidor sql usando o Xquery .

Por ser um campo xml, algumas das preocupações podem ser tratadas.

Com CSV: não é possível garantir que cada valor seja o tipo de dados correto: não há como impedir 1,2,3, banana, 5

Com XML: os valores em uma tag podem ser forçados a serem do tipo correto


Com CSV: não é possível usar restrições de chave estrangeira para vincular valores a uma tabela de pesquisa; nenhuma maneira de impor integridade referencial.

Com XML: ainda é um problema


Com CSV: não é possível impor exclusividade: não há como impedir 1,2,3,3,3,5

Com XML: ainda é um problema


Com CSV: não é possível excluir um valor da lista sem buscar a lista inteira.

Com XML: itens únicos podem ser removidos


Com CSV: difícil pesquisar todas as entidades com um determinado valor na lista; você precisa usar uma varredura de tabela ineficiente.

Com XML: o campo xml pode ser indexado


Com CSV: difícil de contar elementos na lista ou fazer outras consultas agregadas. **

Com XML: não particularmente difícil


Com CSV: difícil associar os valores à tabela de pesquisa que eles fazem referência. **

Com XML: não particularmente difícil


Com CSV: difícil obter a lista em ordem classificada.

Com XML: não particularmente difícil


Com CSV: armazenamento de números inteiros como seqüências de caracteres ocupa aproximadamente o dobro do espaço que o armazenamento de números inteiros binários.

Com XML: o armazenamento é ainda pior que um CSV


Com CSV: além de muitos caracteres de vírgula.

Com XML: tags são usadas em vez de vírgulas


Em resumo, o uso de XML contorna alguns dos problemas da lista delimitada E pode ser convertido em uma lista delimitada conforme necessário

James A Mohler
fonte
6

Sim, isso é ruim. Minha opinião é que, se você não gosta de usar bancos de dados relacionais, procure uma alternativa que melhor lhe convém, há muitos projetos interessantes "NOSQL" por aí com alguns recursos realmente avançados.

Robin
fonte
0

Eu provavelmente tomaria o meio termo: transforme cada campo no CSV em uma coluna separada no banco de dados, mas não se preocupe muito com a normalização (pelo menos por enquanto). Em algum momento, a normalização pode se tornar interessante, mas com todos os dados inseridos em uma única coluna, você praticamente não obtém nenhum benefício ao usar um banco de dados. Você precisa separar os dados em campos lógicos / colunas / como quiser chamá-los antes de poder manipulá-los de maneira significativa.

Jerry Coffin
fonte
O formulário contém mais alguns campos; essa é apenas uma parte do formulário (que não expliquei bem na pergunta).
Mad Scientist
0

Se você tiver um número fixo de campos booleanos, poderá usar um INT(1) NOT NULL(ou, BIT NOT NULLse existir) ou CHAR (0)(nulo) para cada um. Você também pode usar um SET(eu esqueço a sintaxe exata).

Solomon Ucko
fonte
11
INT(1)leva 4 bytes; o (1)não tem sentido.
Rick James