Por que você armazenaria uma enumeração no DB?

69

Eu já vi várias perguntas, como esta , pedindo conselhos sobre como armazenar enumerações no DB. Mas eu me pergunto por que você faria isso. Então, digamos que eu tenho uma entidade Personcom um gendercampo e uma Genderenumeração. Então, minha tabela de pessoa tem um gênero de coluna.

Além do motivo óbvio de impor a correção, não vejo por que criaria uma tabela extra genderpara mapear o que já tenho no meu aplicativo. E eu realmente não gosto de ter essa duplicação.

user3748908
fonte
11
Onde mais você armazenaria dados que podem mudar regularmente? Embora você possa ter pensado em todas as opções, e se alguém aparecer e quiser adicionar uma nova opção. Você está pronto para ajustar essa lista codificada? Alguém pode querer dar seu gênero como algo que não seja homem ou mulher, por exemplo, intersexo.
JB King
4
@JBKing ... basta olhar para a lista de gênero do Facebook.
3
Se seus clientes são "Tumblrites iludidos", você cria um esquema de banco de dados que permite criar algo que atenda às necessidades deles, pelo menos, se você pretende permanecer no negócio.
Gort the Robot

Respostas:

74

Vamos dar outro exemplo que é menos cheio de concepções e expectativas. Eu tenho um enum aqui, e é o conjunto de prioridades para um bug.

Qual valor você está armazenando no banco de dados?

Então, eu poderia estar armazenando 'C', 'H', 'M', e 'L'no banco de dados. Ou 'HIGH'assim por diante. Isso tem o problema de dados digitados em sequência . Há um conjunto conhecido de valores válidos e, se você não estiver armazenando esse conjunto no banco de dados, pode ser difícil trabalhar com ele.

Por que você está armazenando os dados no código?

Você tem List<String> priorities = {'CRITICAL', 'HIGH', 'MEDIUM', 'LOW'};ou algo nesse sentido no código. Isso significa que você tem vários mapeamentos desses dados no formato correto (você está inserindo todos os limites no banco de dados, mas está exibindo-o como Critical). Agora, seu código também é difícil de localizar. Você vinculou a representação do banco de dados da ideia a uma sequência armazenada no código.

Em qualquer lugar que você precisar acessar essa lista, é necessário ter duplicação de código ou uma classe com várias constantes. Nenhuma das quais são boas opções. Também não se deve esquecer que existem outros aplicativos que podem usar esses dados (que podem ser gravados em outros idiomas - o aplicativo da web Java possui um sistema de relatórios Crystal Reports usado e um trabalho em lote Perl alimentando dados). O mecanismo de relatório precisaria conhecer a lista de dados válida (o que acontece se não houver nada marcado em 'LOW'prioridade e você precisar saber que essa é uma prioridade válida para o relatório?), E o trabalho em lotes terá informações sobre qual é o valor válido valores são.

Hipoteticamente, você pode dizer "somos uma loja de idioma único - tudo está escrito em Java" e temos um único .jar que contém essas informações - mas agora isso significa que seus aplicativos estão fortemente acoplados entre si e que .jar contém os dados. Você precisará liberar a parte de relatório e a parte de atualização em lote junto com o aplicativo da Web cada vez que houver uma alteração - e espero que essa liberação ocorra sem problemas para todas as partes.

O que acontece quando seu chefe quer outra prioridade?

Seu chefe veio hoje. Há uma nova prioridade - CEO. Agora você precisa alterar todo o código , recompilar e reimplementar.

Com uma abordagem 'enum-in-the-table', você atualiza a lista de enum para ter uma nova prioridade. Todo o código que obtém a lista extrai-o do banco de dados.

Os dados raramente ficam sozinhos

Com prioridades, os dados são inseridos em outras tabelas que podem conter informações sobre fluxos de trabalho ou sobre quem pode definir essa prioridade ou outros enfeites.

Voltando um pouco ao gênero, como mencionado na pergunta: O gênero tem um link para os pronomes em uso: he/his/hime she/hers/her... e você deseja evitar a codificação embutida no próprio código. E então seu chefe aparece e você precisa adicionar o 'OTHER'gênero (para simplificar) e relacionar esse gênero com they/their/them... e seu chefe vê o que o Facebook tem e ... bem, sim.

Ao se restringir a um bit de dados do tipo string em vez de a uma tabela enum, você agora precisa replicar essa string em várias outras tabelas para manter esse relacionamento entre os dados e seus outros bits.

E quanto a outros datastores?

Não importa onde você armazena isso, o mesmo princípio existe.

  • Você pode ter um arquivo,, priorities.propque tenha a lista de prioridades. Você lê esta lista em um arquivo de propriedades.
  • Você pode ter um banco de dados de armazenamento de documentos (como o CouchDB ) que possua uma entrada para enums(e depois escreva uma função de validação em JavaScript ):

    {
       "_id": "c18b0756c3c08d8fceb5bcddd60006f4",
       "_rev": "1-c89f76e36b740e9b899a4bffab44e1c2",
       "priorities": [ "critical", "high", "medium", "low" ],
       "severities": [ "blocker", "bad", "annoying", "cosmetic" ]
    }
    
  • Você pode ter um arquivo XML com um pouco de esquema:

    <xs:element name="priority" type="priorityType"/>
    
    <xs:simpleType name="priorityType">
      <xs:restriction base="xs:string">
        <xs:enumeration value="critical"/>
        <xs:enumeration value="high"/>
        <xs:enumeration value="medium"/>
        <xs:enumeration value="low"/>
      </xs:restriction>
    </xs:simpleType>
    

A idéia central é a mesma. O próprio armazenamento de dados é onde a lista de valores válidos precisa ser armazenada e aplicada. Ao colocá-lo aqui, é mais fácil argumentar sobre o código e os dados. Você não precisa se preocupar em verificar defensivamente o que possui a cada vez (em maiúsculas ou minúsculas? Por que existe um chriticaltipo nesta coluna? Etc ...) porque você sabe o que está recebendo de volta do armazenamento de dados. exatamente o que o armazenamento de dados espera que você envie de outra forma - e você pode consultar o armazenamento de dados para obter uma lista de valores válidos.

O takeaway

O conjunto de valores válidos são dados , não código. Você não precisa se esforçar para DRY código - mas a questão da duplicação é que você está duplicando os dados no código, em vez de respeitar o seu lugar como dados e armazená-lo em um banco de dados.

Isso facilita a gravação de vários aplicativos no armazenamento de dados e evita ter instâncias nas quais você precisará implantar tudo o que estiver fortemente acoplado aos dados em si - porque você não acoplado seu código aos dados.

Isso torna os aplicativos de teste mais fáceis porque você não precisa testar novamente o aplicativo inteiro quando a CEOprioridade é adicionada - porque você não tem nenhum código que se preocupe com o valor real da prioridade.

Ser capaz de raciocinar sobre o código e os dados independentemente um do outro facilita a localização e a correção de bugs durante a manutenção.

Peter Mortensen
fonte
6
Se você pode adicionar um valor de enum ao seu código sem precisar alterar nenhuma lógica (e para que não seja a exibição localizada dele), duvido da necessidade do valor de enum adicional em primeiro lugar. E, embora eu tenha idade suficiente para avaliar a capacidade de consultar facilmente backups de bancos de dados com consultas SQL simples para analisar um problema, nos ORMs hoje em dia você pode se sair muito bem sem precisar olhar o banco de dados subjacente. Eu não entendo o ponto sobre localização (pronomes) aqui - esse material certamente não deveria estar em um banco de dados, mas em algum tipo de arquivo de recursos, eu diria.
Voo 15/12
11
@Voo the pronomes é um exemplo de outros dados relacionados a esse valor enumesco. Sem que os dados estejam em uma tabela, os valores digitados estritamente precisariam estar lá sem restrições FK adequadas. Se você tiver pronomes (como este) em um arquivo de recurso, terá um acoplamento entre o banco de dados e o arquivo (atualize o banco de dados e reimplemente o arquivo). Considere as enumerações de redmine que são modificáveis ​​via interface de administrador em tempo real sem a necessidade de reimplementar.
11
... lembre-se também de que os bancos de dados são um armazenamento de dados poliglota. Se você está exigindo que a validação seja feita como parte do ORM em um idioma, foi necessário duplicar essa validação em qualquer outro idioma usado (recentemente trabalhei com um front end Java que fazia o Python enviar dados para o banco de dados - o Java ORM e os sistemas Python precisam concordar com as coisas - e esse acordo (os tipos válidos) foi implementado com mais facilidade fazendo com que o banco de dados o aplicasse com uma tabela 'enum'.).
2
@ O uso de enum do Redmine é o mesmo que o bugzilla "a tabela mais importante contém todos os bugs do sistema. É composta de várias propriedades de bugs, incluindo todos os valores de enum, como gravidade e prioridade." - Não é um campo de texto de forma livre, é um valor que faz parte desse conjunto conhecido e enumerável. Não é um enum de tempo de compilação , mas ainda é um enumish. Veja também Mantis .
11
Então, para confirmar - seu ponto é que as pessoas nunca devem usar Enums? Não estava claro.
Niico 11/07/16
18

Qual destes você acha que é mais provável que cometa erros ao ler a consulta?

select * 
from Person 
where Gender = 1

Ou

select * 
from Person join Gender on Person.Gender = Gender.GenderId
where Gender.Label = "Female" 

As pessoas criam tabelas enum no SQL porque acham que o último é mais legível - levando a menos erros ao escrever e manter o SQL.

Você poderia transformar o gênero em uma string diretamente Person, mas teria que tentar aplicar o caso. Você também pode aumentar o acerto de armazenamento da tabela e o tempo de consulta devido à diferença entre cadeias e números inteiros, dependendo de quão impressionante é o seu banco de dados ao otimizar as coisas.

Telastyn
fonte
5
Mas então estamos juntando tabelas. Se minha entidade tiver duas enumerações, juntarei três tabelas apenas para uma consulta simples.
user3748908
11
@ user3748908 - e daí? As junções são boas para os DBs e as alternativas são piores - pelo menos aos olhos das pessoas que escolheram essa rota.
Telastyn
8
@ user3748908: Os bancos de dados não apenas são realmente bons em fazer junções, mas também em aplicar a consistência. A imposição da consistência funciona muito, muito bem quando você pode apontar uma coluna em uma tabela para a linha de identificação de outra e dizer "o valor dessa coluna deve ser um dos identificadores dessa tabela".
Blrfl
2
Tudo isso é verdade, mas há muitos casos em que você precisa sacrificar as junções por razões de desempenho. Não me entenda mal, eu sou toda sobre esse tipo de design e associação, mas estou pensando que o mundo não vai acabar se você achar que às vezes não precisa das uniões devido ao desempenho.
21915 JonH
3
Se você tiver que abandonar a associação às tabelas de referência por motivos de desempenho @JonH, precisará comprar um servidor maior ou parar de tentar enviar predicados através de um grande número de subconsultas (presumo que você saiba o que está fazendo). As tabelas de referências são as coisas que devem estar no seu cache alguns segundos após o início do banco de dados.
Ben
10

Não acredito que as pessoas ainda não mencionaram isso.

Chaves estrangeiras

Mantendo a enum no seu banco de dados e adicionando uma chave estrangeira na tabela que contém um valor de enum, você garante que nenhum código entre com valores incorretos para essa coluna. Isso ajuda a integridade dos dados e é o motivo mais óbvio pelo qual você deve ter tabelas para enumerações.

Benjamin Gruenbaum
fonte
A questão tem apenas 5 linhas e afirma claramente "Além do motivo óbvio de impor a correção". Portanto, ninguém o mencionou porque o OP está afirmando que é óbvio e ele está procurando outras justificativas - PS: Eu concordo com você, essa é uma boa razão.
user1007074
6

Estou no campo que concorda com você. Se você mantiver uma enumeração de Gênero em seu código e um tblGender em seu banco de dados, poderá ter problemas durante a manutenção. Você precisará documentar que essas duas entidades devem ter os mesmos valores e, portanto, todas as alterações feitas em uma também devem ser feitas na outra.

Você precisará passar os valores de enumeração para os procedimentos armazenados da seguinte maneira:

create stored procedure InsertPerson @name varchar, @gender int
    insert into tblPeople (name, gender)
    values (@name, @gender)

Mas pense em como você faria isso se mantivesse esses valores em uma tabela de banco de dados:

create stored procedure InsertPerson @name varchar, @genderName varchar
    insert into tblPeople (name, gender)
    select @name, fkGender
    from tblGender
    where genderName = @genderName --I hope these are the same

Os bancos de dados relacionais são criados com junções em mente, mas qual consulta é mais fácil de ler?


Aqui está outro exemplo de consulta:

create stored procedure SpGetGenderCounts
    select count(*) as count, gender
    from tblPeople
    group by gender

Compare isso com isso:

create stored procedure SpGetGenderCounts
    select count(*) as count, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender
    group by genderName --assuming no two genders have the same name

Aqui está outro exemplo de consulta:

create stored procedure GetAllPeople
    select name, gender
    from tblPeople

Observe que, neste exemplo, você precisaria converter a célula de gênero nos seus resultados de um int para um enum. Essas conversões são fáceis no entanto. Compare isso com isso:

create stored procedure GetAllPeople
    select name, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender

Todas essas consultas são menores e mais fáceis de manter quando você pensa em manter as definições de enum fora do banco de dados.

user2023861
fonte
11
E se não fosse gênero? Acho que estamos muito empolgados com o fato de gênero ser o campo. E se o OP dissesse "Então, digamos que eu tenho um bug de entidade com um campo Prioridade" - sua resposta mudaria?
4
@MichaelT A lista de possíveis valores de "prioridade" faz parte do código pelo menos na mesma medida em que faz parte dos dados. Você vê ícones gráficos para várias prioridades? Você não espera que eles sejam retirados do banco de dados? E coisas assim podem ser temáticas e estilizadas e ainda representam o mesmo intervalo de valores armazenados no DB. Você não pode simplesmente alterá-lo no banco de dados; você tem um código de apresentação para sincronizar.
Eugene Ryabtsev
1

Eu criaria uma tabela de gêneros pelo motivo de poder ser usada na análise de dados. Eu poderia procurar todas as pessoas do sexo masculino ou feminino no banco de dados para gerar um relatório. Quanto mais maneiras você visualizar seus dados, mais fácil será descobrir informações de tendências. Obviamente, essa é uma enumeração muito simples, mas para enumerações complexas (como os países do mundo ou estados), facilita a geração de relatórios especializados.

zackery.fix
fonte
1

Primeiro, você precisa decidir se o banco de dados será usado apenas por um aplicativo ou se é possível que vários aplicativos o utilizem. Em alguns casos, um banco de dados nada mais é do que um formato de arquivo para um aplicativo (os bancos de dados SQLite costumam ser usados ​​nesse sentido). Nesse caso, a duplicação de bits da definição de enum como uma tabela geralmente pode ser boa e pode fazer mais sentido.

No entanto, assim que você quiser considerar a possibilidade de ter vários aplicativos acessando o banco de dados, uma tabela para a enum faz muito sentido (as outras respostas abordam o porquê com mais detalhes). A outra coisa a considerar é que você ou outro desenvolvedor deseja examinar os dados brutos do banco de dados. Nesse caso, isso pode ser considerado outro uso de aplicativo (apenas aquele em que o medidor de laboratório é SQL bruto).

Se você tiver a enumeração definida no código (para uma verificação mais limpa do código e no tempo de compilação), bem como uma tabela no banco de dados, eu recomendaria adicionar testes de unidade para verificar se os dois estão sincronizados.

Eric Johnson
fonte
1

Quando você possui uma enumeração de código usada para direcionar a lógica de negócios no código, ainda deve criar uma tabela para representar os dados no banco de dados pelos diversos motivos detalhados acima / abaixo. Aqui estão algumas dicas para garantir que seus valores de banco de dados permaneçam sincronizados com os valores de código:

  1. Não faça do campo de identificação na tabela uma coluna de identidade. Inclua ID e Descrição como campos.

  2. Faça algo diferente na tabela que ajude os desenvolvedores a saber que os valores são semi-estáticos / vinculados a uma enumeração de código. Em todas as outras tabelas de pesquisa (geralmente onde os valores podem ser adicionados pelos usuários), normalmente tenho LastChangedDateTime e LastChangedBy, mas não tê-los em tabelas relacionadas à enumeração me ajuda a lembrar que eles só podem ser alterados pelos desenvolvedores. Documente isso.

  3. Crie um código de verificação que verifique se cada valor na enumeração está na tabela correspondente e se apenas esses valores estão na tabela correspondente. Se você tiver "testes de integridade" automatizados de aplicativos que executam a pós-compilação, nele. Caso contrário, faça com que o código seja executado automaticamente na inicialização do aplicativo sempre que o aplicativo estiver sendo executado no IDE.

  4. Criar produção entregar scripts SQL que fazem o mesmo, mas de dentro do banco de dados. Se criados corretamente, também ajudarão nas migrações do ambiente.

Paul Schirf
fonte
0

Depende também de quem acessa os dados. Se você tiver apenas um aplicativo que pode ser bom. Se você adicionar um data warehouse ou um sistema de relatórios. Eles precisarão saber o que esse código significa, qual é a versão editável para humanos do código.

Normalmente, a tabela de tipos não seria duplicada como uma enumeração no código. Você pode carregar a tabela de tipos em uma lista que é armazenada em cache.

Class GenderList

   Public Shared Property UnfilteredList
   Public Shared Property Male = GetItem("M")
   Public Shared Property Female = GetItem("F")

End Class

Muitas vezes, digite ir e vir. Você precisaria de uma data para quando o novo tipo foi adicionado. Saiba quando um tipo específico foi removido. Exiba-o apenas quando necessário. E se um cliente quiser "transgênero" como gênero, mas outros não? Todas essas informações são melhor armazenadas no banco de dados.

the_lotus
fonte