2014-11-07 2 views
1

У меня есть тривиальные таблицы post, tag и post_tags в тривиальных отношениях Много-ко-многим. Я хочу выбрать некоторые сообщения, включив и исключив некоторые теги. Я пробовал много вариантов SQL-запросов, но ни один из них не работает для , за исключением тегов.
Я начал из запроса, как это:PostgreSQL NOT IN работает некорректно с JOIN

SELECT post.* FROM post 
INNER JOIN post_tags ON post.id = post_tags.post_id 
INNER JOIN tag ON post_tags.tag_id = tag.id 
WHERE tag.name IN ('Science','Culture') 
    AND tag.name NOT IN ('War', 'Crime') 
GROUP BY post.id 
HAVING COUNT(post_tags.id) > 1 
ORDER BY post.rating DESC 
LIMIT 50; 

Но, к сожалению, это не работает. Я вижу сообщения с тегом «Война» в наборе результатов. Тогда я попытался переместить условие NOT IN в отдельный подзапрос на post_tags и присоединиться к нему:

SELECT post.* FROM post 
INNER JOIN post_tags ON post.id = post_tags.post_id 
INNER JOIN (SELECT * FROM tag WHERE name NOT IN ('War', 'Crime')) AS tags 
    ON post_tags.tag_id = tags.id 
WHERE tags.name IN ('Science','Culture')   
GROUP BY post.id 
HAVING COUNT(post_tags.id) > 1 
ORDER BY post.rating DESC 
LIMIT 50; 

Даже пытались исключить некоторые посты в первом JOIN так:

SELECT post.* FROM post 
INNER JOIN post_tags ON post.id = post_tags.post_id 
    AND post_tags.tag_id NOT IN (SELECT id FROM tag WHERE name IN ('War', 'Crime')) 
INNER JOIN tag ON post_tags.tag_id = tag.id 
WHERE tag.name IN ('Science','Culture')   
GROUP BY post.id 
HAVING COUNT(post_tags.id) > 1 
ORDER BY post.rating DESC 
LIMIT 50; 

Но ни одна из этих работ , Меня особенно смущает вопрос о втором запросе (соединение с фильтрованным набором результатов вместо таблицы).
Использование PostgreSQL версии 9.3, OS Ubuntu 14.04.
Любые мысли?

ответ

3

Он отлично работает. Это ваша логика. Вы отфильтровываете те теги, которые хотите проверить. Таким образом, они не являются частью проверки.

Вместо перемещения условия в п having:

SELECT p.* 
FROM post p INNER JOIN 
    post_tags pt 
    ON p.id = pt.post_id INNER JOIN 
    tag t 
    ON pt.tag_id = t.id 
WHERE t.name IN ('Science', 'Culture', 'War', 'Crime') 
GROUP BY p.id 
HAVING SUM(CASE WHEN t.name IN ('Science', 'Culture') THEN 1 ELSE 0 END) > 1 AND 
     SUM(CASE WHEN t.name IN ('War', 'Crime') THEN 1 ELSE 0 END) = 0 
ORDER BY p.rating DESC; 

Существует разница между игнорированием значение (в предложении where) по сравнению с проверки того, что она не существует (в пункте having).

2

Это приложение от . Проверьте описание тега.

Вы должны определить, что вы хотите точно. Сообщения с «хорошие» теги и ни одна из «плохих» тегов? Или все хороших тегов?

Наилучший метод запроса зависит от расположения таблицы. Обычно мы предполагаем ссылочную целостность и что (post_id, tag_id) определен уникально в post_tags, но это не определено.

Предполагая, что и описать вашу проблему, как:

Возвращение на 50 постов с наивысшим рейтингом, по меньшей мере, один из тегов («Наука», «Культура»), и ни один из тегов ('War ',' Преступление ').

Мы можем перевести это простое английское предложение в SQL непосредственно:

SELECT p.* 
FROM post p 
WHERE EXISTS (    -- at least one of the tags ('Science','Culture') 
    SELECT 1 
    FROM tag t 
    JOIN post_tags pt ON pt.tag_id = t.id 
    WHERE pt.post_id = p.id 
    AND t.name IN ('Science', 'Culture') 
AND NOT EXISTS (   -- none of the tags ('War', 'Crime') 
    SELECT 1 
    FROM tag t 
    JOIN post_tags pt ON pt.tag_id = t.id 
    WHERE pt.post_id = p.id 
    AND t.name IN ('War', 'Crime') 
ORDER BY p.rating DESC  -- with the highest rating 
LIMIT 50;     -- 50 posts 

Это, как правило, быстрее, чем группировка строк и подсчета голосов - а также работает, если (post_id, tag_id) не является уникальным.

Более методы реляционного деления:

+0

Я должен сказать, что ваше решение в 1,5 раза быстрее, на моей базе данных, чем первое решение. Благодаря! –

+0

@VoidFloyd: Итак, что заставило вас снова принять менее эффективное решение? –

+1

На больших объемах данных это решение становится все более медленным. Первое решение выполняется быстрее. Но самое быстрое решение состоит в том, чтобы дважды присоединиться к таблице post_tags - в первый раз это внутреннее соединение с условием IN, а во втором - внешнее соединение с условием NOT IN. Затем мы можем подсчитать pt.id и pt2.id и сделать условие HAVING для этого числа - 2 и 0 соответственно. Кроме того, конечно, лучше не присоединяться к самому тегу tabe, лучше получить необходимые идентификаторы от него (или от запроса, зависит от приложения) и использовать их в joiningconditions post_tags. –

Смежные вопросы