Como você efetivamente modela a herança em um banco de dados?

131

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.)

Even Mien
fonte
14
Se você está interessado em "melhores práticas", a maioria das respostas está simplesmente incorreta. As práticas recomendadas determinam que o RDb e o aplicativo sejam independentes; eles têm critérios de design completamente diferentes. Portanto, "modelar herança" em um banco de dados (ou modelar o RDb para se adequar a um único aplicativo ou idioma do aplicativo) é uma prática muito ruim, desinformada e quebra as regras básicas de design do RDb, e o prejudica.
PerformanceDBA
possível duplicata Algo como herança em design de banco de dados
Steve Chambers
6
@PerformanceDBA Então, qual é a sua sugestão para evitar a herança no modelo de banco de dados? Digamos que temos 50 tipos diferentes de professores e que queremos conectar esse professor em particular à turma. Como você conseguiria isso sem ter herança?
svlada
1
@svlada. Isso é simples de implementar em um RDb, portanto, a "herança" é necessária. Faça uma pergunta, inclua a tabela defns e um exemplo, e eu responderei em detalhes. Se você fizer isso em termos OO, será uma bagunça real.
PerformanceDBA

Respostas:

162

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
Brad Wilson
fonte
23
"O que você escolhe depende de suas necessidades" - por favor, elabore, pois acho que os motivos das escolhas formam o núcleo da questão.
728 Alex
12
Veja meu comentário sobre a questão. O uso de novos nomes engraçados para os termos técnicos do Rdb existentes gera confusão. "TPT" é supertipo-subtipo. "TPH" é não normalizado, um erro grave. "TPH" é ainda menos normalizado, outro erro grave.
PerformanceDBA
45
Somente um DBA presume que a desnormalização é sempre um erro. :)
Brad Wilson
7
Embora eu admita que a desnormalização resulta em ganhos de desempenho em alguns casos, isso se deve inteiramente a uma separação incompleta (ou inexistente) entre a estrutura lógica e física dos dados no DBMS. Infelizmente, a maioria dos DBMS comerciais sofre com esse problema. @PerformanceDBA está correto. A desnormalização é um erro de julgamento, sacrificando a consistência dos dados pela velocidade. Infelizmente, é uma escolha que um DBA ou desenvolvedor nunca precisaria fazer se o DBMS fosse projetado corretamente. Para o registro eu não sou um DBA.
27913 Kenneth Cochran
6
@Brad Wilson. Somente um desenvolvedor desnormalizaria "por desempenho" ou de outra forma. Muitas vezes, não é des normalização, a verdade é que não é normalizado. Que des-normalização ou não-normalizado é um erro, é um fato, apoiado pela teoria e experimentado por milhões, não é uma "presunção".
PerformanceDBA
133

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.

Jeffrey L Whitledge
fonte
12
+1 Finalmente, uma ilha de conhecimento genuíno em um mar de ignorância (e recusa em aprender algo fora do seu âmbito). Concordado, não é mágico: se o RDb é projetado usando princípios de RDb, é fácil "mapear" ou "projetar" qualquer "classe". Forçar o RDb em requisitos de classe é simplesmente incorreto.
PerformanceDBA
2
Resposta interessante. Como você sugeriria a modelagem do exemplo Pessoa-Funcionário na resposta aceita?
sevenforce
2
@ sevenforce-O design do banco de dados realmente depende dos requisitos do sistema, que não são fornecidos. Não há informações suficientes para decidir. Em muitos casos, algo semelhante ao design da "tabela por tipo" pode ser apropriado, se não for seguido de maneira servil. Por exemplo, data de início é provavelmente uma boa propriedade para um objeto Employee, mas no banco de dados deve ser realmente um campo na tabela Emprego, pois uma pessoa pode ser contratada várias vezes com várias datas de início. Isso não importa para os objetos (que usariam os mais recentes), mas é importante no banco de dados.
Jeffrey L Whitledge
2
Claro, minha pergunta era principalmente sobre a maneira de modelar herança. Desculpe por não ter sido claro o suficiente. Obrigado. Como você mencionou, provavelmente deve haver uma Employmenttabela 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)
sevenforce
5
Esta é uma verdadeira jóia de resposta. Vai levar algum tempo para realmente afundar e exigir algum exercício para dar certo, mas isso já influenciou meu processo de pensamento no design de banco de dados relacional.
MarioDS 16/08
9

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.

Marcin
fonte
3
+1 Tentar modelar a herança em um banco de dados é um desperdício de bons recursos relacionais.
21430 Daniel Spiewak
7

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;
imang
fonte
4

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

texto alternativo

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

texto alternativo 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.

mattlant
fonte
Eu também consideraria o Target por classe Concrete como realmente não modelar bem a herança e, portanto, não mostrei.
mattlant
Você poderia adicionar uma referência de onde é a ilustração?
chryss
Onde estão as imagens que você está falando no final de sua resposta?
Musa Haidari
1

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.

Per Hornshøj-Schierbeck
fonte
2
por que as pessoas acreditam que normalizar um banco de dados prejudica o desempenho? as pessoas também pensam que o princípio DRY prejudica o desempenho do código? De onde vem essa percepção errada?
Steven A. Lowe
1
Possivelmente porque a desnormalização pode melhorar o desempenho, portanto a normalização o degrada, relativamente falando. Não posso dizer que concordo com isso, mas provavelmente foi assim que aconteceu.
Matthew Scharley 10/10/08
2
No início, a normalização pode ter um pequeno efeito no desempenho, mas, com o tempo, à medida que o número de linhas aumenta, JOINs eficientes começarão a superar as tabelas em massa. Obviamente, a normalização tem outros benefícios maiores - consistência e falta de redundância etc.
Rob
1

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!

Steven A. Lowe
fonte
Essa discussão acabou sendo sobre adicionar algumas colunas a todas as tabelas, não sobre modelar herança. Penso que o título dessa discussão deve ser alterado para refletir melhor a natureza da questão e da discussão.
Mesmo Mien
1

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.

Matthew Schinckel
fonte
1

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!

Pierre
fonte