МИНУС в MySQL? - PullRequest
       67

МИНУС в MySQL?

2 голосов
/ 09 октября 2008

У меня есть темы (id *), теги (id *, name) и таблица ссылок topic_tags (topicFk *, tagFk *).

Теперь я хочу выбрать каждую тему, в которой есть все хорошие теги (a, b, c), но нет плохих тегов (d, e, f).

Как мне это сделать?

Ответы [ 7 ]

5 голосов
/ 09 октября 2008

Предполагая, что ваша таблица 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
3 голосов
/ 09 октября 2008

Вот еще один альтернативный запрос. Может быть, более понятным и удобным является размещение списка хороших и плохих тегов вверху. Я проверял это на 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;
3 голосов
/ 09 октября 2008

Вот решение, которое будет работать, но требует объединения для каждого требуемого тега.

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')
1 голос
/ 09 октября 2008

Как написано, пришли еще 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;

Абсолютно не проверено, но, надеюсь, вы увидите, откуда взялась логика.

0 голосов
/ 09 октября 2008

Мое собственное решение с использованием идей Паулса и Билла.

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

В то же время внешнее соединение с ошибочными тегами не должно иметь ни одного совпадения (все поля имеют значение 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
0 голосов
/ 09 октября 2008

Вы можете использовать ключевое слово 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')
0 голосов
/ 09 октября 2008

Не совсем уверен, что понимаю, и я надеюсь, что есть лучший способ сделать хорошую часть тегов, но:

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));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...