PostgreSQL NOT IN无法正常使用JOIN

时间:2014-11-07 23:56:12

标签: sql postgresql join relational-division

我在一个简单的多对多关系中有简单的表格posttagpost_tags。我想通过包含和排除某些标签来选择一些帖子。我尝试了很多SQL查询的变体,但它们都不适用于排除标记 我从这样的查询开始:

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;

但不幸的是,这不起作用。我在结果集中看到带有“War”标签的帖子。然后我尝试将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 有什么想法吗?

2 个答案:

答案 0 :(得分: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子句中)之间存在差异。

答案 1 :(得分:2)

这是的应用程序。查看标签说明。

您必须定义完全的内容。帖子包含一个的“好”标签而没有“坏”标签?或好的标签的所有

最佳查询技术取决于表格布局。通常我们假设参照完整性,并且(post_id, tag_id)post_tags的定义是唯一的,但是没有定义。

假设并将您的问题描述为:

返回评分​​最高的50个帖子,其中至少有一个标签(“科学”,“文化”),没有任何标签(“战争”,“犯罪”)。 < / p>

我们可以直接将这个简单的英语句子翻译成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)不唯一,也会有效。

关系分工的更多技巧: