Estou pensando em como representar uma estrutura complexa em um banco de dados SQL Server.
Considere um aplicativo que precisa armazenar detalhes de uma família de objetos, que compartilham alguns atributos, mas que muitos outros não são comuns. Por exemplo, um pacote de seguro comercial pode incluir cobertura de responsabilidade, automóvel, propriedade e indenização no mesmo registro de apólice.
É trivial implementá-lo em C #, etc, pois você pode criar uma Política com uma coleção de Seções, em que Seção é herdada conforme necessário para os vários tipos de capa. No entanto, os bancos de dados relacionais não parecem permitir isso facilmente.
Percebo que existem duas opções principais:
Crie uma tabela Política e, em seguida, uma tabela Seções, com todos os campos necessários para todas as variações possíveis, a maioria das quais seria nula.
Crie uma tabela de políticas e várias tabelas de seção, uma para cada tipo de capa.
Ambas as alternativas parecem insatisfatórias, especialmente porque é necessário escrever consultas em todas as seções, o que envolveria inúmeras junções ou verificações nulas.
Qual é a melhor prática para esse cenário?
fonte
Respostas:
@Bill Karwin descreve três modelos de herança em seu livro SQL Antipatterns , ao propor soluções para o antipattern SQL Entity-Attribute-Value . Esta é uma breve visão geral:
Herança de tabela única (também conhecida como tabela por herança de hierarquia):
Usar uma única tabela como na sua primeira opção é provavelmente o design mais simples. Como você mencionou, muitos atributos específicos ao subtipo deverão receber um
NULL
valor nas linhas em que esses atributos não se aplicam. Com este modelo, você teria uma tabela de políticas, com a seguinte aparência:Manter o design simples é uma vantagem, mas os principais problemas com essa abordagem são os seguintes:
Quando se trata de adicionar novos subtipos, você teria que alterar a tabela para acomodar os atributos que descrevem esses novos objetos. Isso pode se tornar rapidamente problemático quando você tem muitos subtipos ou se planeja adicionar subtipos regularmente.
O banco de dados não poderá impor quais atributos se aplicam e quais não se aplicam, pois não há metadados para definir quais atributos pertencem a quais subtipos.
Você também não pode impor
NOT NULL
atributos de um subtipo que devem ser obrigatórios. Você precisaria lidar com isso em seu aplicativo, o que geralmente não é o ideal.Herança concreta da tabela:
Outra abordagem para lidar com a herança é criar uma nova tabela para cada subtipo, repetindo todos os atributos comuns em cada tabela. Por exemplo:
Esse design basicamente resolverá os problemas identificados para o método de tabela única:
Agora, os atributos obrigatórios podem ser aplicados
NOT NULL
.Adicionar um novo subtipo requer adicionar uma nova tabela em vez de adicionar colunas a uma existente.
Também não há risco de que um atributo inadequado seja definido para um subtipo específico, como o
vehicle_reg_no
campo para uma política de propriedade.Não há necessidade do
type
atributo, como no método de tabela única. O tipo agora é definido pelos metadados: o nome da tabela.No entanto, este modelo também possui algumas desvantagens:
Os atributos comuns são misturados com os atributos específicos do subtipo e não há uma maneira fácil de identificá-los. O banco de dados também não saberá.
Ao definir as tabelas, você teria que repetir os atributos comuns para cada tabela de subtipo. Definitivamente não é SECO .
A pesquisa de todas as políticas, independentemente do subtipo, torna-se difícil e exigiria vários
UNION
s.É assim que você precisaria consultar todas as políticas, independentemente do tipo:
Observe como a adição de novos subtipos exigiria que a consulta acima fosse modificada com um adicional
UNION ALL
para cada subtipo. Isso pode facilmente levar a erros no seu aplicativo se esta operação for esquecida.Herança de tabela de classe (também conhecida como tabela por herança de tipo):
Esta é a solução que @David menciona na outra resposta . Você cria uma única tabela para sua classe base, que inclui todos os atributos comuns. Em seguida, você criaria tabelas específicas para cada subtipo, cuja chave primária também serve como uma chave estrangeira para a tabela base. Exemplo:
Esta solução resolve os problemas identificados nos outros dois projetos:
Atributos obrigatórios podem ser aplicados com
NOT NULL
.Adicionar um novo subtipo requer adicionar uma nova tabela em vez de adicionar colunas a uma existente.
Não há risco de que um atributo inadequado seja definido para um subtipo específico.
Não há necessidade do
type
atributo.Agora, os atributos comuns não são mais misturados aos atributos específicos do subtipo.
Podemos ficar secos, finalmente. Não há necessidade de repetir os atributos comuns para cada tabela de subtipo ao criar as tabelas.
O gerenciamento de um incremento automático
id
para as políticas se torna mais fácil, porque isso pode ser tratado pela tabela base, em vez de cada tabela de subtipo gerá-las independentemente.A pesquisa de todas as políticas, independentemente do subtipo, agora é muito fácil: não
UNION
é necessário - apenas aSELECT * FROM policies
.Considero a abordagem da tabela de classes a mais adequada na maioria das situações.
Os nomes desses três modelos vêm do livro de Martin Fowler, Patterns of Enterprise Application Architecture .
fonte
A terceira opção é criar uma tabela "Política" e, em seguida, uma tabela "SectionsMain" que armazena todos os campos comuns aos tipos de seção. Em seguida, crie outras tabelas para cada tipo de seção que contenha apenas os campos que não são comuns.
Decidir qual é o melhor depende principalmente de quantos campos você possui e como deseja gravar seu SQL. Todos eles trabalhariam. Se você tiver apenas alguns campos, provavelmente eu usaria o número 1. Com "lotes" de campos, eu me inclinaria para o # 2 ou # 3.
fonte
Com as informações fornecidas, eu modelaria o banco de dados para ter o seguinte:
POLÍTICAS
PASSIVOS
PROPRIEDADES
... e assim por diante, porque eu esperaria que houvesse atributos diferentes associados a cada seção da política. Caso contrário, poderia haver uma única
SECTIONS
tabela e, além dapolicy_id
, haveria umasection_type_code
...De qualquer forma, isso permitiria o suporte de seções opcionais por política ...
Não entendo o que você acha insatisfatório sobre essa abordagem - é assim que você armazena dados enquanto mantém a integridade referencial e não duplica os dados. O termo é "normalizado" ...
Como o SQL é baseado em SET, é um pouco estranho aos conceitos de programação processual / OO e exige que o código faça a transição de um domínio para outro. ORMs são frequentemente considerados, mas não funcionam bem em sistemas complexos e de alto volume.
fonte
Além disso, na solução Daniel Vassallo, se você usa o SQL Server 2016 ou superior, existe outra solução que eu usei em alguns casos sem perda considerável de desempenho.
Você pode criar apenas uma tabela com apenas o campo comum e adicionar uma única coluna com a sequência JSON que contém todos os campos específicos do subtipo.
Testei esse design para gerenciar herança e estou muito feliz com a flexibilidade que posso usar no aplicativo relativo.
fonte
A outra maneira de fazer isso é usar o
INHERITS
componente Por exemplo:Assim, é possível definir uma herança entre tabelas.
fonte
INHERITS
além do PostgreSQL ? MySQL por exemplo?Eu me inclino para o método nº 1 (uma tabela unificada de seções), a fim de recuperar com eficiência diretivas inteiras com todas as suas seções (o que eu assumo que seu sistema estará fazendo muito).
Além disso, não sei qual versão do SQL Server você está usando, mas em 2008+ Sparse Columns ajuda a otimizar o desempenho em situações em que muitos dos valores em uma coluna serão NULL.
Por fim, você terá que decidir o quão "semelhantes" são as seções de política. A menos que eles diferem substancialmente, acho que uma solução mais normalizada pode ser mais problemática do que vale a pena ... mas apenas você pode fazer essa ligação. :)
fonte
Como alternativa, considere o uso de bancos de dados de documentos (como o MongoDB) que oferecem suporte nativo a estruturas de dados e aninhamento avançados.
fonte
Confira a resposta que dei aqui
Mapeamento individual do NHibernate fluente com chaves sintéticas
fonte