Как смоделировать базу данных с множеством отношений m: n на столе - PullRequest
10 голосов
/ 16 августа 2011

Я сейчас настраиваю базу данных, которая имеет большое количество отношений «многие ко многим». Каждое отношение моделировалось через таблицу ссылок. Пример:

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

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

Таблицы: Персона, Работа, Дом, Ресторан, Персона, Работа, Персона, Дом, Персона, Ресторан.

Отношения 1 - n: Person -> 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, Ресторан -> Person_Attributes

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

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

P1, J1, NULL, NULL

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

P1, NULL, H1, NULL

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

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

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

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

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

Ответы [ 5 ]

20 голосов
/ 17 августа 2011

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

Таблица 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 

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

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

Теперь, если я отключу 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 голосов
/ 16 августа 2011

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

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

2 голосов
/ 16 августа 2011

Я не думаю, что второй метод является правильным, потому что ваша таблица 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

таблица уже содержит избыточные данные, он добавляет задание 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 ... и так далее, и так далее ... Вы понимаете мою точку зрения?

1 голос
/ 17 августа 2011

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

1 голос
/ 16 августа 2011

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

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

...