SQL che seleziona le righe in cui il valore di una colonna è comune in un'altra colonna di criteri
-
05-07-2019 - |
Domanda
Ho una tabella di riferimenti incrociati che assomiglia a questa:
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
Abbina i documenti ai soggetti. I documenti possono essere membri di più di un argomento. Voglio restituire righe da questa tabella in cui un determinato documento corrisponde a tutti gli argomenti di un determinato set. Ad esempio, dato l'insieme dei soggetti:
(17,76)
Voglio restituire solo le righe per i documenti che corrispondono a tutti gli argomenti di quell'insieme (almeno) da qualche parte nella tabella dei riferimenti incrociati. Il set di output desiderato dato il set sopra sarebbe:
id document_id subject_id
2 5 17
3 5 76
5 9 17
6 9 76
Si noti che l'ultima riga della tabella non viene restituita perché quel documento corrisponde solo a uno degli argomenti richiesti.
Qual è il modo più semplice ed efficiente per eseguire una query in SQL?
Soluzione
Suppongo che la chiave natrual di questa tabella sia document_id + subject_id e che id sia un surrogato; IOW, document_id e subject_id sono unici. In quanto tale, farò solo finta che non esista e che un vincolo unico sia sulla chiave naturale.
Cominciamo con l'ovvio.
SELECT document_id, subject_id
FROM document_subjects
WHERE subject_id IN (17,76)
Questo ti dà tutto ciò che vuoi più cose che non vuoi. Quindi tutto ciò che dobbiamo fare è filtrare le altre cose. Le "altre cose" sono gruppi di righe con un conteggio non uguale al conteggio dei soggetti desiderati.
SELECT document_id
FROM document_subjects
WHERE subject_id IN (17,76)
GROUP BY document_id
HAVING COUNT(*) = 2
Nota che subject_id viene rimosso perché non partecipa al raggruppamento. Facendo un ulteriore passo avanti, aggiungerò una tabella immaginaria chiamata subject_i_want che contiene N file di argomenti che desideri.
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)
Ovviamente subject_i_want potrebbe essere sostituito con un'altra subquery, tabella temporanea o altro. Ma, una volta che hai questo elenco di document_id, puoi usarlo all'interno di una sottoselezione di una query più 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))
O qualunque altra cosa.
Altri suggerimenti
Uso di Oracle (o di qualsiasi database che consenta la clausola with). Ciò consente la definizione dei valori subject_id esattamente una volta.
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);
Questa è una domanda molto interessante.
Suppongo che vorresti una domanda più generalizzata, ma questo è quello che farei nel caso in cui tu abbia sempre lo stesso numero di argomenti (diciamo due):
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
Ovviamente, potresti aggiungere un altro INNER JOIN per aggiungere un altro ID soggetto. Ma ammetto che non è un'ottima soluzione generale.
select document_id from table1
where subject_id in (17, 76)
group by document_id
having count(distinct subject_id) = 2