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


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

Considere una aplicación que necesita almacenar detalles de una familia de objetos, que comparten algunos atributos, pero tienen muchos otros no comunes. Por ejemplo, un paquete de seguro comercial puede incluir cobertura de responsabilidad civil, 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 posibles variaciones, 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 cubierta.

Ambas alternativas parecen insatisfactorio, 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?

Author: Eric Lavoie, 2010-08-27

7 answers

@Bill Karwin describe tres modelos de herencia en su libro SQL Antipatterns, al proponer soluciones al antipattern 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 mencionaste, muchos atributos que son específicos de subtipos tendrán que recibir un valor NULL en filas donde estos atributos no se aplican. Con este modelo, tendría una tabla de políticas, que se vería algo como esto:

+------+---------------------+----------+----------------+------------------+
| 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 alterar la tabla para acomodar los atributos que describen estos nuevos objetos. Esto puede convertirse rápidamente en problemático cuando tiene muchos subtipos, o si planea agregar subtipos en de forma 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 NOT NULL en atributos de un subtipo que deberían ser obligatorios. Usted tendría que manejar esto en su aplicación, que en general no es ideal.

Herencia de la Tabla de concreto:

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 aplicar con NOT NULL.

  • Para agregar un nuevo subtipo es necesario 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 en particular, como el campo vehicle_reg_no para una directiva de propiedades.

  • No es necesario el atributo type 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 viene con 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 no lo sabrá bien.

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

  • Buscar 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 sea modificado con un UNION ALL adicional para 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. Se crea una sola tabla para la clase base, que incluye todos los atributos comunes. Luego crearía tablas específicas para cada subtipo, cuya clave primaria también sirve como una clave externa a la mesa de 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 aplicar con NOT NULL.

  • Para agregar un nuevo subtipo es necesario 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 en particular.

  • No es necesario el atributo type.

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

  • Por fin podemos mantenernos secos. No es necesario repetir los atributos comunes para cada tabla de subtipos al crear las tablas.

  • Administrar un incremento automático id para 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 subtipos las genere de forma independiente.

  • Buscar todas las políticas independientemente del subtipo ahora se vuelve muy fácil: No se necesitan UNIONs - solo un SELECT * FROM policies.

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


Los nombres de estos tres modelos provienen de El libro de Martin Fowler Patrones de Arquitectura de Aplicaciones Empresariales.

 350
Author: Daniel Vassallo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:27

La 3a opción es crear una tabla "Policy", luego una tabla "SectionsMain" que almacena todos los campos que son comunes en todos los tipos de secciones. A continuación, cree otras tablas para cada tipo de sección que solo contengan los campos que no sean comunes.

Decidir cuál es el mejor depende principalmente de cuántos campos tiene y cómo desea escribir su SQL. Todos funcionarían. Si usted tiene apenas algunos campos entonces iría probablemente con #1. Con "un montón" de campos me gustaría inclínate hacia #2 o #3.

 12
Author: David,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-26 20:15:48

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

POLÍTICAS

  • POLICY_ID (clave primaria)

PASIVO

  • LIABILITY_ID (clave primaria)
  • POLICY_ID (clave foránea)

PROPIEDADES

  • PROPERTY_ID (clave primaria)
  • POLICY_ID (clave foránea)

...y así sucesivamente, porque yo esperaría que haya diferentes atributos asociados con cada sección de la política. De lo contrario, podría haber una sola tabla SECTIONS 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 de este enfoque: así es como almacena datos mientras mantiene la integridad referencial y no duplica datos. El término es "normalizado"...

Debido a que SQL se basa en conjuntos, es bastante ajeno a los conceptos de programación/OO procedimentales y requiere código para transición de un reino al otro. A menudo se consideranMs, pero no funcionan bien en sistemas complejos y de gran volumen.

 8
Author: OMG Ponies,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-26 20:22:50

La otra forma de hacerlo, es usando el componente INHERITS. 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);

Así es posible definir una herencia entre tablas.

 4
Author: Marco Paulo Ollivier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-14 17:09:13

Echa un vistazo a la respuesta que di aquí

NHibernate fluido mapeo uno a uno con claves sintéticas

 0
Author: Zoidberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:01

Me inclino hacia el método #1 (una tabla de sección unificada), en aras de recuperar eficientemente políticas completas con todas sus secciones (lo que asumo que su sistema estará haciendo mucho).

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

En última instancia, tendrá que decidir qué tan "similares" son las secciones de la política. A menos que difieren sustancialmente, creo que una solución más normalizada podría ser más problemática de lo que vale... pero sólo tú puedes hacer esa llamada. :)

 0
Author: Dan J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-26 20:22:05

Además en la solución de Daniel Vassallo, si usas SQL Server 2016, hay otra solución que utilicé en algunos casos sin pérdidas considerables de rendimiento.

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

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

 0
Author: overcomer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-01 12:17:09