Каков наилучший способ представления связи «многие ко многим» между записями в одной таблице SQL? - PullRequest
6 голосов
/ 23 января 2009

У меня есть таблица SQL примерно так:

Обновление: я изменяю таблицу примеров, поскольку существующая иерархическая природа исходных данных (Штат, Города, Школы) затмевает тот факт, что между элементами необходимы простые отношения.

entities
id      name               
1       Apple     
2       Orange            
3       Banana             
4       Carrot                
5       Mushroom        

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

Отношения определяются конечным пользователем.

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

Один путь, каким я его вижу ...

Мой инстинкт говорит, что таблица отношений выглядит так:

entity_entity
entity_id_a       entity_id_b
1                 2
5                 1
4                 1
5                 4
1                 3

В таком случае, учитывая предоставленный entity_id, равный 4, как можно получить все связанные записи, которые будут 1 и 5?

Аналогично, запрос entity_id = 1 должен возвращать 2, 3, 4 и 5.

Спасибо за ваше время и дайте мне знать, если я вообще смогу уточнить вопрос.

Ответы [ 8 ]

11 голосов
/ 23 января 2009

Определить ограничение: entity_id_a < entity_id_b.

Создание индексов:

CREATE UNIQUE INDEX ix_a_b ON entity_entity(entity_id_a, entity_id_b);
CREATE INDEX ix_b ON entity_entity(entity_id_b);

Второй индекс не должен включать entity_id_a, поскольку вы будете использовать его только для выбора всех a в пределах одного b. RANGE SCAN на ix_b будет быстрее, чем SKIP SCAN на ix_a_b.

Заполните таблицу своими сущностями следующим образом:

INSERT
INTO entity_entity (entity_id_a, entity_id_b)
VALUES (LEAST(@id1, @id2), GREATEST(@id1, @id2))

Затем выберите:

SELECT entity_id_b
FROM entity_entity
WHERE entity_id_a = @id
UNION ALL
SELECT entity_id_a
FROM entity_entity
WHERE entity_id_b = @id

UNION ALL здесь позволяет вам использовать вышеуказанные индексы и избегать дополнительной сортировки для уникальности.

Все вышеизложенное справедливо для симметричных и антирефлексивных отношений. Это означает, что:

  • Если a относится к b , то b относится к a

  • a никогда не относится к a

1 голос
/ 23 января 2009

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

Если элементы находятся в (не перекрывающихся) классах эквивалентности, вы можете сделать классы эквивалентности основой для дизайна таблицы, где все в классе считается эквивалентным. Сами классы могут быть анонимными:

CREATE TABLE equivalence_class (
    class_id int -- surrogate, IDENTITY, autonumber, etc.
    ,entity_id int
)

entity_id должно быть уникальным для непересекающегося раздела вашего пространства.

Это позволяет избежать проблемы обеспечения правильной лево- или правосторонности или форсирования матрицы правых верхних отношений.

Тогда ваш запрос немного отличается:

SELECT c2.entity_id
FROM equivalence_class c1
INNER JOIN equivalence_class c2
    ON c1.entity_id = @entity_id
    AND c1.class_id = c2.class_id
    AND c2.entity_id <> @entity_id

или, что эквивалентно:

SELECT c2.entity_id
FROM equivalence_class c1
INNER JOIN equivalence_class c2
    ON c1.entity_id = @entity_id
    AND c1.class_id = c2.class_id
    AND c2.entity_id <> c1.entity_id
1 голос
/ 23 января 2009

Подход таблицы ссылок выглядит нормально, за исключением того, что вам может понадобиться «тип отношений», чтобы вы знали, ПОЧЕМУ они связаны.

Например, отношения между Роли и Северной Каролиной не такие же, как отношения между Роли и Даремом. Кроме того, вы можете узнать, кто является «родителем» в отношениях, если вы вели условные выпадающие списки. (т. е. вы выбираете штат, вы видите города в этом штате).

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

1 голос
/ 23 января 2009

Я думаю, что предложенная вами структура в порядке.

Чтобы получить связанные записи, сделайте что-то вроде

SELECT related.* FROM entities AS search 
LEFT JOIN entity_entity map ON map.entity_id_a = search.id
LEFT JOIN entities AS related ON map.entity_id_b = related.id
WHERE search.name = 'Search term'

Надеюсь, это поможет.

0 голосов
/ 24 января 2009

На основании вашей обновленной схемы этот запрос должен работать:

select if(entity_id_a=:entity_id,entity_id_b,entity_id_a) as related_entity_id where :entity_id in (entity_id_a, entity_id_b)

где: entity_id связан с сущностью, которую вы запрашиваете

0 голосов
/ 23 января 2009

Мой совет: ваш начальный дизайн стола плохой. Не храните разные вещи в одной таблице. (Первое правило проектирования базы данных, прямо там, не храните несколько частей информации в одном поле). Это гораздо сложнее сделать запрос и приведет к значительным проблемам производительности в будущем. Кроме того, это будет проблемой при вводе данных в таблицу соответствия. Как вы узнаете, какие объекты необходимо будет реализовать при создании новой записи? Было бы гораздо лучше правильно составить реляционные таблицы. Таблицы сущностей - почти всегда плохая идея. Я не вижу никакой причины из этого примера иметь такую ​​информацию в одной таблице. Честно говоря, у меня был бы университетский стол и соответствующая адресная таблица. Было бы легко запросить и выполнить гораздо лучше.

0 голосов
/ 23 января 2009

Я могу придумать несколько способов.

Один проход с делом:

SELECT DISTINCT
    CASE
        WHEN entity_id_a <> @entity_id THEN entity_id_a
        WHEN entity_id_b <> @entity_id THEN entity_id_b
    END AS equivalent_entity
FROM entity_entity
WHERE entity_id_a = @entity_id OR entity_id_b = @entity_id

Или два отфильтрованных запроса, UNIONed таким образом:

SELECT entity_id_b AS equivalent_entity
FROM entity_entity
WHERE entity_id_a = @entity_id
UNION
SELECT entity_id_a AS equivalent_entity
FROM entity_entity
WHERE entity_id_b = @entity_id
0 голосов
/ 23 января 2009
select * from entities
where entity_id in 
(
    select entity_id_b 
    from entity_entity 
    where entity_id_a = @lookup_value
)
...