Как идентифицировать предпочтительного дочернего элемента в родительской записи, сохраняя ссылочную целостность?

Я определяю объект, который представляет страницу, состоящую из одного или нескольких элементов Part. На каждой странице должна быть определена хотя бы одна часть. Каждая Часть принадлежит только одной Странице. Один из элементов части будет «главной» частью на странице, а все остальные будут полагаться на главную часть. Помимо этой разницы, главная и ведомая части ведут себя одинаково.

Я рассматриваю несколько разных способов выразить это в SQL. Я могу придумать пару вариантов, каждый со своими преимуществами и недостатками. Я уверен, что нет единого «правильного ответа», но я ищу отзывы о том, какие подходы имеют скрытые сложности, которые позже укусят меня за задницу. Ограничения и триггеры CASCADE желательны, чтобы другие стороны могли безопасно изменять таблицы, когда это необходимо, но я хотел бы избежать конструкции, требующей запутанной и сложной логики.

Во время настройки пользователю нужна возможность выбрать мастер, не позволяя помечать 0 или несколько частей как первичные. Во время выполнения каждая часть должна определить, является ли она мастером или должна полагаться на мастер для получения информации.

Моя целевая платформа — Microsoft SQL Server 2008 или выше. В приведенных ниже примерах используются ключи int просто потому, что это делает примеры меньше — я бы предпочел использовать ключи GUID при реализации.

Вариант 1 – пометить основную часть

create table [Page] (
  PageKey int identity primary key,
  PageName varchar(30) not NULL
)
go
create table [Part] (
  PartKey int identity primary key,
  PartName varchar(30) not NULL,
  PageKey int not null,
  constraint fkPage foreign key (PageKey) references [Page](PageKey),
  IsMasterPart bit not NULL default 0
)

Преимущества: простота понимания данных; аналогичный подход используется в базовом приложении, которое я улучшаю; запись детали знает, что она является мастером, без поиска данных в записи страницы. Недостатки: принудительное использование одного и только одного мастера потребует, чтобы триггеры отклоняли обновления, нарушающие правило, или код прикладного уровня, или и то, и другое.

Вариант 2 — внешний ключ на странице

create table [Page] (
  PageKey int identity primary key,
  PageName varchar(30) not NULL,
  MasterPartKey int not NULL
)
go
create table [Part] (
  PartKey int identity primary key,
  PartName varchar(30) not NULL,
  PageKey int not null,
  constraint fkPage foreign key (PageKey) references [Page](PageKey),
  IsMasterPart bit not NULL default 0
)
go
alter table [Page] 
  add constraint fkMasterPart 
  foreign key (MasterPartKey) references [Part](PartKey)

Преимущества: DRI защищает от удаления мастер-детали; может иметь только одну основную деталь; не может иметь Страницу без мастер-части. Недостатки: потенциальная проблема курицы и яйца при назначении значений PK/FK.

  • Каков шаблон (или есть ли он) для вставки родителя и первого обязательного дочернего элемента в одной операции?
  • Каков шаблон (или есть ли он) для удаления последнего дочернего и родительского элементов, который по-прежнему предотвратит удаление страницы и основной части, когда существуют другие части?

Я подозреваю, что упустил одну или две детали, необходимые для хорошего ответа. Пожалуйста, спросите, и я обновлю.


person Bruce    schedule 04.10.2017    source источник
comment
именно такие комментарии отвлекают меня, прежде чем я доберусь до сути вопроса › Я бы склонялся к ключам GUID при реализации. Теперь я хочу отложить ваш вопрос и поговорить об этом комментарии. Зачем вам использовать GUID, а не целые числа? Постоянно увеличивающийся ключ кластеризации – Дебаты о кластеризованном индексе……….снова! - Кимберли Трипп   -  person SqlZim    schedule 04.10.2017
comment
Наверное, этим комментарием я замутил воду. Это было не очень актуально, но я обязательно прочитаю ваши ссылки, прежде чем двигаться дальше. Спасибо!   -  person Bruce    schedule 04.10.2017
comment
Я прочитал вашу статью, и я ценю комментарии о разделении страниц (правда, потому что я тоже проповедую это). В моем сценарии самая большая таблица будет содержать только сотни строк, возможно, тысячи, если я настроен оптимистично. более важно иметь назначенные значения ключей, которые можно вслепую вставить в другую базу данных (поддерживая перекрестные ссылки), чем иметь постоянно увеличивающийся ключ. Данные настройки, а не данные транзакции. Но спасибо за откат. Вот для чего я пришел сюда.   -  person Bruce    schedule 09.10.2017


Ответы (1)


Я бы выбрал вариант 1. Ваш недостаток варианта 1 неверен: правило «один и только один мастер» может быть применено с помощью ПРОВЕРОЧНОГО ОГРАНИЧЕНИЯ, которое вызывает UDF. И это то, что я бы использовал.

person Tab Alleman    schedule 04.10.2017
comment
А если они захотят сделать мастером другую часть? Есть ли хороший способ выполнить это атомарно? Возможно, установка флага на новой части очищает старый мастер, или мне следует написать обновление... set IsMasterPart = (1 - IsMasterPart) где PartKey in (@old, @new) ? - person Bruce; 04.10.2017
comment
Это можно было бы сделать атомарно (я думаю) с тщательно продуманным ОБНОВЛЕНИЕМ: SET IsMasterPart=CASE WHEN somecondtion THEN 1 ELSE 0 END... - person Tab Alleman; 04.10.2017