Один ко многим самому себе
Как можно структурировать таблицу для объекта, который может иметь отношение один ко многим к себе? В частности, я работаю над приложением для отслеживания разведения животных. У каждого животного есть удостоверение личности; у него также есть идентификатор производителя и идентификатор дамы. Таким образом, возможно иметь одного ко многим от отца или дамы до его потомства. Я был бы склонен к чему-то вроде этого:
ID INT NOT NULL PRIMARY KEY
SIRE_ID INT
DAME_ID INT
и записать нулевое значение для тех животных, которые были приобретены и добавлены к племенному скоту, и идентификационный номер в таблице для остальных.
Так:
- Может кто-нибудь указать мне на статью / веб-страницу, которая обсуждает моделирование такого рода отношений?
- Идентификатор должен быть INT или чем-то вроде String? NULL в INT будет означать, что у животного нет родителей в базе данных, но String со специальными значениями флага может использоваться для указания того же самого.
Будет ли это лучше всего моделировать через две таблицы? Я имею в виду одну таблицу для животных и отдельную таблицу, указывающую исключительно родство, например:
животное
ID INT НЕ НУЛЬ ПЕРВИЧНЫЙ КЛЮЧ
родство
ID INT НЕ НУЛЬ ПЕРВИЧНЫЙ КЛЮЧ ИНОСТРАННЫЙ КЛЮЧ
SIRE_ID INT ПЕРВИЧНЫЙ КЛЮЧ ИНОСТРАННЫЙ КЛЮЧ
DAME_ID INT ПЕРВИЧНЫЙ КЛЮЧ ИНОСТРАННЫЙ КЛЮЧ
Прошу прощения за вышесказанное: мой SQL ржавый. Я надеюсь, что это как бы передает то, о чем я думаю.
9 ответов
Что ж, это "нормальное" отношение "один ко многим", и метод, который вы предлагаете, является классическим для его решения.
Обратите внимание, что две таблицы денормализованы (я не могу точно указать, где находится часть "супер-ключ-не-хорошо-должен-быть-подмножество-другого-ключа-fsck-I-забыл", но я уверен, что это где-то там); Интуитивно понятная причина в том, что кортеж в первом соответствует максимум кортежу во втором, поэтому, если у вас нет большого количества животных с нулевыми идентификаторами отца и дамы, это не очень хорошее решение в любой перспективе (это ухудшает производительность - нужно объединение - и не снижает требования к хранению).
Я думаю, что ваш макет с использованием только одной таблицы в порядке. Вы определенно хотите сохранить SIRE_ID и DAME_ID в том же типе данных, что и ID. Вы также хотите объявить их как ЗАРУБЕЖНЫЕ КЛЮЧИ (возможно, что внешний ключ указывает на ту же таблицу, а внешний ключ также может быть нулевым).
ID INT NOT NULL PRIMARY KEY
SIRE_ID INT REFERENCES TABLENAME (ID)
DAME_ID INT REFERENCES TABLENAME (ID)
Используя этот макет, вы можете легко искать родительских животных, а также вы можете построить дерево потомства для данного животного (для Oracle есть CONNECT BY)
Я задавал подобный вопрос несколько месяцев назад на сайте MySQL. Я бы порекомендовал вам взглянуть на ответ, который я получил от Питера Броули относительно этого типа отношений: http://forums.mysql.com/read.php?135,187196,187196
Если вы хотите изучить эту тему дальше, я бы порекомендовал вам взглянуть на древовидные иерархии в Википедии.
Альтернативная предлагаемая архитектура (которая будет полностью нормализована) будет выглядеть примерно так:
Таблица: животное
ID | Имя | разводить
Таблица: родословная
animal_id | parent_id | parentType (сир или дама)
Я не знаю о животноводстве, но похоже, что ваш Sire_ID это отец, а Dame_ID это мать? Нет проблем. Одна строка для каждого животного, ноль sire_ и dame_ID для купленных животных, я не вижу никаких проблем.
[ID],[Sire_ID],[Dame_ID];
0,null,null (male)
1,null,null (female)
2,null,null (female)
3,0,1 (male)
4,0,2 (male)
5,null,null (female)
6,3,5
7,4,5
и так далее. Вы вероятно заполнили бы TreeView или XmlNodeList в цикле while...
While (myAnimal.HasChildren) {
Animal[] children = GetChildren(Animal.ID)
for (int x=0; x<children.length; x++)
myAnimal.Children.Add(children[x]);
}
В данном случае Animal.Children - это коллекция животных. Следовательно, myAnimal.Children[0].ather вернет myAnimal. .Parent[] может быть коллекцией двух его родителей, которая должна работать, если [0] всегда является одним родителем (отцом), а [1] всегда другим (матерью).
Сделайте ID идентификатором PK и назначьте Sire_ID и Dame_ID программно, вернув идентификаторы его родителей. Никакие связи с внешним ключом не должны быть необходимыми, хотя оба родительских идентификатора могут ссылаться на ID, если вы действительно этого хотите.
INT является лучшим выбором для столбца ID и лучше подходит, если вам нужно использовать последовательность для генерации уникальных идентификаторов.
Я не вижу никакой выгоды в разделении дизайна на две таблицы.
Похоже, вы хотите построить что-то вроде дерева.
Как насчет чего-то вроде?
ID Primary Key,
Parent_ID Foreing_Key
( data )
Существует некоторая функциональность для выполнения запросов в таблицах с отношениями к себе. Смотрите синтаксис Connect By: http://www.adp-gmbh.ch/ora/sql/connect_by.html
Я думаю, что, поскольку ясно, что у животного есть только один отец и одна плотина, использование одного стола имело бы смысл. Я предпочитаю использовать int или bigint в качестве идентификатора строки с нулевым значением, означающим отсутствие связи. Тогда я бы, вероятно, использовал какой-то другой метод для уникальной идентификации животных, чтобы они не попадали в таблицу дважды и не создавали уникальный индекс для этого столбца.
Используйте предложение "connect by" с SQL, чтобы указать, какой иерархии следовать.
На самом деле это не отношения один ко многим, если только у животного не может быть много родителей.
Я бы оставил его в виде единой таблицы с уникальным идентификатором ключа для животного, одним целым полем для каждого из родителей и, вероятно, текстовым полем, которое можно использовать для общих заметок о животном, например, где оно было куплено, если это так.