2010-06-23 4 views
3

Il y a deux tables, categories et books et je voudrais sélectionner tous les livres basés sur les catégories données.SQL: Comment faire une sélection en fonction des catégories?

table Catégories:

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

Table Livres:

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

J'ai essayé SELECT * FROM categories WHERE cat_id IN(1,3) mais il retourne des livres contenant au moins une des catégories données. Ce que j'aimerais, c'est qu'il ne retourne que les livres contenant TOUTES les catégories, donc il ne devrait retourner que toutes les lignes (ou une seule) où book_id = 1 puisque c'est le seul livre avec toutes les catégories données.

Répondre

3

Essayez:

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

Ou si vous avez l'intention de passer un tableau à postgres de langage qui prend en charge passant array directement à lui (comme ceci: http://fxjr.blogspot.com/2009/05/npgsql-tips-using-in-queries-with.html), utilisez ceci:

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

Si vous voulez obtenir le nom du livre:

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 

Carroll @ Evan, modifiant la requête:

ANSI chemin 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 

Sans le nom du livre:

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

Quel est l'avantage de inline la condition et sa valeur de comptage dans la même clause (i.e..having) par opposition à mettre séparément la condition where clause et son compte dans having clause? ...

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

... Si nous en ligne à la fois la condition et sa valeur de comptage dans having clause, nous pouvons faciliter un enquête de disons liste tous les livres avec des catégories de 1 et 3, ou avec des catégories de 2 et 3 et 4. Future-proofing FTW! De plus, les tests des catégories combinées et leur nombre sont proches les uns des autres, plus facteur en termes de lisibilité.

Pour faciliter ce genre de requête:

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 

Pour obtenir des performances (parfois, atteindre à la fois les performances et la lisibilité, ne se mélangent pas bien), doit dupliquer le test des éléments de clause having où clause:

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 

[EDIT]

BTW, voici le idiomatiques MySQL:

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

cela semble plutôt gênant, et faux.sum est fait pour ajouter des arguments, 'count()' est fait pour compter les lignes. Voir ma réponse pour un moyen beaucoup plus facile de le faire. –

+0

avant de dire que c'est faux, c'est un postgres idiomatique. si j'utilise mysql, je ferai ceci: 'sum (categories.cat_id in (1,3))', pour le fait que dans mysql, boolean et integer sont les mêmes, ils sont juste 1 et 0 dans les coulisses , donc plus de casting nécessaire. pour postgresql, nous avons juste besoin de convertir boolean en entier pour que les choses fonctionnent comme prévu. ok .., pour vous, je vais le rendre conforme à ANSI SQL. édition à venir –

3

Vous obtenez en réalité plus d'une entrée par livre. Si n de n catégories sont attribuées au livre, vous obtenez n entrées pour le livre. Donc, vous pouvez grouper votre requête et ne sélectionner que ceux qui ont n résultats:

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

Mais alors il retourne toujours tous les livres contenant au moins une des catégories données, alors que ce n'est pas un comportement voulu. – EarthMind

+0

C'est une bonne idée. – Pointy

+2

@Earthmind vous ajouteriez une clause "having" à la fin et n'accepterez que les lignes où le nombre est 2 (ou peu importe le nombre de catégories dans votre liste "IN"). – Pointy

-1

Essayez ceci:

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)) 

Modifié: J'ai modifié la requête afin qu'il retourne les livres contenant toutes catégories comme indiqué dans la question

+0

ici vous ne vérifiez pas à quelles catégories un livre est affecté à – chiccodoro

+0

cette requête renvoie les livres qui sont dans deux catégories – pcent

+1

droite , donc il ne répond pas à la question – chiccodoro

0

Inscrivez-vous contre chaque catégorie dont vous avez besoin:

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 

Ou vous faites cela de manière équivalente en utilisant WHERE EXISTS (semi join) si vous n'aimez pas ajouter des jointures internes.

1

Encore une autre méthode alternative:

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

Vous pouvez continuer à la chaîne intersecte si vous avez plus de deux catégories de match.

+0

Cela nécessiterait un select séparé pour chaque catégorie, et augmenterait la complexité dans le code et l'opération pour chaque catégorie ajoutée. Vous consultez des index ou scannez des tables inutilement. –

+0

Vous ne savez pas si cela mérite un vote négatif. Mon intention d'ajouter cet exemple est de montrer qu'il s'agit vraiment d'un problème basé sur un ensemble et qu'il existe une solution «correcte» basée sur un ensemble que PostgreSQL supporte. Je n'ai fait aucune déclaration sur les performances ou la facilité d'utilisation du point de vue du code d'application. En effet, j'utilise aussi la méthode HAVING SUM (CASE ...) dans mon propre code d'application, mais pour les requêtes ad-hoc, je trouve INTERSECT plus facile à lire et à écrire. –

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); 

Cela suppose un livre ne peut pas être dans la même catégorie deux fois. Ceci sélectionne tous les livres de chaque catégorie, compte les catégories et s'assure que le nombre de catégories est le nombre maximum de catégories.

Questions connexes