Quais são as melhores práticas para modelar herança em bancos de dados?
Quais são os trade-offs (por exemplo, querability)?
(Estou mais interessado no SQL Server e .NET, mas também quero entender como outras plataformas solucionam esse problema.)
Quais são as melhores práticas para modelar herança em bancos de dados?
Quais são os trade-offs (por exemplo, querability)?
(Estou mais interessado no SQL Server e .NET, mas também quero entender como outras plataformas solucionam esse problema.)
Respostas:
Existem várias maneiras de modelar herança em um banco de dados. Qual você escolher depende de suas necessidades. Aqui estão algumas opções:
Tabela por tipo (TPT)
Cada classe tem sua própria tabela. A classe base possui todos os elementos da classe base e cada classe que deriva dela tem sua própria tabela, com uma chave primária que também é uma chave estrangeira para a tabela da classe base; a classe da tabela derivada contém apenas os diferentes elementos.
Então, por exemplo:
class Person {
public int ID;
public string FirstName;
public string LastName;
}
class Employee : Person {
public DateTime StartDate;
}
Resultaria em tabelas como:
table Person
------------
int id (PK)
string firstname
string lastname
table Employee
--------------
int id (PK, FK)
datetime startdate
Tabela por hierarquia (TPH)
Há uma tabela única que representa toda a hierarquia de herança, o que significa que várias das colunas provavelmente serão esparsas. Uma coluna discriminadora é adicionada, informando ao sistema que tipo de linha é esse.
Dadas as classes acima, você acaba com esta tabela:
table Person
------------
int id (PK)
int rowtype (0 = "Person", 1 = "Employee")
string firstname
string lastname
datetime startdate
Para todas as linhas do tipo 0 (Pessoa), a data de início será sempre nula.
Tabela por concreto (TPC)
Cada classe tem sua própria tabela totalmente formada, sem referências a outras tabelas.
Dadas as classes acima, você acaba com estas tabelas:
table Person
------------
int id (PK)
string firstname
string lastname
table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate
O design adequado do banco de dados não é nada como o design adequado de objetos.
Se você planeja usar o banco de dados para algo que não seja simplesmente serializar seus objetos (como relatórios, consultas, uso de vários aplicativos, inteligência de negócios etc.), não recomendo nenhum tipo de mapeamento simples de objetos para tabelas.
Muitas pessoas pensam em uma linha em uma tabela de banco de dados como uma entidade (passei muitos anos pensando nesses termos), mas uma linha não é uma entidade. É uma proposição. Uma relação de banco de dados (isto é, tabela) representa alguma declaração de fato sobre o mundo. A presença da linha indica que o fato é verdadeiro (e, inversamente, sua ausência indica que o fato é falso).
Com esse entendimento, você pode ver que um único tipo em um programa orientado a objetos pode ser armazenado em uma dúzia de relações diferentes. E vários tipos (unidos por herança, associação, agregação ou completamente não afiliados) podem ser parcialmente armazenados em uma única relação.
É melhor perguntar a si mesmo, quais fatos você deseja armazenar, quais perguntas você deseja obter respostas, quais relatórios deseja gerar.
Depois que o design do banco de dados apropriado é criado, é simples criar consultas / visualizações que permitem serializar seus objetos para essas relações.
Exemplo:
Em um sistema de reservas de hotéis, talvez você precise armazenar o fato de que Jane Doe tem uma reserva para um quarto no Seaview Inn de 10 a 12 de abril. Isso é um atributo da entidade cliente? É um atributo da entidade hoteleira? É uma entidade de reserva com propriedades que incluem cliente e hotel? Pode ser uma ou todas essas coisas em um sistema orientado a objetos. Em um banco de dados, não é nada disso. É simplesmente um fato.
Para ver a diferença, considere as duas consultas a seguir. (1) Quantas reservas de hotel Jane Doe tem para o próximo ano? (2) Quantos quartos estão reservados para 10 de abril no Seaview Inn?
Em um sistema orientado a objetos, a consulta (1) é um atributo da entidade cliente e a consulta (2) é um atributo da entidade hoteleira. Esses são os objetos que expõem essas propriedades em suas APIs. (Embora, obviamente, os mecanismos internos pelos quais esses valores são obtidos possam envolver referências a outros objetos.)
Em um sistema de banco de dados relacional, ambas as consultas examinariam a relação de reserva para obter seus números e, conceitualmente, não há necessidade de se preocupar com nenhuma outra "entidade".
Assim, é tentando armazenar fatos sobre o mundo - em vez de tentar armazenar entidades com atributos - que um banco de dados relacional adequado é construído. E, uma vez projetado adequadamente, as consultas úteis que não foram sonhadas durante a fase de design podem ser facilmente construídas, uma vez que todos os fatos necessários para atender essas consultas estão em seus devidos lugares.
Employment
tabela que reúne todos os empregos com suas datas de início. Portanto, se Employer
é importante conhecer a data de início do emprego atual , esse poderia ser um caso de uso adequado para a View
, que inclui essa propriedade consultando? (nota: parece que por causa do '-' logo após o meu apelido, não recebi nenhuma notificação no seu comentário)
Resposta curta: você não.
Se você precisar serializar seus objetos, use um ORM ou, melhor ainda, algo como registro de ação ou prevalência.
Se você precisar armazenar dados, armazene-os de maneira relacional (tomando cuidado com o que está armazenando e prestando atenção no que Jeffrey L Whitledge acabou de dizer), não afetado pelo design do seu objeto.
Os padrões TPT, TPH e TPC são os caminhos a seguir, conforme mencionado por Brad Wilson. Mas algumas notas:
as classes filhas que herdam de uma classe base podem ser vistas como entidades fracas na definição da classe base no banco de dados, o que significa que são dependentes da classe base e não podem existir sem ela. Eu já vi várias vezes que IDs únicos são armazenados para cada tabela filha, mantendo o FK na tabela pai. Um FK é suficiente e é ainda melhor ter a cascata ao excluir habilitada para a relação FK entre as tabelas filho e base.
No TPT, vendo apenas os registros da tabela base, você não consegue encontrar qual classe filho o registro está representando. Às vezes, isso é necessário quando você deseja carregar uma lista de todos os registros (sem fazer isso select
em todas as tabelas filhas). Uma maneira de lidar com isso é ter uma coluna representando o tipo da classe filho (semelhante ao campo rowType no TPH), misturando o TPT e o TPH de alguma forma.
Digamos que desejamos criar um banco de dados que contenha o seguinte diagrama de classes de formas:
public class Shape {
int id;
Color color;
Thickness thickness;
//other fields
}
public class Rectangle : Shape {
Point topLeft;
Point bottomRight;
}
public class Circle : Shape {
Point center;
int radius;
}
O design do banco de dados para as classes acima pode ser assim:
table Shape
-----------
int id; (PK)
int color;
int thichkness;
int rowType; (0 = Rectangle, 1 = Circle, 2 = ...)
table Rectangle
----------
int ShapeID; (FK on delete cascade)
int topLeftX;
int topLeftY;
int bottomRightX;
int bottomRightY;
table Circle
----------
int ShapeID; (FK on delete cascade)
int centerX;
int center;
int radius;
Existem dois tipos principais de herança que você pode configurar em um banco de dados, tabela por entidade e tabela por hierarquia.
Tabela por entidade é onde você tem uma tabela de entidade base que possui propriedades compartilhadas de todas as classes filho. Você tem por classe filho outra tabela, cada uma com apenas propriedades aplicáveis a essa classe. Eles estão ligados 1: 1 pelos seus PK's
Tabela por hierarquia é o local em que todas as classes compartilham uma tabela e as propriedades opcionais são anuláveis. Também é um campo discriminador, que é um número que indica o tipo que o registro atualmente possui
SessionTypeID é discriminador
O destino por hierarquia é mais rápido de ser consultado, pois você não precisa de junções (apenas o valor discriminador), enquanto o destino por entidade precisa de junções complexas para detectar que tipo de algo é e recuperar todos os seus dados.
Edit: As imagens que mostro aqui são capturas de tela de um projeto no qual estou trabalhando. A imagem do ativo não está completa, daí o vazio, mas era principalmente para mostrar como sua configuração, não o que colocar dentro de suas tabelas. Isso depende de você ;). A tabela de sessões contém informações da sessão de colaboração virtual e pode ser de vários tipos de sessões, dependendo do tipo de colaboração envolvida.
Você normalizaria seu banco de dados e isso realmente refletiria sua herança. Pode ter degradação no desempenho, mas é assim que ocorre com a normalização. Você provavelmente terá que usar o bom senso para encontrar o equilíbrio.
repetição de resposta de thread semelhante
no mapeamento OR, a herança mapeia para uma tabela pai em que as tabelas pai e filho usam o mesmo identificador
por exemplo
create table Object (
Id int NOT NULL --primary key, auto-increment
Name varchar(32)
)
create table SubObject (
Id int NOT NULL --primary key and also foreign key to Object
Description varchar(32)
)
SubObject tem um relacionamento de chave estrangeira para Object. ao criar uma linha de SubObject, você deve primeiro criar uma linha de objeto e usar o ID nas duas linhas
EDIT: se você estiver procurando modelar o comportamento também, você precisaria de uma tabela Type que listasse os relacionamentos de herança entre tabelas e especificasse o nome do assembly e da classe que implementasse o comportamento de cada tabela
parece um exagero, mas tudo depende do motivo pelo qual você deseja usá-lo!
Usando o SQL ALchemy (Python ORM), você pode fazer dois tipos de herança.
A experiência que tive foi usando uma mesa de canto e tendo uma coluna discriminante. Por exemplo, um banco de dados de ovinos (sem brincadeira!) Armazenava todos os ovinos em uma tabela e Rams e ovelhas eram manipulados usando uma coluna de gênero nessa tabela.
Assim, você pode consultar todas as ovelhas e obter todas as ovelhas. Ou você pode consultar apenas por Ram, e ele só obterá Rams. Você também pode fazer coisas como ter uma relação que só pode ser um carneiro (isto é, o pai de uma ovelha) e assim por diante.
Observe que alguns mecanismos de banco de dados já fornecem mecanismos de herança nativamente como o Postgres . Veja a documentação .
Por exemplo, você consultaria o sistema Pessoa / Funcionário descrito em uma resposta acima desta maneira:
/ * Mostra o primeiro nome de todas as pessoas ou funcionários * / SELECT nome próprio FROM Pessoa; / * Mostra a data de início de todos os funcionários apenas * / SELECT data de início do funcionário;
Nessa escolha do seu banco de dados, você não precisa ser particularmente inteligente!