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

Resuelto Steve Jones asked hace 14 años • 7 respuestas

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 muchos otros 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 distintos 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. Crea una tabla de Pólizas 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 de nulos.

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

Steve Jones avatar Aug 27 '10 03:08 Steve Jones
Aceptado

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

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

Usar una sola mesa como en su primera opción es probablemente el diseño más simple. Como mencionó, a muchos atributos que son específicos de subtipo se les deberá asignar 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 con regularidad.

  • 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 NULLatributos de un subtipo que deberían ser obligatorios. Tendrías que manejar esto en tu aplicación, lo cual en general no es ideal.

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

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

  • Tampoco existe riesgo de que se establezca un atributo inadecuado para un subtipo particular, como el vehicle_reg_nocampo de 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 presenta algunas desventajas:

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

  • Al definir las tablas, habría que repetir los atributos comunes para cada tabla de subtipo. Definitivamente eso no es SECO .

  • Buscar todas las políticas, independientemente del subtipo, se vuelve difícil y requeriría un montón de UNIONanuncios.

Así tendrías que consultar todas las políticas sin importar el 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;

Tenga en cuenta que agregar nuevos subtipos requeriría que la consulta anterior se modifique con una consulta adicional UNION ALLpara cada subtipo. Esto puede provocar fácilmente errores en su aplicación si se olvida esta operación.

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

Esta es la solución que @David menciona en la otra respuesta . Crea una tabla única para su clase base, que incluye todos los atributos comunes. Luego crearías tablas específicas para cada subtipo, cuya clave primaria 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 aplicar con 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 inadecuado para un subtipo en particular.

  • No es necesario el typeatributo.

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

  • Podemos permanecer SECO, por fin. 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.

  • Buscar todas las políticas independientemente del subtipo ahora se vuelve muy fácil: no UNIONes necesario, solo un archivo SELECT * FROM policies.

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


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

Daniel Vassallo avatar Aug 26 '2010 20:08 Daniel Vassallo

La tercera opción es crear una tabla "Política" y luego una tabla "Secciones principales" que almacene todos los campos que son comunes entre los tipos de secciones. Luego crea 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 tenga y de cómo desee escribir su SQL. Todos funcionarían. Si tiene solo unos pocos campos, probablemente elegiría el número 1. Con "muchos" campos, me inclinaría por el n.º 2 o el n.º 3.

David avatar Aug 26 '2010 20:08 David

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

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

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

overcomer avatar Sep 01 '2017 12:09 overcomer

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

POLÍTICAS

  • POLICY_ID (clave principal)

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 hubiera diferentes atributos asociados con cada sección de la política. De lo contrario, podría haber una sola SECTIONStabla y además de policy_id, habría una section_type_code...

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

No entiendo qué es lo que le parece insatisfactorio de este enfoque: así es como se almacenan los datos manteniendo la integridad referencial y sin duplicarlos. 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 código para realizar la transición de un ámbito al otro. A menudo se consideran los ORM, pero no funcionan bien en sistemas complejos y de gran volumen.

OMG Ponies avatar Aug 26 '2010 20:08 OMG Ponies

La otra forma de hacerlo es utilizando 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 tanto, es posible definir una herencia entre tablas.

Marco Paulo Ollivier avatar Dec 04 '2013 16:12 Marco Paulo Ollivier