2009-09-10 45 views
12

Mam tabelę krzyżową odniesienia, który wygląda tak:SQL wybierając wiersze gdzie wartość jednej kolumny jest wspólne całej innej kolumnie kryteria

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 

pasuje dokumentów poddanych. Dokumenty mogą być członkami więcej niż jednego przedmiotu. Chcę zwrócić wiersze z tej tabeli, w których dany dokument pasuje do obiektów w danym zestawie. Na przykład, biorąc pod uwagę zestaw przedmiotów:

(17,76)

Chcę wrócić tylko wiersze dla dokumentów, które pasują do wszystkich przedmiotów w tym zestawie (co najmniej) gdzieś w przekroju tabeli odniesienia. Żądany zestaw wyjściowy podany wyżej zestaw będzie:

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

Zauważ, że ostatni wiersz tabeli nie jest zwracana tylko dlatego, że dokument pasuje do jednego z wymaganych przedmiotów.

Jaki jest najprostszy i najskuteczniejszy sposób na zapytanie o to w SQL?

+0

Byłoby świetnie wiedzieć, w jaki sposób podajesz parametry kwerendy. Widzę jedną odpowiedź, natomiast idealnie w porządku, działa ona tylko dla dokładnie 2 wartości w zestawie parametrów. Jeśli możesz ograniczyć liczbę parametrów, powiedzmy 10 maks., To jedna rozmowa. Jeśli chcesz, aby aplikacja była elastyczna, sugestie będą inne. – Eugene

+0

Dzięki, dane wejściowe są w zasadzie "wybierz dowolną liczbę tematów", aby zbiór tematów identyfikacyjnych mógł rosnąć tak dużą, jak liczba przedmiotów (teoretycznie). – Maciek

Odpowiedz

27

Zakładam, że natrual kluczem tej tabeli jest DOCUMENT_ID + subject_id i że id jest zastępczym; IOW, id_dokumentu i subject_id są unikalne. W związku z tym zamierzam udawać, że nie istnieje i że wyjątkowe ograniczenie dotyczy naturalnego klucza.

Zacznijmy od oczywistości.

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

To dostaje wszystko, co chcesz plusa rzeczy, których nie chcesz. Więc wszystko, co musimy zrobić, to odfiltrować inne rzeczy. "Innymi rzeczami" są grupy wierszy mające liczbę, która nie jest równa liczbie pożądanych przedmiotów.

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

Uwaga: identyfikator podmiotu jest usuwany, ponieważ nie uczestniczy w grupowaniu. Idąc dalej, dodam wyimaginowaną tabelę o nazwie subject_i_want, która zawiera N wierszy tematów, które chcesz.

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) 

Oczywiście subject_i_want można zamienić na inne podkwerendy, tabelę tymczasową lub cokolwiek innego. Ale gdy już masz tę listę id_dokumentu, możesz jej użyć w ramach podwydziału większego zapytania.

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

Cokolwiek.

+0

Niesamowite, dzięki. – Maciek

+1

+1 bardzo ładne, Alex. Ostatnio zauważyłem kilka odmian tego pytania i jest to najbardziej przejrzyste ogólne rozwiązanie, jakie dotychczas widziałem. – Matt

+0

+1, bardzo nce i pomógł mi, byłoby lepiej, gdyby liczba (*) wykonana i Posiadanie byłby na odrębnych wpisów, ponieważ mogłoby to wyeliminować możliwość uwzględnienia duplikatów danych; najlepiej COUNT (DISTINCT subject_id) zamiast COUNT (*) –

1

To bardzo interesujące pytanie.

Jestem zakładając chcesz bardziej uogólniony zapytanie, ale to jest to, co chciałbym robić w przypadku, w którym zawsze mają taką samą liczbę osobników (słownie dwa):

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    

Oczywiście, możesz dodać jeszcze INNER JOIN, aby dodać kolejny identyfikator podmiotu. Ale przyznaję, że nie jest to bardzo dobre ogólne rozwiązanie.

+0

D'oh, naprawdę szukam rozwiązania, które mogłoby dopasować dowolną liczbę przedmiotów. – Maciek

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

Używanie Oracle (lub dowolnej bazy danych, która zezwala na klauzulę with). Pozwala to na zdefiniowanie wartości subject_id dokładnie jeden raz.

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

Uważam, że ta odpowiedź jest najbardziej pomocna, ponieważ dotyczy również PostgreSQL. – ramhiser