我在一个简单的多对多关系中有简单的表格post
,tag
和post_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 有什么想法吗?
答案 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)
这是relational-division的应用程序。查看标签说明。
您必须定义完全的内容。帖子包含一个的“好”标签而没有“坏”标签?或好的标签的所有?
最佳查询技术取决于表格布局。通常我们假设参照完整性,并且(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)
不唯一,也会有效。
关系分工的更多技巧: