

Вот решение, которое будет работать, но требует объединения для каждого тега, который вам нужен.
SELECT * FROM topics WHERE topic_id IN (SELECT topic_id FROM topic_tags a INNER JOIN topic_tags b on a.topic_id=b.topic_id and b.tag = 'b' INNER JOIN topic_tags c on b.topic_id=c.topic_d and c.tag = 'c' WHERE a.tag = 'a') AND topic_id NOT IN (SELECT topic_id FROM topic_tags WHERE tag = 'd' or tag = 'e' or tag = 'f')
Не совсем уверен, что понимаю, и я надеюсь, что есть лучший способ сделать хорошую часть тегов, но:
select id from topic
inner join topic_tags tta on topic.id=tta.topicFk and tta.tagFk=a
inner join topic_tags ttb on topic.id=ttb.topicFk and ttb.tagFk=b
inner join topic_tags ttc on topic.id=ttc.topicFk and ttc.tagFk=c
left join topic_tags tt on topic.id=tt.topicFk and tt.tagFk in (d,e,f)
where tt.topicFk is null;
Обновление: примерно так:
select id from topic
left join topic_tags tt on topic.id=tt.topicFk and tt.tagFk in (d,e,f)
where tt.topicFk is null and
3=(select count(*) from topic_tags where topicFk=topic.id and tagFk in (a,b,c));
Я вижу один ответ, предполагающий, что a, b, c, d, e, f - это имена, а не идентификаторы. Если так, то это:
select id from topic
left join topic_tags tt on topic.id=tt.topicFk
inner join tags on tt.tagFk=tags.id and tags.name in (d,e,f)
where tt.topicFk is null and
3=(select count(*) from tags inner join topic_tags on tags.id=topic_tags.tagFk and topic_tags.topicFk=topic.id where tags.name in (a,b,c));
Как писал, пришли еще 3 ответа, но это другое, поэтому я все равно отправлю его.
Идея состоит в том, чтобы выбрать все темы с тегами a, b, c, затем определить те темы, которые также имеют d, e, f, с левым соединением, а затем отфильтровать их с помощью предложения where, ищущего нули в этом соединении .. .
select distinct topics.id from topics
inner join topic_tags as t1
on (t1.topicFK=topics.id)
inner join tags as goodtags
on(goodtags.id=t1.tagFK and goodtags.name in ('a', 'b', 'c'))
left join topic_tags as t2
on (t2.topicFK=topics.id)
left join tags as badtags
on(badtags .id=t2.tagFK and batags.name in ('d', 'e', 'f'))
where badtags.name is null;
Совершенно непроверено, но, надеюсь, вы понимаете, откуда исходит логика.
Вы можете использовать ключевое слово minus, чтобы отфильтровать темы с нежелательными тегами.
-- All topics with desired tags.
select distinct T.*
from Topics T inner join Topics_Tags R on T.id = R.topicFK
inner join Tags U on U.id = R.topic=FK
where U.name in ('a', 'b', 'c')
minus
-- All topics with undesired tags. These are filtered out.
select distinct T.*
from Topics T inner join Topics_Tags R on T.id = R.topicFK
inner join Tags U on U.id = R.topic=FK
where U.name in ('d', 'e', 'f')
MySQL 5.1 не имеет ключевого слова "минус"
Предполагая, что ваша таблица Topic_Tags уникальна, это отвечает на ваш вопрос точный, но не может быть обобщено для вашей реальной проблемы:
SELECT
TopicId
FROM Topic_Tags
JOIN Tags ON
Topic_Tags.TagId = Tags.TagId
WHERE
Tags.Name IN ('A', 'B', 'C', 'D', 'E', 'F')
GROUP BY
TopicId
HAVING
COUNT(*) = 3
AND MAX(Tags.Name) = 'C'
Более общее решение было бы:
SELECT
*
FROM (
SELECT
TopicId
FROM Topic_Tags
JOIN Tags ON
Topic_Tags.TagId = Tags.TagId
WHERE
Tags.Name IN ('A', 'B', 'C')
GROUP BY
TopicId
HAVING
COUNT(*) = 3
) as GoodTags
LEFT JOIN (
SELECT
TopicId
FROM Topic_Tags
JOIN Tags ON
Topic_Tags.TagId = Tags.TagId
WHERE
Tags.Name = 'D'
OR Tags.Name = 'E'
OR Tags.Name = 'F'
) as BadTags ON
GoodTags.TopicId = BadTags.TopicId
WHERE
BadTags.TopicId IS NULL
Вот еще один альтернативный запрос. Может быть, удобнее и понятнее иметь список хороших и плохих тегов вверху. Я тестировал это на MySQL 5.0.
SELECT t.*,
SUM(CASE WHEN g.name IN ('a', 'b', 'c') THEN 1 ELSE 0 END) AS num_good_tags,
SUM(CASE WHEN g.name IN ('d', 'e', 'f') THEN 1 ELSE 0 END) AS num_bad_tags
FROM topics AS t
JOIN topic_tags AS tg ON (t.id = tg.topicFk)
JOIN tags AS g ON (g.id = tg.tagFk)
GROUP BY t.id
HAVING num_good_tags = 3 AND num_bad_tags = 0;
Проще: sum (if (g.name in ('a', 'b', 'c'), 1,0)) Еще проще: sum (g.name in ('a', 'b', 'c' ))
@ysth: Да, ты прав. Мне нравится использовать стандартные предикаты SQL там, где это возможно (IF не является стандартным SQL), и я предпочитаю явно указывать 1 и 0 вместо того, чтобы полагаться на логические выражения, равные целочисленным значениям.
SELECT t.* ... GROUP BY t.id не является стандартным SQL.
@ 1010, вы правы, но вопрос помечен как MySQL.
Мое собственное решение с использованием идей Полса и Билла.
Идея состоит в том, чтобы внутренне объединить темы с хорошими тегами (чтобы выбросить темы без хороших тегов), а затем подсчитать уникальные теги для каждой темы (чтобы убедиться, что все хорошие теги присутствуют).
В то же время внешнее соединение с плохими тегами не должно иметь ни одного совпадения (все поля NULL).
SELECT topics.id
FROM topics
INNER JOIN topic_tags topic_ptags
ON topics.id = topic_ptags.topicFk
INNER JOIN tags ptags
ON topic_ptags.tagFk = ptags.id
AND ptags.name IN ('a','b','c')
LEFT JOIN topic_tags topic_ntags
ON topics.id = topic_ntags.topicFk
LEFT JOIN tags ntags
ON topic_ntags.tagFk = ntags.id
AND ntags.name IN ('d','e','f')
GROUP BY topics.id
HAVING count(DISTINCT ptags.id) = 3
AND count(ntags.id) = 0
Возвращаясь к этому, я должен добавить этот ответ на вопрос «какие темы имеют хотя бы один из тегов a, b или c, но не один тег d, e или f» - но вопрос был сформулирован как требующий все a, b, c тегов. Я вижу, что Марк опубликовал решение, которое аккуратно исправляет это, ловко подсчитывая хорошие теги.