Нужен ли столбец с уникальным идентификатором в таблице «многие ко многим (соединение)»?

22

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

Теперь таблицы Application и Permission просты:

Applications
--------------
PK  ApplicationID
    Name

Permissions
--------------
PK  PermissionID
    Name

Но какой ЛУЧШИЙ способ сделать таблицу соединений? У меня есть эти два варианта:

ApplicationPermissions
-----------------------
PK  ApplicationPermissionID
CU  ApplicationID
CU  PermissionID

ИЛИ

ApplicationPermissions
-----------------------
CPK ApplicationID
CPK PermissionID

PK = Primary Key
CPK = Composite Primary Key
CU = Composite Unique Index

Вы когда-нибудь были обожжены, когда делали это так, как другие? это строго предпочтение? Мне пришло в голову, что многие из «различий» будут абстрагированы моим шаблоном репозитория (например, я бы почти никогда не создавал весь объект разрешений и не добавлял бы его в приложение, но делал бы это по ID или уникальному имени или что-то), но я думаю, я ищу ужасные истории, так или иначе.

solidau
источник

Ответы:

20

Я полагаю, что вы имеете в виду таблицу «соединения», а не таблицу «соединения».

Для соединительной таблицы нет необходимости иметь собственное поле идентификатора. Вам никогда не понадобится присоединяться или фильтровать такой идентификатор. Вы можете присоединиться или отфильтровать только идентификаторы таблиц, которые вы отображаете. Идентификатор в соединительной таблице является пустой тратой дискового пространства.

Так что «лучший» вариант - избегать идентификации. Как правило, у соединительной таблицы будет 2 покрывающих индекса. Каждый покрывающий индекс использует один из сопоставленных идентификаторов в качестве основного поля сортировки.

Но «лучшее» далеко не далеко. Это очень незначительная проблема - иметь избыточное поле идентификатора. У вас не будет никаких страшных историй на небольшом количестве потраченного впустую диска. Идентификатор не будет «красть» кластеризованный индекс, потому что вы все равно не хотите кластеризоваться в сопоставленном комбо.

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

mike30
источник
2
Что ж, вы уже заявили, что добавление идентификатора - это небольшая уступка, которую легко преодолеть потенциальными преимуществами, поэтому мне кажется, что (учитывая, что наличие уникального идентификатора в каждой таблице является более или менее оптимальной практикой в ​​большинстве СУБД и ORM) Вы бы рекомендовали использовать идентификатор как «лучший» или «вариант по умолчанию», а не иметь его.
Роберт Харви
4
«Вам никогда не понадобится присоединяться или запрашивать такой идентификатор», - если вы говорите «никогда» в технологической ситуации, это побуждает вас к тому, чтобы это произошло. Сказав это, бывают случаи, когда вы присоединяетесь к этой таблице присоединения (да, я слышал, что она называется таблицей «соединения», а не таблицей «соединения») еще в четвертой таблице, поскольку присоединенные сущности на самом деле являются бизнес-объект самостоятельно.
Джесси С. Slicer
4
@RobertHarvey. Идентификация - это хорошая практика для организаций. Но соединение - это скорее деталь реализации многих-многих отношений, а не сущность сама по себе. Но, как указывает слайдер Джесси С., бывают случаи, когда перекресток можно было бы рассматривать как бизнес-объект.
mike30
1
"трата дискового пространства." - Я думаю, что некоторые движки (InnoDB?) В любом случае создают (внутренний) первичный ключ, если вы его не создаете сами - так что на самом деле вы можете не получить дисковое пространство, не имея его.
Alex
@Alex. Вы помещаете составной ПК на сопоставленные идентификаторы.
mike30
11

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

Например, если кто-то просит вас сохранить несколько строк разных таблиц (или ссылки на них) в файле или в памяти, например, для целей ведения журнала, это очень удобно, если вы заранее знаете, что вам просто нужно хранить ровно одну имя таблицы и ровно один целочисленный идентификатор, и вам не придется иметь дело с какими-либо «особыми случаями».

Другое дело, что когда вы начинаете с комбинированных PK, вы, вероятно, через некоторое время столкнетесь с необходимостью комбинированных внешних ключей (поскольку вы можете прийти к точке, когда вы захотите добавить ссылку FK на вашу ApplicationPermissionsтаблицу). Тогда следующее требование может состоять в том, чтобы этот FK был уникальным в сочетании с другими атрибутами или внешними ключами - что приведет к увеличению общей сложности. Конечно, нет ничего невозможного для большинства современных систем БД, но единое решение значительно облегчает жизнь программистам.

И, наконец, оператор like SELECT ... FROM TABLE WHERE TableNameID IN (id1,id2,...)хорошо работает с одним столбцом в качестве первичного ключа, но я никогда не видел диалекта SQL, который позволял бы вам делать это с помощью комбинированных ключей. Если вы заранее знаете, что такой запрос вам никогда не понадобится, хорошо, но не удивляйтесь, если завтра вы получите требование, которое будет легче всего решить с помощью такого вида SQL.

Конечно, когда вы ожидаете, что ваша ApplicationPermissionsтаблица будет содержать несколько сотен миллионов строк, вам следует избегать чего-то вроде a ApplicationPermissionsID.

Док Браун
источник
Хотя я не выбрал твой ответ. Мне нравятся некоторые аспекты этого. Спасибо за ваши мысли (upvote).
Solidau
6

Хотя ответ Майка хороший, вот причины, по которым я бы добавил отдельное поле идентификатора или нет.

  1. Попробуйте использовать отдельное поле идентификатора для таблицы соединения / соединения, если она содержит поля, отличные от идентификатора . Это имеет тенденцию отмечать, что это первоклассная сущность.

  2. Рассмотрите возможность использования отдельного поля идентификатора, если API или какая-либо существующая логика стремятся использовать отдельные поля для извлечения / редактирования объектов. Это может помочь другим людям следовать вашему коду в контексте более крупного проекта.

  3. Не используйте его, если нет особой выгоды (KISS). EF знает, как обрабатывать этот тип таблицы, и иногда можно пропустить составное уникальное ограничение, когда другие люди пытаются понять этот тип отношений. Кроме того, при нормализации я стараюсь использовать наименьший возможный ключ, который однозначно определяет кортеж . Во втором примере у вас фактически есть 2 отдельных первичных ключа-кандидата.

Захари Йейтс
источник
-5
table Person
   Id int identity(1,1) not null primary key
   ...other fields go here...
table Address
   Id int identity(1,1) not null primary key
   ...other fields go here...
table PersonAddress
   Id int identity(1,1) not null primary key
   PersonId int not null
   AddressId int not null

Не забудьте создать индекс и внешний ключ на обоих PersonIdи AddressId.

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

16PlusYearsAsADeveloper
источник
1
Я думаю , что одна проблемы с этим подходом является схема позволяет два PersonAddressстроки с одинаковыми , PersonIdи AddressIdзначениями.
Сэм