2009-09-10 9 views
12

J'ai une table de référence croisée qui ressemble à ceci:lignes SQL sélection où une valeur de colonne est commune à une autre colonne de critères

id document_id subject_id 
1 8   21 
2 5   17 
3 5   76 
4 7   88 
5 9   17 
6 9   76 
7 2   76 

Il correspond à des documents à des sujets. Les documents peuvent être membres de plus d'un sujet. Je veux retourner les lignes de cette table où un document donné correspond tous les les sujets dans un ensemble donné. Par exemple, compte tenu de l'ensemble des sujets:

(17,76)

Je veux retourner les lignes que pour les documents qui correspondent à tous les sujets de cet ensemble (au moins) quelque part dans la table de référence croisée. L'ensemble de sortie souhaité étant donné l'ensemble ci-dessus serait:

id document_id subject_id 
2 5   17 
3 5   76 
5 9   17 
6 9   76 

Notez que la dernière ligne de la table n'est pas retourné parce que le document correspond uniquement à l'un des sujets requis.

Quel est le moyen le plus simple et le plus efficace d'interroger pour cela dans SQL?

+0

Ce serait bien de savoir comment vous fournissez les paramètres à la requête. Je vois une réponse, bien que parfaitement bien, cela ne fonctionnera que pour exactement 2 valeurs dans le jeu de paramètres. Si vous pouvez limiter le nombre de paramètres à, disons, 10 max, alors c'est une conversation. Si vous avez besoin d'une application flexible, les suggestions seront différentes. – Eugene

+0

Merci, l'entrée est fondamentalement "choisir un nombre quelconque de sujets" de sorte que l'ensemble des identifiants de sujet peut croître aussi grand que le nombre de sujets (en théorie). – Maciek

Répondre

27

Je suppose que la clé natrual de ce tableau est document_id + id_sujet, et cet identifiant est un substitut; IOW, document_id et subject_id sont uniques. En tant que tel, je vais juste prétendre qu'il n'existe pas et qu'une contrainte unique est sur la clé naturelle.

Commençons par l'évidence.

SELECT document_id, subject_id 
    FROM document_subjects 
WHERE subject_id IN (17,76) 

qui vous fait tout ce que vous voulez , plus choses que vous ne voulez pas. Donc, tout ce que nous devons faire est de filtrer les autres choses. Les "autres choses" sont des groupes de lignes dont le compte n'est pas égal au nombre de sujets désirés.

SELECT document_id 
    FROM document_subjects 
WHERE subject_id IN (17,76) 
GROUP BY document_id 
HAVING COUNT(*) = 2 

Notez que subject_id est supprimé car il ne participe pas au regroupement. Prenant ceci un peu plus loin, je vais ajouter une table imaginaire appelée sujets_i_want qui contient N lignes de sujets que vous voulez. Il est évident que subjects_i_want pourrait être permuté pour une autre sous-requête, une table temporaire, ou autre chose. Mais, une fois que vous avez cette liste de document_id, vous pouvez l'utiliser dans un sous-ensemble d'une requête plus grande.

SELECT document_id, subject_id, ... 
    FROM document_subjects 
WHERE document_id IN(
     SELECT document_id 
      FROM document_subjects 
      WHERE subject_id IN (SELECT subject_id FROM subjects_i_want) 
      GROUP BY document_id 
     HAVING COUNT(*) = (SELECT COUNT(*) FROM subjects_i_want)) 

Ou peu importe.

+0

Génial, merci. – Maciek

+1

+1 très gentil, Alex. J'ai remarqué quelques variations de cette question récemment, et c'est la solution générale la plus clairement présentée jusqu'à présent. – Matt

+0

+1, très nce et m'a aidé, il vaudrait mieux que le compte (*) soit effectué sur des entrées distinctes, car cela supprimerait la possibilité de voir également des données dupliquées; de préférence COUNT (DISTINCT sujet_id) au lieu de COUNT (*) –

1

C'est une question très intéressante.

Je présume que vous souhaitez une requête plus généralisée, mais ce que je ferais dans le cas où vous avez toujours le même nombre de sujets (par exemple deux):

SELECT T.id, T.document_id, T.subject_id 
    FROM table T 
     INNER JOIN table T1 ON T.document_id = T1.document_id AND T1.subject_ID = 17 
     INNER JOIN table T2 ON T.document_id = T2.document_id AND T2.subject_ID = 76    

Bien sûr, vous pouvez ajouter encore une autre INNER JOIN pour ajouter un autre sujet. Mais je reconnais que ce n'est pas une très bonne solution générale.

+0

D'oh, je cherche en effet une solution qui pourrait correspondre à un nombre arbitraire de sujets. – Maciek

0
select document_id from table1 
where subject_id in (17, 76) 
group by document_id 
having count(distinct subject_id) = 2 
2

En utilisant Oracle (ou toute base de données qui autorise la clause with). Cela permet de définir les valeurs subject_id exactement une fois.

with t as (select distinct document_id from table1 where subject_id in (17,76)) 
select document_id from table1 where subject_id in (select subject_id from t) 
group by document_id 
having count(*) = (select count (*) from t); 
+0

J'ai trouvé que cette réponse était la plus utile car elle s'applique également à PostgreSQL. – ramhiser

Questions connexes