É um desperdício criar uma nova tabela de banco de dados em vez de usar o tipo de dados enum?

38

Suponha que eu possua 4 tipos de serviços que ofereço (é improvável que eles mudem com frequência):

  • Teste
  • desenhar
  • Programação
  • De outros

Suponha que eu tenha de 60 a 80 serviços reais que se enquadram em uma das categorias acima. Por exemplo, 'um serviço' pode ser "Programa de Teste usando a técnica A" e é do tipo "Teste".

Eu quero codificá-los em um banco de dados. Eu vim com algumas opções:

Opção 0:

Use VARCHARdiretamente para codificar o tipo de serviço diretamente como uma sequência

Opção 1:

Use banco de dados enum. Mas enum é mau

Opção 2:

use duas tabelas:

service_line_item (id, service_type_id INT, description VARCHAR);
service_type (id, service_type VARCHAR);

Posso até desfrutar de integridade referencial:

ALTER service_line_item 
    ADD FOREIGN KEY (service_type_id) REFERENCES service_type (id);

Parece bom, sim?

Mas ainda tenho que codificar as coisas e lidar com números inteiros, ou seja, ao preencher a tabela. Ou eu tenho que criar programação elaborada ou construções de banco de dados ao preencher ou lidar com a tabela. Ou seja, JOINs quando lida diretamente com o banco de dados ou cria novas entidades orientadas a objetos no lado da programação e garante que eu as opere corretamente.

Opção 3:

Não use enum, não use duas tabelas, apenas use uma coluna inteira

service_line_item (
    id,
    service_type INT,        -- use 0, 1, 2, 3 (for service types)
    description VARCHAR
);

Isso é como um 'enum falso' que requer mais sobrecarga no lado do código, como, por exemplo, saber disso {2 == 'Programming'}e lidar com isso adequadamente.

Questão:

Atualmente eu o implementei usando a Opção 2 , guiada sob conceitos

  1. não use enum (opção 1)
  2. evite usar um banco de dados como planilha (opção 0)

Mas não posso deixar de sentir que isso me parece um desperdício em termos de programação e sobrecarga cognitiva - tenho que estar ciente de duas tabelas e lidar com duas tabelas, versus uma.

Por um "caminho menos desperdício", estou olhando Option 3. A TI é mais leve e requer essencialmente as mesmas construções de código para operar (com pequenas modificações, mas complexidade e estrutura são basicamente as mesmas, mas com uma única tabela)

Suponho que, idealmente, nem sempre seja um desperdício, e há bons casos para qualquer uma das opções, mas há uma boa orientação sobre quando alguém deve usar a Opção 2 e quando a Opção 3?

Quando existem apenas dois tipos (binários)

Para adicionar um pouco mais a essa pergunta ... no mesmo local, tenho uma opção binária de serviço "Padrão" ou "Exceção", que pode ser aplicado ao item de linha de serviço. Eu codifiquei isso usando a opção 3 .

Eu escolhi não criar uma nova tabela apenas para manter os valores {"Padrão", "Exceção"}. Portanto, minha coluna contém {0, 1} e o nome da coluna é chamado exception, e meu código está fazendo uma tradução {0, 1} => {STANDARD, EXCEPTION}(da qual codifiquei como constantes na linguagem de programação)

Até agora não gostamos dessa maneira ..... (não gostamos da opção 2 nem da opção 3). Eu acho a opção 2 superior a 3, mas com mais sobrecarga, e ainda assim não consigo escapar da codificação das coisas como números inteiros, independentemente da opção usada em 2 e 3.

ORM

Para adicionar algum contexto, depois de ler as respostas - eu apenas comecei a usar um ORM novamente (recentemente), no meu caso, Doutrina 2. Depois de definir o esquema do banco de dados por meio de anotações, eu queria preencher o banco de dados. Como todo o meu conjunto de dados é relativamente pequeno, eu queria tentar usar construções de programação para ver como ele funciona.

Eu primeiro preenchi service_types e depois service_line_items, pois havia uma lista existente de uma planilha real. Portanto, coisas como 'padrão / exceção' e 'Teste' são todas as strings da planilha e precisam ser codificadas em tipos apropriados antes de armazená-las no DB.

Encontrei esta resposta SO: O que você usa em vez de ENUM na doutrina2? , que sugeriu não usar a construção enum do DB, mas usar um INTcampo e codificar os tipos usando a construção 'const' da linguagem de programação.

Mas, como apontado na pergunta SO acima, posso evitar o uso de números inteiros diretamente e usar construções de linguagem - constantes - uma vez definidas.

Mas ainda assim ... não importa como você o vire, se eu estou começando stringcomo um tipo, primeiro tenho que convertê-lo para um tipo adequado, mesmo ao usar um ORM.

Então, se diz $str = 'Testing';, eu ainda preciso ter um bloco em algum lugar que faça algo como:

switch($str):
{ 
    case 'Testing':  $type = MyEntity::TESTING; break;
    case 'Other':    $type = MyEntity::OTHER; break;
}

A coisa boa é que você não está lidando com números inteiros / números mágicos [em vez disso, lidando com quantidades constantes codificadas], mas o ruim é que você não pode extrair automaticamente as coisas dentro e fora do banco de dados sem essa etapa de conversão. conhecimento.

E foi isso que eu quis dizer, em parte, dizendo coisas como "ainda tenho que codificar e lidar com números inteiros". (Concedido, agora, após o comentário de Ocramius, não precisarei lidar diretamente com números inteiros, mas sim com constantes nomeadas e alguma conversão de / para constantes, conforme necessário).

Dennis
fonte
9
Faça o que fizer, não faça o # 3. O psicopata que o mantém constantemente precisa descobrir o que esses números mágicos significam. Se você fizer isso, é melhor torcer para que eles não saibam onde você mora. blog.codinghorror.com/coding-for-violent-psychopaths #
RubberDuck
7
Gosto da opção 2. Se você não gosta da proliferação de tabelas de pesquisa, use uma tabela e adicione uma coluna "tipo de pesquisa". Mas sim, criar uma tabela de pesquisa é a maneira "padrão" de fazer isso, pois permite fazer coisas divertidas, como preencher facilmente uma lista suspensa na interface do usuário.
Robert Harvey
Não use "EDITAR" em suas postagens aqui; nós não somos um fórum. Cada postagem do Stack Exchange já contém um histórico de edição detalhado que qualquer pessoa pode visualizar.
Robert Harvey
se não consigo usar EDIT, o que devo usar?
Dennis
Basta editar a postagem e torná-la natural, como eu já fiz. Veja o histórico de edições para revisar as alterações.
Robert Harvey

Respostas:

35

A opção 2, usando tabelas de referência, é a maneira padrão de fazê-lo. Ele foi usado por milhões de programadores e é conhecido por funcionar. É um padrão , para que qualquer pessoa que esteja olhando suas coisas saiba imediatamente o que está acontecendo. Existem bibliotecas e ferramentas que funcionam em bancos de dados, poupando você de muito e muito trabalho, que lidarão com isso corretamente. Os benefícios de usá-lo são inúmeros.

É um desperdício? Sim, mas apenas um pouco. Qualquer banco de dados meio decente sempre manterá em cache pequenas tabelas tão frequentemente unidas, para que o desperdício seja geralmente imperceptível.

Todas as outras opções que você descreveu são ad hoc e hacky, incluindo MySQL enum, porque não fazem parte do padrão SQL. (Fora isso, o que é péssimo enumé a implementação do MySQL, não a ideia em si. Eu não me importaria de vê-la um dia como parte do padrão.)

Sua opção final nº 3 com o uso de um número inteiro simples é especialmente hacky. Você obtém o pior de todos os mundos: sem integridade referencial, sem valores nomeados, sem conhecimento definitivo no banco de dados sobre o que significa um valor, apenas números inteiros arbitrários lançados por todo o lugar. Por esse token, você também pode parar de usar constantes no seu código e começar a usar valores codificados. circumference = radius * 6.28318530718;. Que tal isso?

Eu acho que você deve reexaminar por que acha onerosas as tabelas de referência. Ninguém mais os acha onerosos, até onde eu sei. Será que é porque você não está usando as ferramentas certas para o trabalho?

Sua frase sobre ter que "codificar coisas e lidar com números inteiros", ou "criar construções elaboradas de programação" ou "criar novas entidades orientadas a objetos no lado da programação", me diz que talvez você esteja tentando fazer relações relacionais a objetos mapeamento (ORM) em tempo real disperso por todo o código do seu aplicativo ou, na melhor das hipóteses, você pode tentar rolar seu próprio mecanismo de mapeamento relacional a objetos, em vez de usar uma ferramenta ORM existente para o trabalho, como o Hibernate. Todas essas coisas são fáceis com o Hibernate. Demora um pouco para aprendê-lo, mas depois de aprendê-lo, você pode realmente se concentrar no desenvolvimento de seu aplicativo e esquecer a mecânica de como representar coisas no banco de dados.

Por fim, se você deseja facilitar sua vida ao trabalhar diretamente com o banco de dados, há pelo menos duas coisas que você pode fazer, nas quais posso pensar agora:

  1. Crie visualizações que unam suas tabelas principais a quaisquer tabelas de referência que eles referenciem, para que cada linha contenha não apenas os IDs de referência, mas também os nomes correspondentes.

  2. Em vez de usar um ID inteiro para a tabela de referência, use uma coluna CHAR (4), com abreviações de 4 letras. Portanto, os IDs de suas categorias se tornariam "TEST", "DSGN", "PROG", "OTHR". (Suas descrições permaneceriam palavras inglesas apropriadas, é claro.) Será um pouco mais lento, mas confie em mim, ninguém notará.

Finalmente, quando existem apenas dois tipos, a maioria das pessoas usa apenas uma coluna booleana. Portanto, essa coluna "padrão / exceção" seria implementada como um booleano e seria chamada "IsException".

Mike Nakis
fonte
3
Como um aparte, o Postgres também possui tipos de enumeração . Eles são simples e nada de especial, permitindo que você use uma string legível como um valor, mas que um número inteiro mais eficiente seja usado sob o capô.
Kat
E o caso em que os dados são consequentemente repetidos, mas não redundantes (por exemplo, não resultam em anomalias de atualização / inserção / exclusão)? Por exemplo, o sexo de uma pessoa (improvável para introduzir novos tipos de dados, não vai precisar alterar o nome de um gênero, etc.)
Adam Thompson
Isto: porque eventualmente você descobrirá que precisa de um "ambiente de aceitação" e que suas enumerações que não mudam precisam ser alteradas.
Pieter B
3

Opção 2 com constantes ou enumerações no final da programação.
Embora ele duplique o conhecimento, violando o princípio Fonte Única da Verdade, você pode lidar com ele usando a técnica Fail-fast . Quando o sistema é carregado, ele verifica se os valores de enumeração ou const existem no banco de dados. Caso contrário, o sistema deve gerar um erro e recusar o carregamento. Geralmente, será mais barato corrigir esse bug nesse momento ou mais tarde, quando algo mais sério puder ter acontecido.

José Margaça Lopes
fonte
0

Não há nada para impedi-lo de usar strings [curtas] como chaves, para que você ainda possa ter a legibilidade dos nomes em suas tabelas e não recorrer à codificação de número substituto sem sentido. Você ainda deve ter uma tabela separada para descrever os tipos de serviço, apenas com a possibilidade de, digamos, que seu aplicativo seja internacional!

Seus usuários podem ver suas quatro categorias em seu próprio idioma, mas suas tabelas de banco de dados ainda contêm valores que você pode ler - e nenhuma delas requer nenhuma estrutura de banco de dados ou alterações de código!

table service_type 
( id VARCHAR 
, name VARCHAR 
  primary key ( id ) 
);
table service_line_item 
( id 
, service_type VARCHAR 
, description VARCHAR
  foreign key ( service_type ) references service_type ( id )
);

select * from service_type ; 

+-------------+----------------+
| id          | name           |
+-------------+----------------+
| Testing     | Testen         |
| Design      | Design         | 
| Programming | Programmierung |
| Other       | Andere         |
+-------------+----------------+

ou, para seus clientes franceses ...

update services_types set name = 'Essai'         where id = 'Testing'; 
update services_types set name = 'Conception'    where id = 'Design'; 
update services_types set name = 'Programmation' where id = 'Programming'; 
update services_types set name = 'Autre'         where id = 'Other'; 
Phill W.
fonte