Как обеспечить целостность БД с помощью неуникальных внешних ключей? - PullRequest
5 голосов
/ 09 октября 2009

Я хочу иметь таблицу базы данных, которая хранит данные с историей изменений (например, страницы в Википедии). Я подумал, что хорошей идеей будет иметь два столбца, которые идентифицируют строку: (name, version). Таким образом, пример таблицы будет выглядеть так:

TABLE PERSONS:
    id:      int,
    name:    varchar(30),
    version: int,
    ... // some data assigned to that person.

Таким образом, если пользователи хотят обновить личные данные, они не делают ОБНОВЛЕНИЕ - вместо этого они создают новую строку ЧЕЛОВЕК с тем же name, но другим version значением. Данные, отображаемые пользователю (для заданных name) - это данные с самым высоким version.

У меня есть вторая таблица, скажем, СОБАКИ, которая ссылается на людей в таблице ЛИЦ:

TABLE DOGS:
    id:         int,
    name:       varchar(30),
    owner_name: varchar(30),
    ...

Очевидно, owner_name является ссылкой на PERSONS.name, но я не могу объявить его как внешний ключ (в MS SQL Server), потому что PERSONS.name не уникален!

Вопрос : Как тогда, в MS SQL Server 2008, я должен обеспечить целостность базы данных (т. Е. Что для каждого СОБАКА существует как минимум одна строка в ЧЕЛОВЕКАХ, так что ее ЛИЦО.name == DOG.owner_name)

Я ищу самое элегантное решение - я знаю, что могу использовать триггеры в таблице PERSONS, но это не так декларативно и элегантно, как хотелось бы. Есть идеи?


Дополнительная информация

Приведенный выше дизайн имеет следующее преимущество: я могу «запомнить» текущую id (или (name, version) пару) человека, и я уверен, что данные в этой строке никогда не изменятся. Это важно, например, если я помещу данные этого человека как часть документа, который затем будет напечатан, и через 5 лет кто-то захочет распечатать его копию без изменений (например, с теми же данными, что и сегодня), тогда это будет очень легко сделать .

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


Редактировать: Благодаря ответу Майкла Гаттузо, я обнаружил другой способ описать эти отношения. Есть два решения, которые я разместил в качестве ответов. Пожалуйста, проголосуйте, какой вам больше нравится.

Ответы [ 8 ]

5 голосов
/ 12 октября 2009

В родительской таблице создайте уникальное ограничение для (id, version). Добавьте столбец версии в свою дочернюю таблицу и используйте проверочное ограничение, чтобы убедиться, что оно всегда равно 0. Используйте ограничение FK для сопоставления (parentid, версия) с родительской таблицей.

2 голосов
/ 09 октября 2009

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

1 голос
/ 09 октября 2009

Хорошо, во-первых, вам нужно нормализовать ваши таблицы. Google "нормализация базы данных", и вы получите много чтения. Стол PERSONS, в частности, требует внимания.

Во-вторых, когда вы создаете ссылки на внешний ключ, 99,999% времени вы хотите ссылаться на числовое значение идентификатора. Т.е., [СОБАКИ]. [Владелец] должен быть ссылкой на [ЛИЦА]. [Id].

Редактировать: добавление примера схемы (простите за свободный синтаксис). Я предполагаю, что у каждой собаки есть только один владелец. Это один способ реализации истории персонажа. Все столбцы не нулевые.

Persons Table:
int Id
varchar(30) name
...

PersonHistory Table:
int Id
int PersonId (foreign key to Persons.Id)
int Version (auto-increment)
varchar(30) name
...

Dogs Table:
int Id
int OwnerId (foreign key to Persons.Id)
varchar(30) name
...

Последняя версия данных будет храниться непосредственно в таблице Persons, а более старые данные хранятся в таблице PersonHistory.

0 голосов
/ 10 октября 2009

Благодаря ответу Майкла Гаттузо, я обнаружил другой способ описать эти отношения. Есть два решения, это второе из них. Пожалуйста, проголосуйте, какой вам больше нравится.

Раствор 2

В таблице PERSONS мы оставляем только имя (уникальный идентификатор) и ссылку на данные first (not current!) Человека:

TABLE PERSONS:
    name:            varchar(30),
    first_data_id: int

Мы создаем новую таблицу PERSONS_DATA, которая содержит всю историю данных для этого человека:

TABLE PERSONS_DATA:
    id:      int
    name:    varchar(30)
    version: int (auto-generated)
    ... // some data, like address, etc.

Таблица СОБАК остается прежней, она по-прежнему указывает на имя человека (таблица FK to PERSONS).

ПРЕИМУЩЕСТВА:

  • для каждой собаки существует хотя бы одна строка PERSONS_DATA, которая содержит данные о ее владельце (это то, что я хотел)
  • если я хочу изменить данные человека, мне не нужно обновлять строку PERSONS, только добавить новую строку PERSONS_DATA

ОТКЛОНЕНИЕ: чтобы получить данные текущего человека, мне нужно либо:

  • выберите PERSONS_DATA с указанным именем и самой высокой версией (может быть дорого)
  • выберите PERSONS_DATA со специальной версией, например, «-1», но тогда мне придется обновлять две строки PERSONS_DATA каждый раз, когда я добавляю новые PERSONS_DATA, и в этом решении я хотел избежать обновления 2 строк ...

Что вы думаете?

0 голосов
/ 10 октября 2009

Благодаря ответу Майкла Гаттузо, я обнаружил другой способ описать эти отношения. Есть два решения, это первое из них. Пожалуйста, проголосуйте, какой вам больше нравится.

Раствор 1

В таблице PERSONS мы оставляем только имя (уникальный идентификатор) и ссылку на текущие данные человека:

TABLE PERSONS:
    name:            varchar(30),
    current_data_id: int

Мы создаем новую таблицу PERSONS_DATA, которая содержит всю историю данных для этого человека:

TABLE PERSONS_DATA:
    id:      int
    version: int (auto-generated)
    ... // some data, like address, etc.

Таблица СОБАК остается прежней, она по-прежнему указывает на имя человека (таблица FK to PERSONS).

ADVANTAGE: для каждой собаки существует хотя бы одна строка PERSONS_DATA, которая содержит данные о ее владельце (это то, что я хотел)

ОТКЛОНЕНИЕ: если вы хотите изменить данные человека, вы должны:

  1. добавить новую строку PERSONS_DATA
  2. обновить запись PERSONS для этого человека, чтобы она указывала на новую строку PERSONS_DATA.
0 голосов
/ 09 октября 2009

Лица

id (int),
имя,
.....
activeVersion (это будет UID от personVersionInfo)

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

PersonVersionInfo

UID (уникальный идентификатор для идентификации лица + версия),
id (int),
имя
.....
versionId (это будет сгенерировано для каждого человека)

Собаки

DogID
DogName
......

PersonsWithDogs

UID,
DogID

РЕДАКТИРОВАТЬ: вам нужно присоединиться к PersonWithDogs, PersionVersionInfo, Dogs, чтобы получить полную картину (на сегодняшний день). Такая структура поможет вам связать Собаку с Владельцем (с определенной версией).

В случае, если информация о Персоне изменится и вы захотите, чтобы последняя информация была связана с Собакой, вам потребуется обновить таблицу PersonWithDogs, чтобы получить требуемый UID (персонажа) для данной Собаки.

У вас могут быть такие ограничения, как DogID должен быть уникальным в PersonWithDogs.
И в этой структуре UID (человек) может иметь много собак.

Ваши сценарии (что может измениться / ограничения и т. Д.) Помогут лучше разработать схему.

0 голосов
/ 09 октября 2009

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

Однако старые записи должны были быть связаны с первоначальными названиями видов. Это стало сложным, но основное решение состояло в том, чтобы иметь таблицу NAME, которая просто содержала все уникальные названия видов, таблицу видов, представляющую фактические виды, и таблицу NAME_VERSION, которая связывала их вместе. В любое время было бы предпочтительное имя (т. Е. Общепринятое в настоящее время научное название вида), которое представляло собой логическое поле, содержащееся в name_version.

В вашем примере это будет переводить в таблицу Details (detailsid, столбцы otherdetails) таблицу ссылок с именем DetailsVersion (detailsid, personid) и таблицу Person (personid, неизменяемые данные). Отношение собак к персоне.

0 голосов
/ 09 октября 2009

Я бы использовал таблицу ассоциаций для связи множества версий с одним ПК.

...