SQL Как запросить отношение многие ко многим

если у меня есть отношение «многие ко многим» между сообщениями и тегами, как мне выбрать сообщения, содержащие определенный тег?

обновление:

проблема, с которой я сталкиваюсь, заключается в том, что из-за того, где tag.name = 'xxx', выбран только этот тег. я хочу выбрать все сообщения, для которых указан тег, tgt со всеми их тегами, например.

Post 1 -> tag1, tag2
Post 2 -> tag1, tag3
Post 3 -> tag2, tag3

в настоящее время я получаю

Post 1 -> tag2 // missing tag1
Post 3 -> tag2 // missing tag3 

person Jiew Meng    schedule 03.08.2010    source источник
comment
Учитывая, что выбранный ответ представляет собой решение SQL, а не DQL, не следует ли изменить заголовок этого вопроса с DQL на SQL? Я пришел сюда в поисках способа выполнить DQL-запрос «многие ко многим», но здесь ничего об этом нет.   -  person Jason Swett    schedule 30.03.2012


Ответы (1)


Предполагая эти таблицы:

  • Сообщения: идентификатор, автор, дата, содержание
  • Теги: идентификатор, имя
  • PostTags: post_id, tag_id

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

SELECT p.*
FROM posts p
JOIN posttags pt ON p.id = pt.post_id
JOIN tags t ON pt.tag_id = t.id
WHERE t.name = 'sql'

По сути, думайте об отношениях «многие ко многим» как о двух отношениях «один ко многим», потому что именно так они реализованы в обычных СУБД. Таким образом, приведенный выше запрос имеет соединение «один ко многим» из записей в посттеги, а другое — из тегов в посттеги.

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

  • PostTags: id, post_id, tag_id

Любой метод хорош. Это во многом философская разница.

Обновление: если вы хотите выбрать все сообщения с определенным тегом и все теги, которые есть у этих сообщений, то:

SELECT p.*
FROM posts p
JOIN posttags pt ON p.id = pt.post_id
JOIN tags t ON pt.tag_id = t.id
WHERE p.id IN
  (SELECT post_id
  FROM PostTags pt
  JOIN tags t ON pt.tag_id = t.id
  WHERE t.name = 'xyz')

Другой способ сделать это:

SELECT p.*
FROM posts p
JOIN posttags pt ON p.id = pt.post_id
JOIN tags t ON pt.tag_id = t.id
WHERE EXISTS
  (SELECT post_id
  FROM PostTags pt
  JOIN tags t ON pt.tag_id = t.id
  WHERE t.name = 'xyz'
  AND pt.post_id = p.id)

Что работает лучше, нужно будет протестировать, и оно может варьироваться в зависимости от поставщика и версии базы данных. Хороший оптимизатор (например, Oracle), вероятно, оптимизирует их, чтобы они выполнялись одинаково. Другие не могут.

Теперь это вернет вам строки следующим образом:

Post 1, tag 1
Post 1, tag 2
Post 3, tag 2
Post 3, tag 3

поэтому вам нужно будет их объединить, предпочтительно в логике приложения, а не в SQL. В некоторых РСУБД есть расширения для таких вещей, зависящие от поставщика, например MySQL GROUP_CONCAT().

person cletus    schedule 03.08.2010
comment
я сделал только SELECT post, tags FROM Application\Models\Post post INNER JOIN post.tags tags b4 я видел этот пост, и он, кажется, работает. без имени тега. есть ли разница между твоим и моим? - person Jiew Meng; 03.08.2010
comment
я также обнаружил, что из-за предложения where выбирается только указанный там тег. я хочу получать сообщения с этим тегом. но для совпадающих сообщений я хочу все, а не только указанный тег. может быть, я работаю над подзапросом - person Jiew Meng; 03.08.2010
comment
@jiewmeng Я не понимаю твоих вопросов. - person cletus; 03.08.2010
comment
разница между 2 только в последней строке AND pt.post_id = p.id, верно? хм... зачем это нужно? и что будет SQL или DQL? SQL верно? - person Jiew Meng; 04.08.2010