Uma Cardinalidade Um Para Um Implica Em: - O que é partes de um conjunto? O que é a cardinalidade de um conjunto ...
O que é partes de um conjunto? O que é a cardinalidade de um conjunto ...

Cardinalidade Um para Um em Banco de Dados

Esse é um tópico que aparece frequentemente em modelagem de dados e todo mundo entende a teoria na hora. A definição é simples demais para exigir um aviso: uma relação um-para-um (1:1) ocorre quando uma entidade na tabela A se conecta a exatamente uma entidade na tabela B, e vice-versa. A parte que as pessoas esquecem é o que isso implica na prática, porque a teoria nunca mostra os problemas que surgem quando você tenta aplicar.

uma cardinalidade um para um implica em:

Implica na criação de uma restrição de chave primária e chave estrangeira ligando duas tabelas, onde ambas as colunas envolvidas devem ser únicas. Na maioria dos SGBDs, você declara isso usando um FOREIGN KEY com uma restrição UNIQUE na tabela filha. Isso garante que não haja duplicação e que cada registro na tabela pai tenha, no máximo, um correspondente na tabela filho. A implicação mais importante é sobre integridade referencial. Você está obrigando o banco de dados a verificar, a cada INSERT ou UPDATE, se a chave já existe e se é única. Isso custa uma operação extra de varredura ou busca por índice. Não é nada que derrube performance em sistemas pequenos, mas em tabelas com milhões de linhas e escritas concorrentes, o overhead aparece. Eu vi queries de manutenção que dobravam o tempo de execução só porque a cardinalidade 1:1 tinha sido mal planejada.

Como implementar na prática

Vou partir direto para a implementação porque é onde as coisas costumam dar errado. O padrão é ter uma tabela principal e uma tabela complementar. A tabela complementar recebe uma coluna que é ao mesmo tempo PRIMARY KEY e FOREIGN KEY apontando para a tabela principal. Isso acontece porque, na cardinalidade um para um, o identificador da tabela filha precisa coincidir exatamente com o da tabela mãe. Se você usar uma coluna separada como FOREIGN KEY, terá que adicionar uma restrição UNIQUE manual além da chave estrangeira. A abordagem mais limpa é fazer a chave primária da filha ser também a chave estrangeira.

No PostgreSQL, a declaração fica algo assim: CREATE TABLE usuarios (id SERIAL PRIMARY KEY, nome VARCHAR(100) NOT NULL); CREATE TABLE perfis_usuario (id INT PRIMARY KEY REFERENCES usuarios(id) ON DELETE CASCADE, bio TEXT, foto_url VARCHAR(500));

Isso cria automaticamente a restrição de unicidade na tabela perfis_usuario porque a PRIMARY KEY já impõe isso. O ON DELETE CASCADE é opcional mas recomendo fortemente. Sem ele, você terá problemas quando alguém tentar deletar um usuário que ainda tem um perfil associado. No MySQL com InnoDB, o comportamento é o mesmo, mas fique atento a uma diferença: o MySQL não permite ON DELETE SET NULL em chaves primárias. Se você precisar de um delete que preserva o registro da filha zerando a referência, terá que lidar com isso na camada de aplicação. Eu perdi duas horas num domingo refatorando isso em um sistema legado que usava MySQL 5.6.

O caso que quase quebrou meu deploy

Trabalhei numa plataforma de e-commerce onde tínhamos uma tabela um-para-um entre pedidos e endereços_de_entrega. O problema não era a modelagem em si. O problema era que o time de produto decidiu, três meses depois de ir para produção, que um pedido poderia ter múltiplos endereços ao longo do tempo para fins de histórico. A cardinalidade 1:1 não suportava isso sem mudanças estruturais significativas. A solução foi migrar para uma cardinalidade um-para-muitos com uma coluna is_current booleana marcando o endereço ativo. Mas isso exigiu um script de migração que copiava os dados existentes, criava a nova tabela, e depois fazia um UPDATE massivo nos pedidos para atualizar as referências. Levou quatro horas de manutenção planneda e ninguém ficou satisfeito, exceto quem não precisava mais mentir para o banco de dados.

👉 Clique no botão abaixo para saber mais sobre o assunto!

Insights que ninguém conta

A primeira coisa contra-intuitiva é que cardinalidade um-para-um muitas vezes indica um mau design. Se duas tabelas têm uma relação estritamente 1:1, a pergunta que você deveria fazer é: por que elas estão separadas? A resposta mais comum é performance ou segurança. Você separa colunas grandes ou sensíveis em uma tabela à parte para evitar que fiquem sempre sendo carregadas junto com o registro principal. Isso faz sentido quando você tem colunas do tipo TEXT ou BLOB que só são acessadas em casos específicos. Carregar um campo BLOB de uma imagem em cada query de listagem é um custo desnecessário. Separar em uma tabela 1:1 resolve isso. Mas se você está fazendo isso por organisção conceitual apenas, considere unificar as tabelas. Querys ficam mais simples e você evita joins desnecessários.

Outro ponto que passa despercebido: índices automáticos. Quando você cria uma FOREIGN KEY, alguns SGBDs criam automaticamente um índice na coluna estrangeira. Outros não. O PostgreSQL cria. O MySQL com InnoDB cria. O SQL Server cria. O Oracle nem sempre cria. Se você não verificar isso, suas queries de join podem fazer table scan completo na tabela filha e o desempenho cai drasticamente em volumes maiores.

Quando NÃO usar cardinalidade um para um

Não use quando a relação puder evoluir para um-para-muitos no futuro. Relembro do caso de e-commerce mencionado acima. Mudar cardinalidade depois de ter dados é sempre doloroso. Se há qualquer chance de crescimento, comece com a cardinalidade mais flexível desde o início. Também evite quando a tabela filha for acessada com quase tanta frequência quanto a tabela mãe. Nesse caso, o join constante vai penalizar mais do quebeneficiar. Unifique as tabelas. Performance e simplicidade vencem organização conceitual na maioria das vezes.

Outro cenário ruim é quando você precisa de historização. Tabelas 1:1 não são projetadas para guardar versões ou históricos de mudanças. Se seu domínio exige rastrear quando um atributo mudou, use uma tabela separada com carimbo de data/hora e cardinalidade 1:N, não 1:1.

Alternativas quando o 1:1 não cabe

Se você precisa de extensibilidade, considere uma tabela de atributos EAV (Entity-Attribute-Value). É uma solução mais flexível mas com Trade-offs sérios de performance e complexidade de consulta. Use apenas quando realmente precisar de esquema dinâmico. Outra opção é JSON ou JSONB em colunas, disponível no PostgreSQL e MySQL. Você guarda campos adicionais dentro de uma estrutura semi-estruturada na própria tabela principal. Isso elimina a necessidade da tabela filha e do join, mas perde integridade referencial e tipagem forte. Depende do que você prioriza.

Para a maioria dos casos, a cardinalidade um-para-um bem implementada com chaves compostas ou chave estrangeira como chave primária continua sendo a solução mais robusta. A chave é decidir com antecedência se a separação de tabelas realmente agrega valor ou se é apenas organização estética.