¿Cómo se puede representar la herencia en una base de datos?


236

Estoy pensando en cómo representar una estructura compleja en una base de datos de SQL Server.

Considere una aplicación que necesita almacenar detalles de una familia de objetos, que comparten algunos atributos, pero tienen muchos otros que no son comunes. Por ejemplo, un paquete de seguro comercial puede incluir cobertura de responsabilidad, motor, propiedad e indemnización dentro del mismo registro de póliza.

Es trivial implementar esto en C #, etc., ya que puede crear una Política con una colección de Secciones, donde la Sección se hereda según sea necesario para los diversos tipos de cobertura. Sin embargo, las bases de datos relacionales no parecen permitir esto fácilmente.

Puedo ver que hay dos opciones principales:

  1. Cree una tabla de políticas, luego una tabla de secciones, con todos los campos requeridos, para todas las variaciones posibles, la mayoría de las cuales serían nulas.

  2. Cree una tabla de políticas y numerosas tablas de secciones, una para cada tipo de cobertura.

Ambas alternativas parecen insatisfactorias, especialmente porque es necesario escribir consultas en todas las Secciones, lo que implicaría numerosas uniones o numerosas comprobaciones nulas.

¿Cuál es la mejor práctica para este escenario?


Respuestas:


430

@Bill Karwin describe tres modelos de herencia en su libro SQL Antipatterns , al proponer soluciones al antipatrón SQL Entity-Attribute-Value . Este es un breve resumen:

Herencia de tabla única (también conocida como Herencia de tabla por jerarquía):

Usar una sola tabla como en su primera opción es probablemente el diseño más simple. Como mencionó, muchos atributos que son específicos de subtipo tendrán que recibir un NULLvalor en las filas donde estos atributos no se aplican. Con este modelo, tendría una tabla de políticas, que se vería así:

+------+---------------------+----------+----------------+------------------+
| 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 -----/

Mantener el diseño simple es una ventaja, pero los principales problemas con este enfoque son los siguientes:

  • Cuando se trata de agregar nuevos subtipos, tendría que modificar la tabla para acomodar los atributos que describen estos nuevos objetos. Esto puede volverse problemático rápidamente cuando tiene muchos subtipos, o si planea agregar subtipos de manera regular.

  • La base de datos no podrá imponer qué atributos se aplican y cuáles no, ya que no hay metadatos para definir qué atributos pertenecen a qué subtipos.

  • Tampoco puede aplicar los NOT NULLatributos de un subtipo que deberían ser obligatorios. Tendría que manejar esto en su aplicación, que en general no es ideal.

Herencia de mesa de hormigón:

Otro enfoque para abordar la herencia es crear una nueva tabla para cada subtipo, repitiendo todos los atributos comunes en cada tabla. Por ejemplo:

--// 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    |   
+------+---------------------+------------------+

Este diseño básicamente resolverá los problemas identificados para el método de tabla única:

  • Los atributos obligatorios ahora se pueden hacer cumplir NOT NULL.

  • Agregar un nuevo subtipo requiere agregar una nueva tabla en lugar de agregar columnas a una existente.

  • Tampoco hay riesgo de que se establezca un atributo inapropiado para un subtipo particular, como el vehicle_reg_nocampo para una política de propiedad.

  • No es necesario el typeatributo como en el método de tabla única. El tipo ahora está definido por los metadatos: el nombre de la tabla.

Sin embargo, este modelo también tiene algunas desventajas:

  • Los atributos comunes se mezclan con los atributos específicos del subtipo, y no hay una manera fácil de identificarlos. La base de datos tampoco lo sabrá.

  • Al definir las tablas, tendría que repetir los atributos comunes para cada tabla de subtipo. Eso definitivamente no es SECO .

  • La búsqueda de todas las políticas independientemente del subtipo se vuelve difícil y requeriría un montón de UNIONs.

Así es como tendría que consultar todas las políticas independientemente del 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 cómo agregar nuevos subtipos requeriría que la consulta anterior se modifique con un adicional UNION ALLpara cada subtipo. Esto puede conducir fácilmente a errores en su aplicación si se olvida esta operación.

Herencia de tabla de clase (también conocida como Herencia de tabla por tipo):

Esta es la solución que @David menciona en la otra respuesta . Crea una sola tabla para su clase base, que incluye todos los atributos comunes. Luego crearía tablas específicas para cada subtipo, cuya clave principal también sirve como clave externa para la tabla base. Ejemplo:

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 solución resuelve los problemas identificados en los otros dos diseños:

  • Los atributos obligatorios se pueden hacer cumplir NOT NULL.

  • Agregar un nuevo subtipo requiere agregar una nueva tabla en lugar de agregar columnas a una existente.

  • No hay riesgo de que se establezca un atributo inapropiado para un subtipo particular.

  • No es necesario el typeatributo.

  • Ahora los atributos comunes ya no se mezclan con los atributos específicos de subtipo.

  • Podemos quedarnos SECOS, finalmente. No es necesario repetir los atributos comunes para cada tabla de subtipo al crear las tablas.

  • Administrar un incremento automático idpara las políticas se vuelve más fácil, porque esto puede ser manejado por la tabla base, en lugar de que cada tabla de subtipo las genere de forma independiente.

  • La búsqueda de todas las políticas, independientemente del subtipo, ahora se vuelve muy fácil: no UNIONes necesario, solo a SELECT * FROM policies.

Considero que el enfoque de la tabla de clase es el más adecuado en la mayoría de las situaciones.


Los nombres de estos tres modelos provienen del libro Patterns of Enterprise Application Architecture de Martin Fowler .


97
También estoy usando este diseño, pero no mencionas los inconvenientes. Específicamente: 1) usted dice que no necesita el tipo; verdadero pero no puede identificar el tipo real de una fila a menos que mire todas las tablas de subtipos para encontrar una coincidencia. 2) Es difícil mantener sincronizadas la tabla maestra y las tablas de subtipos (se puede, por ejemplo, eliminar la fila en la tabla de subtipos y no en la tabla maestra). 3) Puede tener más de un subtipo para cada fila maestra. Utilizo disparadores para trabajar alrededor de 1, pero 2 y 3 son problemas muy difíciles. En realidad, 3 no es un problema si modelas la composición, pero es por herencia estricta.

19
+1 para el comentario de @ Tibo, ese es un problema grave. La herencia de la tabla de clase en realidad produce un esquema no normalizado. Donde la herencia de la tabla de concreto no lo hace, y no estoy de acuerdo con el argumento de que la herencia de la tabla de concreto obstaculiza el SECO. SQL dificulta DRY, porque no tiene facilidades de metaprogramación. La solución es utilizar un Kit de herramientas de base de datos (o escribir el suyo propio) para hacer el trabajo pesado, en lugar de escribir SQL directamente (recuerde, en realidad es solo un lenguaje de interfaz DB). Después de todo, tampoco escribe su aplicación empresarial en ensamblador.
Jo So

18
@Tibo, sobre el punto 3, puede utilizar el enfoque explicado aquí: sqlteam.com/article/… , consulte la sección Modelado de restricciones de uno a uno .
Andrew

44
@DanielVassallo En primer lugar, gracias por una respuesta sorprendente, 1 duda si una persona tiene una política ¿Cómo saber si es política_motor o política_propiedad? Una forma es buscar PolicyId en todas las subtablas, pero supongo que esta es la mala manera, ¿cuál debería ser el enfoque correcto?
ThomasBecker

11
Realmente me gusta tu tercera opción. Sin embargo, estoy confundido sobre cómo funcionará SELECT. Si selecciona * DE las políticas, obtendrá los identificadores de las políticas, pero aún no sabrá a qué tabla de subtipos pertenece la política. ¿No tendrá que unirse a todos los subtipos para obtener todos los detalles de la política?
Adam

14

La tercera opción es crear una tabla "Política", luego una tabla "SectionsMain" que almacena todos los campos que son comunes en los tipos de secciones. Luego, cree otras tablas para cada tipo de sección que solo contengan los campos que no son comunes.

Decidir cuál es mejor depende principalmente de cuántos campos tiene y cómo desea escribir su SQL. Todos trabajarían. Si solo tiene unos pocos campos, entonces probablemente iría con el n. ° 1. Con "muchos" campos, me inclinaría hacia el # 2 o # 3.


+1: la tercera opción es la más cercana al modelo de herencia y la OMI más normalizada
RedFilter el

Su opción # 3 es realmente lo que quise decir con la opción # 2. Hay muchos campos y algunas secciones también tendrían entidades secundarias.
Steve Jones el

9

Con la información proporcionada, modelaría la base de datos para que tenga lo siguiente:

Políticas

  • POLICY_ID (clave primaria)

PASIVO

  • LIABILITY_ID (clave principal)
  • POLICY_ID (clave externa)

PROPIEDADES

  • PROPERTY_ID (clave principal)
  • POLICY_ID (clave externa)

... y así sucesivamente, porque esperaría que haya diferentes atributos asociados con cada sección de la política. De lo contrario, podría haber una sola SECTIONSmesa y, además de la policy_id, habría una section_type_code...

De cualquier manera, esto le permitiría admitir secciones opcionales por política ...

No entiendo lo que encuentra insatisfactorio sobre este enfoque: así es como almacena los datos mientras mantiene la integridad referencial y no duplica los datos. El término está "normalizado" ...

Debido a que SQL está basado en SET, es bastante ajeno a los conceptos de programación procedimental / OO y requiere un código para la transición de un reino a otro. Los ORM a menudo se consideran, pero no funcionan bien en sistemas complejos de alto volumen.


Sí, entiendo lo de la normalización ;-) Para una estructura tan compleja, con algunas secciones simples y algunas con su propia subestructura compleja, parece poco probable que un ORM funcione, aunque sería bueno.
Steve Jones el

6

Además, en la solución Daniel Vassallo, si usa SQL Server 2016+, hay otra solución que usé en algunos casos sin pérdida considerable de rendimiento.

Puede crear solo una tabla con solo el campo común y agregar una sola columna con el JSON cadena que contiene todos los campos específicos de subtipo.

He probado este diseño para administrar la herencia y estoy muy contento por la flexibilidad que puedo usar en la aplicación relativa.


1
Esa es una idea interesante. Todavía no he usado JSON en SQL Server, pero lo uso mucho en otros lugares. Gracias por el aviso.
Steve Jones

5

La otra forma de hacerlo es usando el INHERITScomponente. Por ejemplo:

CREATE TABLE person (
    id int ,
    name varchar(20),
    CONSTRAINT pessoa_pkey PRIMARY KEY (id)
);

CREATE TABLE natural_person (
    social_security_number varchar(11),
    CONSTRAINT pessoaf_pkey PRIMARY KEY (id)
) INHERITS (person);


CREATE TABLE juridical_person (
    tin_number varchar(14),
    CONSTRAINT pessoaj_pkey PRIMARY KEY (id)
) INHERITS (person);

Por lo tanto, es posible definir una herencia entre tablas.


¿Soportan otros DB INHERITSademás de PostgreSQL ? MySQL por ejemplo?
giannis christofakis

1
@giannischristofakis: MySQL es solo una base de datos relacional, mientras que Postgres es una base de datos relacional de objetos. Entonces, no MySQL no es compatible con esto. De hecho, creo que Postgres es el único DBMS actual que admite este tipo de herencia.
a_horse_with_no_name

2
@ marco-paulo-ollivier, la pregunta del OP es sobre SQL Server, por lo que no entiendo por qué proporciona una solución que solo funciona con Postgres. Obviamente, no aborda el problema.
mapa

@mapto esta pregunta se ha convertido en una especie de objetivo de engaño "cómo se hace la herencia de estilo OO en una base de datos"; que era originalmente sobre el servidor SQL probablemente ahora sea irrelevante
Caius Jard

0

Me inclino hacia el método n. ° 1 (una tabla de sección unificada), en aras de recuperar de manera eficiente políticas completas con todas sus secciones (que supongo que su sistema hará mucho).

Además, no sé qué versión de SQL Server está usando, pero en 2008+ Columnas dispersas ayudan a optimizar el rendimiento en situaciones donde muchos de los valores en una columna serán NULOS.

En última instancia, tendrá que decidir cuán "similares" son las secciones de política. A menos que difieran sustancialmente, creo que una solución más normalizada podría ser más problemática de lo que vale ... pero solo usted puede hacer esa llamada. :)


Habrá demasiada información para presentar toda la Política de una vez, por lo que nunca sería necesario recuperar el registro completo. Creo que es 2005, aunque he usado el escaso 2008 en otros proyectos.
Steve Jones el

¿De dónde viene el término "tabla de sección unificada"? Google casi no muestra resultados y ya hay suficientes términos confusos aquí.
Stephan-v

-1

Alternativamente, considere usar bases de datos de documentos (como MongoDB) que admiten de forma nativa estructuras de datos enriquecidas y anidamiento.


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.