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 VARCHAR
diretamente 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
- não use enum (opção 1)
- 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_type
s e depois service_line_item
s, 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 INT
campo 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 string
como 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).
Respostas:
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éssimoenum
é 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:
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.
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".
fonte
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.
fonte
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!
ou, para seus clientes franceses ...
fonte