@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 herança de tabela por hierarquia):
Usar uma única tabela como em sua primeira opção é provavelmente o design mais simples. Como você mencionou, muitos atributos que são específicos do subtipo terão que receber um
NULL
valor em linhas onde esses atributos não se aplicam. Com este modelo, você teria uma tabela de políticas, que ficaria mais ou menos assim:+------+---------------------+----------+----------------+------------------+
| id | date_issued | type | vehicle_reg_no | property_address |
+------+---------------------+----------+----------------+------------------+
| 1 | 2010-08-20 12:00:00 | MOTOR | 01-A-04004 | NULL |
| 2 | 2010-08-20 13:00:00 | MOTOR | 02-B-01010 | NULL |
| 3 | 2010-08-20 14:00:00 | PROPERTY | NULL | Oxford Street |
| 4 | 2010-08-20 15:00:00 | MOTOR | 03-C-02020 | NULL |
+------+---------------------+----------+----------------+------------------+
\------ COMMON FIELDS -------/ \----- SUBTYPE SPECIFIC FIELDS -----/
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, pois não há metadados para definir quais atributos pertencem a quais subtipos.
-
Você também não pode imporNOT NULL
em atributos de um subtipo que deveriam ser obrigatórios. Você teria que lidar com isso em seu aplicativo, o que em geral não é o ideal.
Herança de tabela de concreto:
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:
--// Table: policies_motor
+------+---------------------+----------------+
| id | date_issued | vehicle_reg_no |
+------+---------------------+----------------+
| 1 | 2010-08-20 12:00:00 | 01-A-04004 |
| 2 | 2010-08-20 13:00:00 | 02-B-01010 |
| 3 | 2010-08-20 15:00:00 | 03-C-02020 |
+------+---------------------+----------------+
--// Table: policies_property
+------+---------------------+------------------+
| id | date_issued | property_address |
+------+---------------------+------------------+
| 1 | 2010-08-20 14:00:00 | Oxford Street |
+------+---------------------+------------------+
Esse design basicamente resolverá os problemas identificados para o método de tabela única:
-
Atributos obrigatórios agora podem ser aplicados comNOT 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 ovehicle_reg_no
campo para uma política de propriedade.
-
Não há necessidade dotype
atributo como no método de tabela única. O tipo agora é definido pelos metadados:o nome da tabela.
No entanto, este modelo também apresenta 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. Isso definitivamente não é SECO.
-
Pesquisar todas as políticas, independentemente do subtipo, torna-se difícil e exigiria um monte deUNION
s.
É assim que você teria que consultar todas as políticas, independentemente do tipo:
SELECT date_issued, other_common_fields, 'MOTOR' AS type
FROM policies_motor
UNION ALL
SELECT date_issued, other_common_fields, 'PROPERTY' AS type
FROM policies_property;
Observe como a adição de novos subtipos exigiria que a consulta acima fosse modificada com um
UNION ALL
adicional para cada subtipo. Isso pode facilmente levar a bugs em seu aplicativo se essa operação for esquecida. Herança de tabela de classe (também conhecida como herança de tabela por 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 chave estrangeira para a tabela base. Exemplo:
CREATE TABLE policies (
policy_id int,
date_issued datetime,
-- // other common attributes ...
);
CREATE TABLE policy_motor (
policy_id int,
vehicle_reg_no varchar(20),
-- // other attributes specific to motor insurance ...
FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);
CREATE TABLE policy_property (
policy_id int,
property_address varchar(20),
-- // other attributes specific to property insurance ...
FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);
Esta solução resolve os problemas identificados nos outros dois projetos:
-
Atributos obrigatórios podem ser aplicados comNOT 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 dotype
atributo.
-
Agora os atributos comuns não são mais misturados com os 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.
-
Gerenciando umid
de incremento automático para as políticas torna-se mais fácil, porque isso pode ser tratado pela tabela base, em vez de cada tabela de subtipo gerá-las independentemente.
-
Pesquisar todas as políticas, independentemente do subtipo, agora se torna muito fácil:NãoUNION
s necessário - apenas umaSELECT * FROM policies
.
Considero a abordagem da tabela de classes como a mais adequada na maioria das situações.
Os nomes desses três modelos vêm do livro Patterns of Enterprise Application Architecture de Martin Fowler.