Шаблон проектирования - одна из многих родительских таблиц

13

Я часто сталкиваюсь с ситуацией в базе данных, когда заданная таблица может быть привязана к одной из нескольких родительских таблиц. Я видел два решения этой проблемы, но ни одно не удовлетворяет лично. Мне любопытно, какие другие модели вы видели там? Есть ли лучший способ сделать это?

Придуманный пример
Допустим, моя система имеет Alerts. Оповещения могут быть получены для различных объектов - клиентов, новостей и продуктов. Данное предупреждение может быть только для одного элемента. По какой-либо причине Клиенты, Статьи и Продукты быстро перемещаются (или локализуются), поэтому необходимый текст / данные не могут быть добавлены в Оповещения при создании Оповещения. Учитывая эту настройку, я видел два решения.

Примечание: ниже DDL для SQL Server, но мой вопрос должен быть применим к любой СУБД.

Решение 1 - Многочисленные клавиши с возможностью обнуления

В этом решении таблица, которая ссылается на одну из множества таблиц, имеет несколько столбцов FK (для краткости ниже DDL не показывает создание FK). ХОРОШО - В этом решении приятно, что у меня есть внешние ключи. Нулевая возможность FK делает это удобным и относительно простым для добавления точных данных. ПЛОХИЕ Запросы не велики, потому что для получения соответствующих данных требуются операторы N LEFT JOINS или N UNION. В SQL Server, в частности, LEFT JOINS исключают создание индексированного представления.

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) 
)

Решение 2. Один FK в каждой родительской таблице.
В этом решении каждая «родительская» таблица имеет FK для таблицы предупреждений. Это позволяет легко получать оповещения, связанные с родителем. С другой стороны, нет никакой реальной цепочки от оповещения о том, кто ссылается. Кроме того, модель данных допускает сиротские оповещения, когда оповещение не связано с продуктом, новостями или клиентом. Опять же, несколько левых соединений, чтобы выяснить связь.

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)
)

Это просто жизнь в базе данных отношений? Есть ли альтернативные решения, которые вы нашли более удовлетворяющими?

EBarr
источник
1
Можно ли создать одну родительскую таблицу Alertable со следующими столбцами: ID, CreateDate, Name, Type. Вы можете иметь свой FK для трех дочерних таблиц, а ваш ALert будет FK только для одной таблицы, Alertable.
AK
В некоторых случаях вы делаете хорошее замечание - эффективное решение № 3. Однако, когда данные быстро перемещаются или локализуются, они не будут работать. Скажем, например, Product, Customer и News имеют соответствующие таблицы «Lang» для поддержки Name на нескольких языках. Если мне нужно передать имя на родном языке пользователей, я не смогу его сохранить Alertable. Что имеет смысл?
EBarr
1
@EBarr: «Если мне нужно передать имя на родном языке пользователя, я не смогу сохранить его в Alertable. В чем смысл?» Нет, это не так. С вашей текущей схемой, если вам нужно указать имя на родном языке пользователя, можете ли вы сохранить его в таблице Product, Customer или News?
ypercubeᵀᴹ
@ypercube Я добавил, что с каждой из этих таблиц связана языковая таблица. Я пытался создать сценарий, в котором текст «имени» может варьироваться в зависимости от запроса и поэтому не может быть сохранен в Alertable.
EBarr
Если у него уже нет общепринятого имени, я предлагаю термин «соединение осьминога» для запроса, который нужно выполнить, чтобы просмотреть все оповещения и связанных с ними родителей. :)
Натан Лонг

Ответы:

4

Я понимаю, что второе решение не применимо, так как оно не предлагает отношения один (объект) ко многим (предупреждение).

Вы придерживаетесь только двух решений из-за строгого соответствия 3NF.

Я бы разработал меньшую схему связи:

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

Или, если отношения целостности должны быть обязательными, я мог бы разработать:

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)

Тем не мение...

Три допустимых решения плюс другое, которое нужно учитывать для многих (объектов)-к-одному (оповещение) отношений ...

Эти представлены, какова мораль?

Они различаются тонко, а вес одинаков по критериям:

  • производительность по вставкам и обновлениям
  • сложность запроса
  • место для хранения

Итак, выберите этот удобный для вас.

Маркус Виниций Помпеу
источник
1
Спасибо за вклад; Я согласен с большинством ваших комментариев. Я, вероятно, искусно описал требуемые отношения (исключая решение 2 в ваших глазах), так как это был выдуманный пример. fn1 - понял, я просто упростил таблицы, чтобы сосредоточиться на проблеме. fn2 - составные ключи и я возвращаюсь! В отношении меньшей схемы сопряжения я понимаю простоту, но лично стараюсь по возможности проектировать с помощью DRI.
EBarr
Рассматривая это, я начал сомневаться в правильности своего решения ... во всяком случае, за него проголосовали дважды, и я благодарен. Хотя я полагаю, что мой дизайн действителен, но не подходит для поставленной задачи, так как 'n' союзы / объединения не рассматриваются ...
Маркус Виниций Помпей
Аббревиатура DRI получила меня. Для всех вас это означает декларативную ссылочную целостность, метод, лежащий в основе ссылочной целостности данных, который обычно реализуется как ... (барабанная дробь) ... DDL FOREIGN KEY. Подробнее на en.wikipedia.org/wiki/Declarative_Referential_Integrity and msdn.microsoft.com/en-us/library/…
Маркус Виниций Помпеу
1

Я использовал таблицы соединений, поддерживаемые триггером. решение работает в качестве крайней меры, если рефакторинг БД невозможен или нежелателен. Идея состоит в том, что у вас есть таблица, которая предназначена только для решения проблем RI, и все DRI идут против этого.

Крис Траверс
источник