Справка по запросу HABTM
-
18-09-2019 - |
Вопрос
У меня есть связь HABTM между "статьями" и "тегами"
Проблема: Я ищу только статьи с тегами "спорт" и "на открытом воздухе", но не статьи только с одним из этих тегов.
Я попробовал это:
SELECT DISTINCT article.id, article.name FROM articles
inner JOIN tags ON (tags.name IN ('outdoors', 'sports')
inner JOIN articles_tags ON articles_tags.article_id = article.id AND articles_tags.tag_id = tags.id
...но это дает мне статьи, посвященные только спорту, только на открытом воздухе И обоим видам спорта + на открытом воздухе
Вопрос какой правильный запрос использовать?(Я использую MySQL)
Решение
Есть два распространенных решения.
Первое решение использует
GROUP BY
подсчитывает теги для каждой статьи, которые соответствуют "outdoors" или "sports", а затем возвращает только группы, у которых есть оба тега.SELECT a.id, a.name FROM articles AS a INNER JOIN articles_tags AS at ON (a.id = at.article_id) INNER JOIN tags AS t ON (t.id = at.tag_id) WHERE t.name IN ('outdoors', 'sports') GROUP BY a.id HAVING COUNT(DISTINCT t.name) = 2;
Некоторым людям это решение кажется более понятным, а добавление значений более простым.Но
GROUP BY
запросы в MySQL, как правило, приводят к созданию временной таблицы, что снижает производительность.Другое решение использует
JOIN
для каждого отдельного тега.При использовании внутренних объединений запрос, естественно, ограничивается статьями, которые соответствуют всем указанным вами тегам.SELECT a.id, a.name FROM articles AS a INNER JOIN articles_tags AS at1 ON (a.id = at1.article_id) INNER JOIN tags AS t1 ON (t1.id = at1.tag_id AND t1.name = 'outdoors') INNER JOIN articles_tags AS at2 ON (a.id = at2.article_id) INNER JOIN tags AS t2 ON (t2.id = at2.article_id AND t2.name = 'sports');
Предполагая
tags.name
иarticles_tags.(article_id,tag_id)
у обоих естьUNIQUE
ограничений, вам не должен понадобитьсяDISTINCT
модификатор запроса.Этот тип запросов, как правило, лучше оптимизируется в MySQL, чем
GROUP BY
решение, предполагая, что вы определили соответствующие индексы.
Повторите ваш последующий вопрос в комментарии, я бы сделал что-то вроде этого:
SELECT a.id, a.name, GROUP_CONCAT(t3.tag) AS all_tags
FROM articles AS a
INNER JOIN articles_tags AS at1 ON (a.id = at1.article_id)
INNER JOIN tags AS t1 ON (t1.id = at1.tag_id AND t1.name = 'outdoors')
INNER JOIN articles_tags AS at2 ON (a.id = at2.article_id)
INNER JOIN tags AS t2 ON (t2.id = at2.article_id AND t2.name = 'sports');
INNER JOIN articles_tags AS at3 ON (a.id = at3.article_id)
INNER JOIN tags AS t3 ON (t3.id = at3.article_id);
GROUP BY a.id;
Это по-прежнему находит только статьи с обоими тегами "outdoors" и "sports", но затем дополнительно объединяет эти статьи с ВСЕ его теги.
Это вернет несколько строк для каждой статьи (по одной для каждого тега), поэтому затем мы используем GROUP BY
чтобы снова сократить количество строк для каждой статьи до одной. GROUP_CONCAT()
возвращает разделенный запятыми список значений в соответствующей группе.
Другие советы
Попробуй это:
SELECT a1.id, a1.name FROM articles a1
JOIN tags t1 ON t1.name ='outdoors'
JOIN articles_tags at1 ON at1.article_id = a1.id AND at1.tag_id = t1.id
JOIN tags t2 ON t2.name = 'sports'
JOIN articles_tags at2 ON at2.article_id = a1.id AND at2.tag_id = t2.id