2010-06-23 38 views
3

有两个表格,categoriesbooks,我想根据给定的类别选择所有书籍。SQL:如何根据类别进行选择?

分类表:

cat_id | book_id 
---------------- 
1  | 1 
2  | 1 
3  | 1 
3  | 2 

书籍表:

id | name 
---------------- 
1 | abc 
2 | def 

我试过SELECT * FROM categories WHERE cat_id IN(1,3)但随后返回包含给定类别中的至少一个书。我想要的是它只返回包含所有类别的书籍,所以它应该只返回book_id = 1的所有(或一个)行,因为它是唯一具有所有给定类别的书籍。

回答

3

尝试:

select book_id 
from categories 
group by book_id 
having sum((cat_id in (1,3))::int) = 2 

或者,如果你打算从支持直接传递数组给它(这样的:http://fxjr.blogspot.com/2009/05/npgsql-tips-using-in-queries-with.html)语言传递一个数组Postgres的,使用这样的:

select book_id 
from categories 
group by book_id 
having sum((cat_id = ANY(ARRAY[1,3]))::int) = 2 

如果你想得到书名:

select categories.book_id, books.name 
from categories 
join books on books.id = categories.book_id 
group by categories.book_id 
    ,books.name 
having sum((categories.cat_id in (1,3))::int) = 2 

@Evan Carroll,修改查询:

ANSI SQL的方式:

select categories.book_id, books.name 
from categories 
join books on books.id = categories.book_id 
group by categories.book_id 
    ,books.name 
having count(case when categories.cat_id in (1,3) then 1 end) = 2 

三世书名称:

select book_id 
from categories 
group by book_id 
having count(case when cat_id in (1,3) then 1 end) = 2 

什么是内联的条件和相同的条款中(即其计数值的优势。having),而不是单独把条件where条款及其having条款计数?...

select book_id 
from categories 
where category_id in (1,3) 
group by book_id 
having count(*) = 2 

...如果我们两个内嵌的条件和条款having其计数值,我们可以很方便的我们可以通过查询所有分类为1和3的书籍,或者分类为2和3和4的。面向未来的FTW!此外,对组合类别和数量的测试彼此相邻,再加上可读性因素。

为了方便那种查询:

select book_id 
from categories 
group by book_id 
having 
    count(case when cat_id in (1,3) then 1 end) = 2 
    or count(case when cat_id in (2,3,4) then 1 end) = 3 

要达到的性能(有时,实现了性能和可读性;不要拌匀),必须复制having子句,其中的元素测试条款:

select book_id 
from categories 
where cat_id in (1,2,3,4) 
group by book_id 
having 
    count(case when cat_id in (1,3) then 1 end) = 2 
    or count(case when cat_id in (2,3,4) then 1 end) = 3 

[编辑]

顺便说一句,这里的惯用MySQL的:

select book_id 
from categories 
group by book_id 
having sum(cat_id in (1,3)) = 2 
+0

这看起来相当尴尬,而且是错误的。sum用于添加参数,'count()'用于对行进行计数。看到我的答案更容易做到这一点。 – 2010-06-23 14:52:58

+0

之前你说这是错的,这是一个习惯性的postgres。如果我使用mysql,我会这样做:'sum(categories.cat_id in(1,3))',因为在mysql中,布尔和整数是相同的,它们在幕后只有1和0 ,所以不需要更多的铸造。对于postgresql,我们只需要将布尔值转换为整数就可以按照预期工作。好的,为你我会使它符合ANSI SQL。编辑即将到来 – 2010-06-23 15:04:23

3

实际上,您每本书都会获得多个条目。如果nn类别被分配给这本书,你会得到n这本书的条目。所以,你可以组你的查询,只选择那些谁拥有ñ点击:如说我编辑查询,以便它返回一个包含所有种类的书籍:

SELECT T.cat_id, count(*) hits FROM 
(
    SELECT * FROM categories WHERE cat_id IN(1,3) 
) T 
GROUP BY T.cat_id 
HAVING hits = 2 
+0

但是,它仍然返回所有包含至少一个给定类别的书籍,但这不是想要的行为。 – EarthMind 2010-06-23 14:13:35

+0

这是一个好主意。 – Pointy 2010-06-23 14:14:12

+2

@Earthmind你会在最后添加一个“having”子句,并且只接受count为2的行(或其他;你的“IN”列表中的类别数量)。 – Pointy 2010-06-23 14:14:58

-1

试试这个:

SELECT * FROM books WHERE id IN 
(SELECT book_id 
FROM categories 
GROUP BY book_id 
HAVING COUNT(distinct cat_id) = (select count(distinct cat_id) from categories)) 

编辑在这个问题

+0

此处您不检查书籍分配到的分类 – chiccodoro 2010-06-23 14:18:26

+0

此查询返回两个类别的书籍 – pcent 2010-06-23 14:20:25

+1

right ,所以它不回答问题 – chiccodoro 2010-06-23 14:24:40

0

加入对你要求每个类别:

SELECT books.* 
FROM books 
    JOIN categories cat1 ON cat1.book_id = books.book_id 
    JOIN categories cat3 ON cat3.book_id = books.book_id 
WHERE cat1.cat_id = 1 
     AND cat3.cat_id = 3 

或者,如果您不想添加内部连接,则可以等效使用WHERE EXISTS(半连接)。

1

另一种替代方法:

SELECT book_id FROM categories WHERE cat_id = 1 
INTERSECT 
SELECT book_id FROM categories WHERE cat_id = 3; 

您可以继续链相交,如果你有两个以上的类别相匹配。

+0

这将需要对每个类别单独选择,并且会增加代码和操作的复杂性为每个添加的类别。您正在索引或不必要地扫描表。 – 2010-06-23 15:30:13

+0

不确定这是否需要反对票。我添加这个例子的目的是为了表明这实际上是一个基于集合的问题,并且存在PostgreSQL支持的“适当的”基于集合的解决方案。我从应用程序代码的角度对性能或易用性没有提出任何要求。事实上,我也在我自己的应用程序代码中使用了HAVING SUM(CASE ...)方法,但是对于临时请求,我发现INTERSECT更容易读取和写入。 – 2010-06-24 14:55:17

0
SELECT * FROM 
(
SELECT b.id, count(c.cat_id) as cat_count 
FROM books AS b 
JOIN cats AS c 
    ON (b.id = c.book_id) 
GROUP BY b.id 
) AS t 
WHERE t.cat_count = (SELECT DISTINCT count(cat_id) FROM cat); 

这是假设一本书不能同日而语的两倍。这将选择任一类别中的所有图书,对类别进行计数,并确保类别数量是类别的最大数量。