¿Cómo modela efectivamente la herencia en una base de datos?

131

¿Cuáles son las mejores prácticas para modelar la herencia en bases de datos?

¿Cuáles son las compensaciones (por ejemplo, consultabilidad)?

(Estoy más interesado en SQL Server y .NET, pero también quiero entender cómo otras plataformas abordan este problema).

Incluso Mien
fuente
14
Si está interesado en la "mejor práctica", la mayoría de las respuestas son simplemente incorrectas. La mejor práctica dicta que el RDb y la aplicación son independientes; Tienen criterios de diseño completamente diferentes. Por lo tanto, "modelar la herencia" en una base de datos (o modelar el RDb para que se adapte a una sola aplicación o lenguaje de aplicación) es una práctica muy mala, desinformada, que rompe las reglas básicas de diseño de RDb y la paraliza.
PerformanceDBA
posible duplicado de algo así como herencia en el diseño de bases de datos
Steve Chambers
66
@PerformanceDBA Entonces, ¿cuál es su sugerencia para evitar la herencia en el modelo DB? Digamos que tenemos 50 tipos diferentes de maestros y que queremos conectar a ese maestro en particular con la clase. ¿Cómo lograrías eso sin tener herencia?
svlada
1
@svlada. Eso es sencillo de implementar en un RDb, por lo que se requiere "herencia". Haga una pregunta, incluya la tabla defns y un ejemplo, y la responderé en detalle. Si lo haces en términos OO, será un desastre real.
PerformanceDBA

Respuestas:

162

Hay varias formas de modelar la herencia en una base de datos. El que elija depende de sus necesidades. Aquí hay algunas opciones:

Tabla por tipo (TPT)

Cada clase tiene su propia mesa. La clase base tiene todos los elementos de la clase base, y cada clase que se deriva de ella tiene su propia tabla, con una clave primaria que también es una clave foránea para la tabla de la clase base; la clase de la tabla derivada contiene solo los diferentes elementos.

Así por ejemplo:

class Person {
    public int ID;
    public string FirstName;
    public string LastName;
}

class Employee : Person {
    public DateTime StartDate;
}

Resultaría en tablas como:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK, FK)
datetime startdate

Tabla por jerarquía (TPH)

Hay una sola tabla que representa toda la jerarquía de herencia, lo que significa que varias de las columnas probablemente serán dispersas. Se agrega una columna discriminadora que le dice al sistema qué tipo de fila es esta.

Dadas las clases anteriores, terminas con esta tabla:

table Person
------------
int id (PK)
int rowtype (0 = "Person", 1 = "Employee")
string firstname
string lastname
datetime startdate

Para cualquier fila que sea de tipo fila 0 (Persona), la fecha de inicio siempre será nula.

Mesa por concreto (TPC)

Cada clase tiene su propia tabla completamente formada sin referencias a ninguna otra tabla.

Dadas las clases anteriores, terminas con estas tablas:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate
Brad Wilson
fuente
23
"Lo que elija depende de sus necesidades", por favor explique, ya que creo que los motivos de las elecciones constituyen el núcleo de la pregunta.
Alex
12
Ver mi comentario sobre la pregunta. El uso de nuevos nombres divertidos para los términos técnicos de Rdb que han existido conduce a la confusión. "TPT" es supertipo-subtipo. "TPH" no está normalizado, un gran error. "TPH" es aún menos Normalizado, otro error grave.
PerformanceDBA
45
Solo un DBA presumiría que la desnormalización es siempre un error. :)
Brad Wilson
77
Si bien voy a admitir que la desnormalización resulta en ganancias de rendimiento en algunos casos, esto se debe completamente a una separación incompleta (o inexistente) entre la estructura lógica y física de los datos en el DBMS. Desafortunadamente, la mayoría de los DBMS comerciales sufren de este problema. @PerformanceDBA es correcto. La subnormalización es un error de juicio, sacrificando la consistencia de los datos por la velocidad. Lamentablemente, es una elección que un DBA o un desarrollador nunca tendrían que hacer si el DBMS se diseñara correctamente. Para que conste, no soy un DBA.
Kenneth Cochran
66
@Brad Wilson. Solo un desarrollador desnormalizaría, "por rendimiento", o de otra manera. A menudo, no es desnormalización, la verdad es que no está normalizada. Que la desnormalización o no normalizada sea un error, es un hecho, respaldado por la teoría y experimentado por millones, no es una "presunción".
PerformanceDBA
133

El diseño adecuado de la base de datos no se parece en nada al diseño de objeto adecuado.

Si planea utilizar la base de datos para algo que no sea simplemente serializar sus objetos (como informes, consultas, uso de múltiples aplicaciones, inteligencia empresarial, etc.), entonces no recomiendo ningún tipo de mapeo simple de objetos a tablas.

Muchas personas piensan que una fila en una tabla de base de datos es una entidad (pasé muchos años pensando en esos términos), pero una fila no es una entidad. Es una proposición. Una relación de base de datos (es decir, tabla) representa una declaración de hechos sobre el mundo. La presencia de la fila indica que el hecho es verdadero (y, a la inversa, su ausencia indica que el hecho es falso).

Con esta comprensión, puede ver que un solo tipo en un programa orientado a objetos puede almacenarse en una docena de relaciones diferentes. Y una variedad de tipos (unidos por herencia, asociación, agregación o completamente no afiliados) pueden almacenarse parcialmente en una sola relación.

Es mejor preguntarse, qué hechos desea almacenar, qué preguntas va a querer respuestas, qué informes desea generar.

Una vez que se crea el diseño adecuado de la base de datos, es simple crear consultas / vistas que le permitan serializar sus objetos a esas relaciones.

Ejemplo:

En un sistema de reserva de hotel, es posible que deba almacenar el hecho de que Jane Doe tiene una reserva para una habitación en el Seaview Inn del 10 al 12 de abril. ¿Es ese un atributo de la entidad del cliente? ¿Es un atributo de la entidad hotelera? ¿Es una entidad de reserva con propiedades que incluyen clientes y hoteles? Podría ser cualquiera o todas esas cosas en un sistema orientado a objetos. En una base de datos, no es ninguna de esas cosas. Es simplemente un hecho desnudo.

Para ver la diferencia, considere las siguientes dos consultas. (1) ¿Cuántas reservas de hotel tiene Jane Doe para el próximo año? (2) ¿Cuántas habitaciones están reservadas para el 10 de abril en el Seaview Inn?

En un sistema orientado a objetos, la consulta (1) es un atributo de la entidad del cliente, y la consulta (2) es un atributo de la entidad del hotel. Esos son los objetos que expondrían esas propiedades en sus API. (Sin embargo, obviamente, los mecanismos internos por los cuales se obtienen esos valores pueden involucrar referencias a otros objetos).

En un sistema de base de datos relacional, ambas consultas examinarían la relación de reserva para obtener sus números, y conceptualmente no hay necesidad de molestarse con ninguna otra "entidad".

Por lo tanto, al intentar almacenar hechos sobre el mundo, en lugar de intentar almacenar entidades con atributos, se construye una base de datos relacional adecuada. Y una vez que se diseña correctamente, las consultas útiles que no se imaginaron durante la fase de diseño se pueden construir fácilmente, ya que todos los hechos necesarios para cumplir con esas consultas se encuentran en sus lugares adecuados.

Jeffrey L Whitledge
fuente
12
+1 Finalmente, una isla de conocimiento genuino en un mar de ignorancia (y negativa a aprender algo fuera de su ámbito). De acuerdo, no es mágico: si el RDb está diseñado utilizando los principios de RDb, es fácil "mapear" o "proyectar" cualquier "clase". Forzar el RDb a requisitos basados ​​en clases es simplemente incorrecto.
PerformanceDBA
2
Interesante respuesta. ¿Cómo sugeriría modelar el ejemplo Persona-Empleado en la respuesta aceptada?
sevenforce
2
@ sevenforce: el diseño de la base de datos realmente depende de los requisitos del sistema, que no se dan. No hay suficiente información para decidir. En muchos casos, algo similar al diseño de "tabla por tipo" puede ser apropiado, si no se sigue servilmente. Por ejemplo, la fecha de inicio es probablemente una buena propiedad para un objeto Empleado, pero en la base de datos realmente debería ser un campo en la tabla Empleo, ya que una persona podría ser contratada varias veces con múltiples fechas de inicio. Esto no importa para los objetos (que usaría el más reciente), pero es importante en la base de datos.
Jeffrey L Whitledge
2
Claro, mi pregunta era principalmente sobre la forma de modelar la herencia. Perdón por no haber sido lo suficientemente claro. Gracias. Como mencionó, lo más probable es que haya una Employmenttabla que recopile todos los empleos con sus fechas de inicio. Entonces, si conocer la fecha de inicio de empleo actual de un Employeres importante, ese podría ser un caso de uso adecuado para un View, que incluye esa propiedad mediante consultas. (nota: parece que debido al '-' justo después de mi nick, no recibí ninguna notificación sobre tu comentario)
sevenforce
55
Esta es una verdadera joya de respuesta. Necesitará algo de tiempo para sumergirme y requerir un poco de ejercicio para hacerlo bien, pero ya ha influido en mi proceso de pensamiento sobre el diseño de bases de datos relacionales.
MarioDS
9

Respuesta corta: no lo haces.

Si necesita serializar sus objetos, use un ORM, o incluso mejor, algo como registro activo o prevalencia.

Si necesita almacenar datos, almacénelos de manera relacional (teniendo cuidado con lo que está almacenando y prestando atención a lo que Jeffrey L Whitledge acaba de decir), no afectado por el diseño de su objeto.

Marcin
fuente
3
+1 Intentar modelar la herencia en una base de datos es un desperdicio de buenos recursos relacionales.
Daniel Spiewak
7

Los patrones de TPT, TPH y TPC son las formas en que va, como lo menciona Brad Wilson. Pero un par de notas:

  • Las clases secundarias que heredan de una clase base se pueden ver como entidades débiles para la definición de clase base en la base de datos, lo que significa que dependen de su clase base y no pueden existir sin ella. He visto varias veces que las ID únicas se almacenan para todas y cada una de las tablas secundarias, al tiempo que se mantiene el FK en la tabla principal. Un FK es suficiente y es aún mejor tener habilitada la cascada de borrado para la relación FK entre el niño y las tablas base.

  • En TPT, al ver solo los registros de la tabla base, no puede encontrar qué clase secundaria representa el registro. Esto a veces es necesario, cuando desea cargar una lista de todos los registros (sin hacerlo select en todas y cada una de las tablas secundarias). Una forma de manejar esto es tener una columna que represente el tipo de la clase secundaria (similar al campo rowType en el TPH), mezclando el TPT y el TPH de alguna manera.

Digamos que queremos diseñar una base de datos que contenga el siguiente diagrama de clase de forma:

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;
}

El diseño de la base de datos para las clases anteriores puede ser así:

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
fuente
4

Hay dos tipos principales de herencia que puede configurar en una base de datos, tabla por entidad y tabla por jerarquía.

Tabla por entidad es donde tiene una tabla de entidad base que tiene propiedades compartidas de todas las clases secundarias. Luego tiene por clase secundaria otra tabla, cada una con solo propiedades aplicables a esa clase. Están vinculados 1: 1 por sus PK

texto alternativo

Tabla por jerarquía es donde todas las clases comparten una tabla, y las propiedades opcionales son anulables. También es un campo discriminador que es un número que denota el tipo que el registro posee actualmente

texto alternativo SessionTypeID es discriminador

El objetivo por jerarquía es más rápido de consultar, ya que no necesita uniones (solo el valor discriminador), mientras que el objetivo por entidad necesita hacer uniones complejas para detectar qué tipo es algo y recuperar todos sus datos.

Editar: Las imágenes que muestro aquí son capturas de pantalla de un proyecto en el que estoy trabajando. La imagen del activo no está completa, por lo tanto, está vacía, pero fue principalmente para mostrar cómo está configurada, no qué poner dentro de las tablas. Eso depende de ti ;). La tabla de sesión contiene información de sesión de colaboración virtual, y puede ser de varios tipos de sesiones, según el tipo de colaboración involucrada.

mate
fuente
También consideraría Target por clase concreta para no modelar bien la herencia y, por lo tanto, no lo mostré.
Mattlant
¿Podría agregar una referencia de donde proviene la ilustración?
chryss
¿Dónde están las imágenes de las que estás hablando al final de tu respuesta?
Musa Haidari
1

Normalizaría su base de datos y eso realmente reflejaría su herencia. Puede tener una degradación del rendimiento, pero así es con la normalización. Probablemente tendrá que usar el buen sentido común para encontrar el equilibrio.

Per Hornshøj-Schierbeck
fuente
2
¿Por qué la gente cree que la normalización de una base de datos degrada el rendimiento? ¿La gente también piensa que el principio DRY degrada el rendimiento del código? ¿De dónde viene esta percepción errónea?
Steven A. Lowe
1
Posiblemente porque la desnormalización puede mejorar el rendimiento, por lo tanto, la normalización lo degrada, en términos relativos. No puedo decir que esté de acuerdo con eso, pero probablemente así fue como sucedió.
Matthew Scharley
2
Al principio, la normalización podría tener un pequeño efecto en el rendimiento, pero con el tiempo, a medida que aumenta el número de filas, las uniones eficientes comenzarán a superar a las tablas más voluminosas. Por supuesto, la normalización tiene otros beneficios mayores: consistencia y falta de redundancia, etc.
Rob
1

repetición de respuesta de hilo similar

en la asignación OR, la herencia se asigna a una tabla primaria donde las tablas principal y secundaria usan el mismo identificador

por ejemplo

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 tiene una relación de clave externa con Object. cuando crea una fila de SubObjeto, primero debe crear una fila de Objeto y usar el Id en ambas filas

EDITAR: si también está buscando modelar el comportamiento, necesitaría una tabla Tipo que enumerara las relaciones de herencia entre tablas y especificara el ensamblado y el nombre de clase que implementaba el comportamiento de cada tabla

parece excesivo, pero todo depende de para qué lo quieras usar.

Steven A. Lowe
fuente
Esa discusión terminó siendo sobre agregar un par de columnas a cada tabla, no sobre modelar la herencia. Creo que el título de esa discusión debería cambiarse para reflejar mejor la naturaleza de la pregunta y la discusión.
Incluso Mien
1

Usando SQL ALchemy (Python ORM), puede hacer dos tipos de herencia.

El que he tenido experiencia es usar una tabla singe y tener una columna discriminante. Por ejemplo, una base de datos de ovejas (¡no es broma!) Almacenó todas las ovejas en una tabla, y Rams y Ewes se manejaron usando una columna de género en esa tabla.

Por lo tanto, puede consultar todas las ovejas y obtener todas las ovejas. O puede consultar solo por Ram, y solo obtendrá Rams. También puede hacer cosas como tener una relación que solo puede ser un carnero (es decir, el padre de una oveja), y así sucesivamente.

Matthew Schinckel
fuente
1

Tenga en cuenta que algunos motores de bases de datos ya proporcionan mecanismos de herencia de forma nativa como Postgres . Mira la documentación .

Por ejemplo, consultaría el sistema Persona / Empleado descrito en una respuesta anterior como esta:

  / * Esto muestra el nombre de todas las personas o empleados * /
  SELECCIONAR nombre de persona; 

  / * Esto muestra la fecha de inicio de todos los empleados solamente * /
  SELECCIONE fecha de inicio de empleado;

En esa es la elección de su base de datos, ¡no necesita ser particularmente inteligente!

Pierre
fuente