Patrón de diseño: una de las muchas tablas para padres

13

Me encuentro con una situación en la base de datos con bastante frecuencia en la que una tabla determinada puede FK a una de varias tablas principales diferentes. He visto dos soluciones para el problema, pero ninguna es personalmente satisfactoria. Tengo curiosidad por saber qué otros patrones has visto por ahí. Hay una mejor manera de hacerlo?

Un ejemplo controlado
Digamos que mi sistema tiene Alerts. Se pueden recibir alertas para una variedad de objetos: clientes, noticias y productos. Una alerta dada puede ser para un solo artículo. Por alguna razón, los Clientes, los Artículos y los Productos se mueven rápidamente (o se localizan), por lo que el texto / datos necesarios no se pueden incluir en Alertas al crear una Alerta. Dada esta configuración, he visto dos soluciones.

Nota: A continuación, DDL es para SQL Server, pero mi pregunta debería ser aplicable a cualquier DBMS.

Solución 1 - Múltiples teclas anulables

En esta solución, la tabla que enlaza con una de muchas tablas tiene múltiples columnas FK (por razones de brevedad, el siguiente DDL no muestra la creación de FK). LO BUENO : en esta solución es bueno que tenga claves foráneas. La nulidad opcional de los FK hace que sea conveniente y relativamente fácil agregar datos precisos. LA MALA consulta no es excelente porque requiere N NEFT JOINS o N UNION para obtener los datos asociados. En SQL Server, específicamente las uniones izquierdas impiden la creación de una vista indizada.

CREATE TABLE Product (
    ProductID    int identity(1,1) not null,
    CreateUTC    datetime2(7) not null,
     Name        varchar(100) not null
    CONSTRAINT   PK_Product Primary Key CLUSTERED (ProductID)
)
CREATE TABLE Customer (
    CustomerID  int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
     Name       varchar(100) not null
    CONSTRAINT  PK_Customer Primary Key CLUSTERED (CustomerID)
)
CREATE TABLE News (
    NewsID      int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    Name        varchar(100) not null
    CONSTRAINT  PK_News Primary Key CLUSTERED (NewsID)
)

CREATE TABLE Alert (
    AlertID     int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    ProductID   int null,
    NewsID      int null,
    CustomerID  int null,
    CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

ALTER TABLE Alert WITH CHECK ADD CONSTRAINT CK_OnlyOneFKAllowed 
CHECK ( 
    (ProductID is not null AND NewsID is     null and CustomerID is     null) OR 
    (ProductID is     null AND NewsID is not null and CustomerID is     null) OR 
    (ProductID is     null AND NewsID is     null and CustomerID is not null) 
)

Solución 2: un FK en cada tabla principal
En esta solución, cada tabla 'principal' tiene un FK en la tabla de alerta. Facilita la recuperación de alertas asociadas con un padre. En el lado negativo, no hay una cadena real desde la Alerta a quién se hace referencia. Además, el modelo de datos permite alertas huérfanas, donde una alerta no está asociada con un producto, noticia o cliente. Nuevamente, múltiples IZQUIERDAS SE UNEN para descubrir la asociación.

CREATE TABLE Product (
    ProductID    int identity(1,1) not null,
    CreateUTC    datetime2(7) not null,
     Name        varchar(100) not null
    AlertID     int null,
    CONSTRAINT   PK_Product Primary Key CLUSTERED (ProductID)
)
CREATE TABLE Customer (
    CustomerID  int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
     Name       varchar(100) not null
    AlertID     int null,
    CONSTRAINT  PK_Customer Primary Key CLUSTERED (CustomerID)
)
CREATE TABLE News (
    NewsID      int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    Name        varchar(100) not null
    AlertID     int null,
    CONSTRAINT  PK_News Primary Key CLUSTERED (NewsID)
)

CREATE TABLE Alert (
    AlertID     int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

¿Es esto solo vida en una base de datos de relaciones? ¿Hay soluciones alternativas que te hayan resultado más satisfactorias?

EBarr
fuente
1
¿Se puede crear una tabla primaria, Alertable, con las siguientes columnas: ID, CreateDate, Name, Type. Puede tener su mesa FK de tres hijos, y su ALert FK solo en una mesa, Alertable.
AK
Para algunos casos, usted hace un buen punto: efectivamente la solución n. ° 3. Sin embargo, cada vez que los datos se mueven rápidamente o se localizan, no funcionarán. Digamos, por ejemplo, que el Producto, el Cliente y las Noticias tienen una tabla "Lang" correspondiente para admitir Nombre en varios idiomas. Si tengo que entregar 'nombre' en el idioma nativo de los usuarios, no puedo almacenarlo Alertable. Eso tiene sentido?
EBarr
1
@EBarr: "Si tengo que entregar 'nombre' en el idioma nativo de los usuarios, no puedo almacenarlo en Alertable. ¿Tiene sentido?" No lo hace Con su esquema actual, si tiene que entregar 'nombre' en el idioma nativo de los usuarios, ¿puede almacenarlo en la tabla Producto, Cliente o Noticias?
ypercubeᵀᴹ
@ypercube Agregué que cada una de esas tablas tiene una tabla de idioma asociada. Estaba intentando crear un escenario en el que el texto del "nombre" puede variar según la solicitud y, por lo tanto, no se puede almacenar en Alertable.
EBarr
A menos que ya tenga un nombre aceptado, propongo el término "unirse al pulpo" para la consulta que haría para ver todas las alertas y sus padres asociados. :)
Nathan Long

Respuestas:

4

Entiendo que la segunda solución no es aplicable, ya que no ofrece una relación (objeto) a muchos (alerta).

Está atascado con solo dos soluciones debido al estricto cumplimiento de 3NF.

Diseñaría un esquema de acoplamiento menor:

CREATE TABLE Product  (ProductID  int identity(1,1) not null, ...)
CREATE TABLE Customer (CustomerID int identity(1,1) not null, ...)
CREATE TABLE News     (NewsID     int identity(1,1) not null, ...)

CREATE TABLE Alert (
  -- See (1)
  -- AlertID     int identity(1,1) not null,

  AlertClass char(1) not null, -- 'P' - Product, 'C' - Customer, 'N' - News
  ForeignKey int not null,
  CreateUTC  datetime2(7) not null,

  -- See (2)
  CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertClass, ForeignKey)
)

-- (1) you don't need to specify an ID 'just because'. If it's meaningless, just don't.
-- (2) I do believe in composite keys

O, si la relación de integridad es obligatoria, podría diseñar:

CREATE TABLE Product  (ProductID  int identity(1,1) not null, ...)
CREATE TABLE Customer (CustomerID int identity(1,1) not null, ...)
CREATE TABLE News     (NewsID     int identity(1,1) not null, ...)

CREATE TABLE Alert (
  AlertID     int identity(1,1) not null,
  AlertClass char(1) not null, /* 'P' - Product, 'C' - Customer, 'N' - News */
  CreateUTC  datetime2(7) not null,
  CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

CREATE TABLE AlertProduct  (AlertID..., ProductID...,  CONSTRAINT FK_AlertProduct_X_Product(ProductID)    REFERENCES Product)
CREATE TABLE AlertCustomer (AlertID..., CustomerID..., CONSTRAINT FK_AlertCustomer_X_Customer(CustomerID) REFERENCES Customer)
CREATE TABLE AlertNews     (AlertID..., NewsID...,     CONSTRAINT FK_AlertNews_X_News(NewsID)             REFERENCES News)

De todas formas...

Tres soluciones válidas más otra a tener en cuenta para muchas relaciones (objetos) a uno (alerta) ...

Estos presentados, ¿cuál es la moraleja?

Difieren sutilmente y pesan lo mismo en los criterios:

  • rendimiento en inserciones y actualizaciones
  • complejidad en las consultas
  • Espacio de almacenamiento

Por lo tanto, elija el más cómodo para usted.

Marcus Vinicius Pompeu
fuente
1
Gracias por el aporte; Estoy de acuerdo con la mayoría de tus comentarios. Probablemente describí ingeniosamente la relación requerida (excluyendo la solución 2 en sus ojos), ya que era un ejemplo inventado. fn1 : entendido, simplemente estaba simplificando mucho las tablas para centrarme en el problema. fn2 - teclas compuestas y vuelvo! Con respecto al esquema de acoplamiento menor, entiendo la simplicidad, pero personalmente trato de diseñar con DRI siempre que sea posible.
EBarr
Al revisar esto, comencé a dudar de la exactitud de mi solución ... de todos modos, ha sido votada dos veces, lo que agradezco. Aunque creo que mi diseño es válido pero no es adecuado para el problema dado, ya que las uniones / uniones 'n' no se abordan ...
Marcus Vinicius Pompeu el
El acrónimo DRI me atrapó. Para todos ustedes, significa Integridad referencial declarativa, la técnica detrás de la integridad de datos referenciales que se implementa comúnmente como ... (redoble de tambores) ... declaraciones DDL FOREIGN KEY. Más en es.wikipedia.org/wiki/Declarative_Referential_Integrity y msdn.microsoft.com/en-us/library/…
Marcus Vinicius Pompeu
1

He usado tablas de unión mantenidas por disparador. la solución funciona bastante bien como último recurso si no es posible o deseable refactorizar la base de datos. La idea es que tiene una tabla que está ahí solo para manejar los problemas de RI, y todo DRI va en contra de ella.

Chris Travers
fuente