Выберите только объекты с n отношениями из отношения n: m

#mysql #sql #join #count #relational-division

#mysql #sql #Присоединиться #количество #реляционное разделение

Вопрос:

В моей базе данных есть отношение изображений и тегов m: n, которое использует перекрестную таблицу для моделирования этого. Таблица imgs содержит гораздо больше информации, чем просто img_id , но это все, что требуется для уникальной идентификации изображения.

Я хочу найти все img_id , которые содержат как tagA, так и tagB (и tagC и так далее, я создам эту строку, чтобы на самом деле не имело значения, будет ли в ней два или десять тегов).

введите описание изображения здесь

Теперь, где я застрял, конечно, сначала вы соедините imgs с img_tags с tags , добавьте предложение where для тегов;

 SELECT * 
FROM imgs 
INNER JOIN img_tags ON imgs.img_id = img_tags.img_id 
INNER JOIN tags     ON img_tags.tag_id = tags.tag_id
WHERE tag = 'tagA' OR tag = 'tagB';
  

и тогда вы получите строки с идентичной imgs информацией, отличающиеся только tag и tag_id . Теперь я должен иметь возможность подсчитывать их, ориентируясь только на те, которые отображаются в том же количестве, в каком были предоставлены теги ( Count(*) = n ), а затем использовать group by для их агрегирования? Но я не могу в этом разобраться.
В случае, если это может иметь значение, вы могли бы предположить, что поля в img_tags оба являются внешними ключами, ссылающимися на другие таблицы, однако это не так, они никак не связаны.

Ответ №1:

Вы можете использовать агрегацию следующим образом:

 SELECT i.* 
FROM imgs i JOIN
     img_tags it
     ON i.img_id = it.img_id JOIN
     tags t
     ON it.tag_id = t.tag_id
WHERE tag IN ('tagA', 'tagB')
GROUP BY i.img_id
HAVING COUNT(*) = 2;
  

Агрегирование с помощью i.img_id безопасно — и поддерживается стандартом SQL — при условии, что img_id это первичный ключ в таблице.

Ответ №2:

Вот подход, использующий коррелированный подзапрос:

 SELECT i.*
FROM imgs i
WHERE (
    SELECT COUNT(*)
    FROM img_tags it
    INNER JOIN tags t ON it.tag_id = t.tag_id
    WHERE i.img_id = it.img_id AND t.tag IN('tagA', 'tagB')
) = 2
  

Это предполагает отсутствие повторяющихся тегов в вашей структуре данных. В противном случае вы можете использовать COUNT(DISTINCT t.tag) вместо COUNT(*) .

Вы также можете использовать агрегирование:

 SELECT i.id
FROM imgs i
INNER JOIN img_tags it ON i.img_id = it.img_id 
INNER JOIN tags t      ON it.tag_id = t.tag_id
WHERE t.tag IN('tagA', 'tagB')
GROUP BY i.id
HAVING COUNT(*) = 2
  

Комментарии:

1. Для всех, кто заходит, использование агрегации намного быстрее (примерно в 5 раз для меня).

Ответ №3:

Если задействовано не так много тегов, я бы просто использовал exists (или not exists, если вы хотите исключить некоторые теги) для этого

 select *
from imgs
where
    exists(select 1 from img_tags it where it.tag_id=(select tag_id from tags where tag='tagA') and it.img_id=imgs.img_id)
    and exists(select 1 from img_tags it where it.tag_id=(select tag_id from tags where tag='tagB') and it.img_id=imgs.img_id);
  

особенно, если вы в конечном итоге захотите выполнить более сложные логические выражения, например (A и (B или не C)).