2015-01-16 3 views
3

Предположим, у меня есть две таблицы в отношениях «один ко многим». Мы назовем первого Брос, а второго - «Прометей». Брат может иметь несколько поколений, но только один из них может быть его «главным человеком».Дизайн базы данных - «специальная» запись от одного до многих отношений

(Посмотрите, примеры трудно Не кричи на меня.).

Как я представляю, что? Я могу поместить запись «main_man» в таблицу bros, но это будет дублировать записи, которые у меня были бы в таблице homies.

Я мог бы также ввести запись в стол, но это не ограничило бы других родственников главным человеком.

Есть ли правильный способ сделать это? Было бы проще просто сделать это неправильно и обработать его с помощью приложения?

ответ

3

Существует несколько вариантов моделирования этого.

Во-первых, делает main_man также должно быть homie? Если это так, я бы добавил флаг в таблицу homies. Типы данных MySQL немного несовершенны, но я бы использовал логическое значение, которое мы всегда сопоставляем с типом данных TINYINT(1) DEFAULT NULL COMMENT 'boolean'.

Следующий шаг - ограничить значения этого значения либо 1, либо NULL, не допускайте никаких других значений. К сожалению, MySQL не применяет ограничения CHECK, поэтому, если мы хотим, чтобы база данных обеспечивала соблюдение этого правила, нам нужно было бы реализовать триггеры BEFORE INSERT/BEFORE UPDATE для его принудительного применения.

Наконец, мы бы добавить UNIQUE Constraint

... ON homies (bro_id, main_man) 

При том, что MySQL будет только разрешить один ряд с main_man значением 1 для каждого bro_id.

Это небольшое отклонение от нормативного шаблона NULL, что означает «неизвестно», которое, как я вижу, поддерживается документацией Microsoft. В нашей реализации мы используем значение NULL для обозначения «no, not main_man». Основным преимуществом значений NULL является то, что SQL (в общем) и MySQL, в частности, не считают значение NULL «дублирующимся» для другого значения NULL. Ограничение UNIQUE позволяет использовать несколько строк со значением NULL. (Я думаю, что есть какая-то установка sql_mode, которая изменяет это поведение, но мы никогда не туда.)

Чтобы получить только homies, которые являются main_man ...

WHERE main_man = 1 

или, более кратко, так как мы не используем ноль для представления TRUE, и если мы уверены, что никакие другие ненулевые значения не могут присутствовать ...

WHERE main_man 

другая логика довольно проста, проверьте main_man IS NULL или MAIN_MAN <=> NULL, ORDER BY main_man, ..., и верните столбец main_man в SELECT, если вы хотите отсортировать его на клиенте.

Вместо этого вы можете использовать тип данных MySQL ENUM, если мы допустим значения NULL, и мы проверяем, что MySQL разрешит и обеспечит принудительное ограничение UNIQUE в столбце ENUM. (Я никогда не пробовал это раньше).

Это всего лишь один подход из нескольких, но это тот, который я успешно использовал в прошлом.

-

ДЕМОНСТРАЦИЯ

CREATE TABLE bro 
(id INT UNSIGNED NOT NULL PRIMARY KEY 
) ENGINE=INNODB; 

CREATE TABLE homie 
(id   INT UNSIGNED NOT NULL PRIMARY KEY 
, bro_id  INT UNSIGNED NOT NULL COMMENT 'FK ref bros.id' 
, main_man TINYINT(1) DEFAULT NULL COMMENT 'boolean, 1=is the main man' 
, homie_name VARCHAR(10) 
) ENGINE=INNODB; 

ALTER TABLE homie 
    ADD UNIQUE INDEX homie_UX1 (bro_id, main_man); 

ALTER TABLE homie 
    ADD CONSTRAINT FK_homie_bro FOREIGN KEY (bro_id) REFERENCES bro (id); 

TODO: добавить ДО INSERT/ДО триггер UPDATE, чтобы ограничить значение для main_man столбца.

Проверьте это, добавив несколько строк и убедитесь, что у нас не может быть более одного main_man для данного bro_id.

INSERT INTO bro (id) VALUES 
(2),(3); 

INSERT INTO homie (id, bro_id, main_man, homie_name) VALUES 
    (11, 2, NULL, 'mr.slate') 
, (12, 2, 1, 'barney') 
; 

-- attempt to insert another main_man   
INSERT INTO homie (id, bro_id, main_man, homie_name) VALUES 
    (13, 2, 1, 'wilma') 
; 

-- Error Code: 1062 
-- Duplicate entry '2-1' for key 'homie_UX1' 

UPDATE homie SET main_man = 1 WHERE id = 11 ; 

-- Error Code: 1062 
-- Duplicate entry '2-1' for key 'homie_UX1' 

ПРИМЕЧАНИЕ: Я забыл упомянуть, как небольшой бонус, то homie_UX1 индекс (созданный для обеспечения соблюдения UNIQUE ограничения) также служит для поддержки внешнего ключа, поскольку bro_id является ведущим колонки. Вот почему мы добавили индекс до того, как добавили ограничение внешнего ключа.

2

То, что следует довольно стандартным способом моделирования отношение один ко многим, где одна из дочерних строк считается «специальный»:

enter image description here

Эта модель обладает следующими важными свойствами:

  • В дополнение к нормальному FK от дочернего к родительскому, мы также используем «обратный» FK от родителя к дочернему.
  • Мы используем , идентифицируя связь, делая ребенка слабым субъектом (то есть дочерний ключ содержит ключ, перенесенный из родителя). Брат идентифицируется homie, он принадлежит и своим «числом» (BRO_NO) в пределах этого конкретного homie. У другого брата в другом homie может быть тот же BRO_NO.

Взятый вместе, эти два свойства обеспечивают, чтобы:

  • В большинстве одного ребенка является особенным на каждый родитель.

Однако в параллельной среде, вы должны быть осторожны, как вы создаете BRO_NO. Некоторые возможности:

  • Сделайте его автоматическим приращением и живите с помощью «отверстий» в значениях.
  • Заблокировать родителя, затем использовать MAX + 1.
  • Просто используйте MAX + 1 без блокировки, но будьте готовы к устранению нарушения ключа и повторите попытку INSERT, если попытка одновременной транзакции вставлять то же значение.

Если есть другие таблицы, ссылки на BRO, вы можете подумать о добавлении суррогатного ключа (например, BRO_ID). За и против суррогатных ключей см. here.

BTW, есть вариация вышеупомянутой модели: потерять обратный FK и просто рассмотреть, какой из них имеет самый маленький BRO_NO как специальный. Это нормально, если вы заранее знаете специальный бросок или если не возражаете обновить ключ (и, возможно, каскадировать изменение), чтобы переместить брови наверх.


Если СУБД поддерживает отложенные ограничения, FK в родителю может быть не-NULL, и обеспечить точно один ребенок особенный (а не только ноль или один). Превращение одного из FK нарушает проблему курица и яйцо при вставке новых данных в присутствии круговых FK. К сожалению, MySQL не поддерживает отложенные ограничения.

Смежные вопросы