Как смоделировать базу данных со многими отношениями m: n на таблице

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

У человека есть ряд рабочих мест, рабочие места выполняются рядом лиц. У человека есть несколько домов, дома заняты несколькими лицами. У человека есть несколько ресторанов, которые ему нравятся, в ресторанах есть несколько человек, которым нравится ресторан.

Сначала я сконструировал это следующим образом:

Таблицы: Person, Job, House, Restaurant, Person_Job, Person_House, Person_Restaurant.

Отношения 1 - n: Лицо → Person_Job, Person → Person_House, Person → Person_Restaurant, Job → Person_Job, House → Person_House, Restaurant → Person_Restaurant.

Это довольно быстро приводит к переполненной и сложной модели ER.

Пытаясь упростить это, я смоделировал его следующим образом:

Таблицы: персона, работа, дом, ресторан, персонализированные атрибуты

Отношения 1 - n: Person → Person_Attributes, Job → Person_Attributes, House → Person_Attributes, Restaurant → Person_Attributes

Таблица Person_Attributes должна выглядеть примерно так: PersonId JobId houseId restaurantId

Если существует связь между человеком и работой, я добавлю следующую запись:

P1, J1, NULL, NULL

Если отношения между людьми существуют, я добавлю следующую запись:

P1, NULL, H1, NULL

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

Это просто упрощает модель ER, и пока я создаю индексы для personId + jobId, personId + houseId и personId + restaurantId, я думаю, что не будет большого влияния на производительность.

Мои вопросы: Является ли второй метод правильным способом моделирования этого? Если нет, то почему? Правильно ли я влияю на производительность? Если нет, то почему?

Пример Workbench MySQL, что я имею в виду, можно найти здесь:

http://www.2shared.com/file/3GBnodEZ/example.html

Ответ 1

Ваш дизайн нарушает Четвертая нормальная форма. Вы пытаетесь сохранить несколько "фактов" в одной таблице, и это приводит к аномалиям.

Таблица Person_Attributes должна выглядеть примерно так: personId jobId houseId restaurantId

Итак, если я свяжусь с одной работой, одним домом, но двумя ресторанами, храню ли я следующее?

personId jobId houseId restaurantId
    1234    42      87         5678
    1234    42      87         9876

И если я добавлю третий ресторан, я скопирую другие столбцы?

personId jobId houseId restaurantId
    1234   123      87         5678
    1234   123      87         9876
    1234    42      87        13579 

Готово! Ой, подождите, что там произошло? Я менял работу одновременно с добавлением нового ресторана. Теперь я неправильно связан с двумя заданиями, но нет возможности различать это и правильно быть связанным с двумя заданиями.

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

personId jobId houseId restaurantId
    1234   123      87         5678
    1234   123      87         9876
    1234   123      87        13579 
    1234    42      87         5678
    1234    42      87         9876
    1234    42      87        13579 

Он начинает выглядеть как декартово произведение всех различных значений jobId, houseId и restaurantId. Фактически, это потому, что эта таблица пытается хранить несколько независимых фактов.

Для правильной реляционной конструкции требуется отдельная таблица пересечений для каждого отношения "многие ко многим". К сожалению, вы не нашли ярлыка.

(Многие статьи о нормализации говорят, что высшие нормальные формы прошлого 3NF являются эзотерическими, и никогда не приходится беспокоиться о 4NF или 5NF. Пусть этот пример опровергнет это утверждение.)


Повторите свой комментарий об использовании NULL: тогда у вас есть проблема, обеспечивающая уникальность, потому что ограничение PRIMARY KEY требует, чтобы все столбцы были NOT NULL.

personId jobId houseId restaurantId
    1234   123      87         5678
    1234  NULL    NULL         9876
    1234  NULL    NULL        13579 

Кроме того, если я добавлю второй дом или второе задание в приведенную выше таблицу, в какую строку я его вставляю? Вы можете в итоге:

personId jobId houseId restaurantId
    1234   123      87         5678
    1234  NULL    NULL         9876
    1234    42    NULL        13579 

Теперь, если я disassociate restaurantId 9876, я мог бы обновить его до NULL. Но это оставляет строку всех NULL, которые я действительно должен просто удалить.

personId jobId houseId restaurantId
    1234   123      87         5678
    1234  NULL    NULL         NULL
    1234    42    NULL        13579 

Если бы у меня был отключенный ресторан 13579, я мог бы обновить его до NULL и оставить строку на месте.

personId jobId houseId restaurantId
    1234   123      87         5678
    1234  NULL    NULL         9876
    1234    42    NULL         NULL 

Но не следует ли мне консолидировать строки, перемещая jobId в другую строку, если там есть вакансия в этом столбце?

personId jobId houseId restaurantId
    1234   123      87         5678
    1234    42    NULL         9876

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

Тем не менее, все различные изменения легки, если вы определяете одну таблицу для отношений "много-ко-многим". Вам нужна сложность наличия еще многих таблиц, но, делая это, вы упростите ваш код приложения.

Добавление ассоциации в ресторан - это просто INSERT в таблице Person_Restaurant. Удаление этой ассоциации - это просто DELETE. Неважно, сколько ассоциаций приходится на работу или дома. И вы можете определить ограничение первичного ключа в каждой из этих таблиц пересечений для обеспечения уникальности.

Ответ 2

Ваша упрощенная версия не представляет собой правильную реляционную модель. Это больше метаданных.

Количество таблиц в вашей базе данных должно представлять количество логических объектов в вашем домене. Это не должно меняться на основе каких-то произвольных представлений о том, сколько объектов слишком много.

Ответ 3

Я не думаю, что второй метод правильный, потому что ваша таблица Person_Attributes будет содержать избыточные данные. Например: говорят, что человек любит 10 ресторанов и работает на 2-х рабочих местах, имеет 3 дома, у вас будет целых 10 * 2 * 3 записи, где должно быть 10 + 2 + 3 (в 3 таблицах ссылок... согласно подходу № 1), Подумайте о недостатках, имеющих миллион пользователей, и если у вас было более 3 атрибутов в таблице Person_Attributes для обработки... поэтому я бы пошел с подходом 1 к вашему вопросу.

Скажем, например, ваша таблица Person_Attributes имеет следующую запись:

personId | houseId | jobId | restaurantId
------------------------------------------
P1      H1  J1  R1

теперь, если человек любит рестораны R2 и R3... таблица выглядит как

P1      H1      J1      R1
P2      H1      J1      R2
P2      H1      J1      R3
В таблице

имеются избыточные данные он добавляет Job J2 в более поздний момент. ваш стол будет выглядеть как

P1      H1      J1      R1
P2      H1      J1      R2
P2      H1      J1      R3
P1      H1      J2      R1
P2      H1      J2      R2
P2      H1      J2      R3

Теперь подумайте, что он добавляет еще один дом H2.. так далее и т.д. Вы видите мою точку зрения?

Ответ 4

По моему скромному мнению, я бы пошел на первую модель. Это, вероятно, более сложная модель, но в конечном итоге это упростит процесс сбора информации из таблиц, а код приложения может стать более грязным или более нечитаемым для других программистов. Кроме того, есть некоторые авторы, которые не рекомендуют использовать такие многоцелевые таблицы.

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

Ответ 5

Вторая модель - проблема с нескольких точек зрения. Сначала это, вероятно, создаст проблемы блокировки, поскольку все идет в одну мета-таблицу. Во-вторых, гораздо реже возникают проблемы с целостностью данных, поскольку вы не можете принуждать ограничения внешнего ключа. Это антипаттерн SQL, чтобы моделировать этот путь. Первая модель была правильной.