Além do Polymorphic Reference: 3 abordagens para modelar dados complexos em SQL
Você já se viu em um dilema ao modelar dados complexos em bancos relacionais? O Polymorphic Reference promete flexibilidade, mas esconde armadilhas que podem comprometer a integridade dos seus dados.
Nos bancos relacionais, existe uma maneira em que criarmos um relacionamento onde é possível ter um relacionamento em que uma coluna pode fazer referencia a diferentes tabelas. O Polymorphic Reference é uma técnica que criamos relações com multiplas tabela utilizando dois campos, o campo type e um campo id. Na prática, como o banco não consegue criar uma chave estrangeira comum, porque a tabela destino muda dependendo do registro, fazemos também a referencia do tipo, porém surgem alguns problemas, como a quebra da integridade referencial, ou seja, não existem garantias que o registro que está sendo referenciado realmente exista, então geralmente esses problemas podem ser resolvidos via código ou com triggers. Com essa abordagem, conseguimos ter maior flexibilidade e organização na modelagem do banco de dados.
Entretanto, existem alguns contrapontos além da perca da integridade referencial também teremos consultas mais lentas em decorrência da sua complexidade, pois teremos um campo tipo para consultar também.
Mas em relação a integridade referencial os triggers não resolvem esse problema? Não exatamente pequeno gafanhoto, hehe. Pra quem não conhece, os trigger são trechos de código SQL que são executados automagicamente, a cada INSERT, UPDATE e DELETE. Então antes de utilizarmos devemos considerar ...
- os triggers iriam sobrecarregar o banco principalmente se tivermos muitas inserções de registros
- (por experiência própria 😅) vai aumentar bastante a complexidade para manutenções
- dependendo do banco, eles podem não garantir 100% que em situações concorrentes (varias transações ao mesmo tempo) a integridade não quebre, por não existir mais o isolamento das transações
- os triggers deixam os modelos mais confusos para pessoas que não o conhecem, ou seja, será necessário mais tempo para outras pessoas conseguirem dar suporte por conta da curva de aprendizado
- sei que existem muitos ninjas de SQL no mundo, mas alguns ORMs ainda são meros mortais e não irão saber lidar com os triggers
Dito isso, em meu modo de enxergar, podemos adotar o Polymorphic Reference em alguns casos.
- quanto tivermos uma quantidade considerável de possíveis relacionamentos
- a modelagem do nosso banco mudar constantemente
- sistemas com dados heterogêneos, como por exemplo, comentarios, anexos, tags ou "likes", podem ser associados a qualquer tipo de entidade em uma rede social
- 🔰 tivermos uma situação em que a velocidade da consulta não for tão crítica
- cenários em que precisamos refletir um NoSQL da vida
- precisarmos evitar colunas nulas
Em contraponto, devemos evitar quando...
- a integridade referencial é algo primordial
- a performance for prioridade
- se tivermos um modelo estável que não vai mudar tanto
Mas existem outras formas, se utilizarmos Union Table com Inherited Structure??
O Union Table é uma abordagem que usa herança de tabelas... exatamente, herança no SQL. As entidades filhas irão herdar uma estrutura padrão de uma entidade mãe, assim como nas linguagens de programação. Podendo ser usada como uma alternativa ao Polymorphic Reference, em que ao invés de criarmos dois campos (id e tipo), referenciamos a entidade mãe com uma chave estrangeira tradicional.
Com isso conseguimos inclusive manter a integridade referencial, teremos querys mais simples, conseguimos expandir para outros tipos e teremos uma modelagens mais entendível com conceitos próximos de POO [...] porém, um fato que não mencionei é esse recurso é nativo do Postgres 🤧, as query são mais simples mas em grandes volumes de dados pode não ser interessante devido a varredura em todas as entidades filhas, assim como no Polymorphic Reference, alguns ORMs não vão manjar tanto e em casos específicos, contraints podem não ter o efeito esperado porque cada entidade filha irá ter seus próprios índices.
Sendo assim, é interessante usar essa abordagem quando soubermos que em todo o projeto será TeamPostgres e nunca será preciso trocar, existir uma necessidade de unificar tipos diferentes, existir frequentemente a necessidade de dados em comum entre as entidades ou até quando quisermos ter semelhanças conceituais com sistemas que utilizam POO. Então devemos evitar essa técnica quando soubermos que vamos precisar trocar de banco de dados como por exemplo um MySQL, SQL SERVER da vida, formos utilizar um ORM que não da suporte para a função de herança no SQL, tivermos um volume imenso de dados, e possuirmos um modelo estável, pra que usar?
Mas não acaba por ai, também podemos fazer Separate Junctions Tables, a abordagem clássica. em comparação com as outras abordagens é mais verbosa que QUE RIMA COM GRUG, o feijão com arroz, o simples que sempre funciona, sem gambiarras. Com ela iremos
- manter a integridade das referencias no banco
- uma performance mais previsível
- podemos usar qualquer banco de dados
- ainda teremos clareza no modelo, inclusive para ORMs
Mas como nem tudo são flores, para quem não gosta tanto teremos mais tabelas e as consultas podem as vezes ser mais trabalhosas. Lembrando que independente de técnica você for utilizar, você deve focar em resolver o problema e não criar outro.