Nie będę komentował, czy istnieje lepiej dopasowany schemat do tego (jest to całkiem możliwe), ale dla schematu z kolumnami name
i item
, poniższe zapytanie powinno działać. (składnia mysql)
SELECT k.name
FROM (SELECT DISTINCT name FROM sets) AS k
INNER JOIN sets i1 ON (k.name = i1.name AND i1.item = 1)
INNER JOIN sets i2 ON (k.name = i2.name AND i2.item = 3)
INNER JOIN sets i3 ON (k.name = i3.name AND i3.item = 5)
LEFT JOIN sets ix ON (k.name = ix.name AND ix.item NOT IN (1, 3, 5))
WHERE ix.name IS NULL;
Chodzi o to, że mamy wszystkie ustawione klucze w k
, które następnie łączymy z danymi pozycji zestawu w sets
raz na każdy element zestawu w zestawie, którego szukamy, w tym przypadku trzy. Każde z trzech sprzężeń wewnętrznych z aliasami tabeli i1
, i2
i i3
odfiltrować wszystkie nazwy zestawów, które nie zawierają elementu szukanego za pomocą tego sprzężenia. Na koniec mamy połączenie lewe z sets
z aliasem tabeli ix
, który zawiera wszystkie dodatkowe przedmioty w zestawie, czyli każdy przedmiot, którego nie szukaliśmy. ix.name
jest NULL
w przypadku, gdy nie znaleziono żadnych dodatkowych elementów, co jest dokładnie tym, czego chcemy, stąd WHERE
klauzula. Zapytanie zwraca wiersz zawierający klucz zestawu, jeśli zestaw zostanie znaleziony, w przeciwnym razie żadnych wierszy.
Edytuj: Pomysł za odpowiedzią collapsars wydaje się być znacznie lepszy niż mój, więc oto nieco krótsza wersja tego z wyjaśnieniem.
SELECT sets.name
FROM sets
LEFT JOIN (
SELECT DISTINCT name
FROM sets
WHERE item NOT IN (1, 3, 5)
) s1
ON (sets.name = s1.name)
WHERE s1.name IS NULL
GROUP BY sets.name
HAVING COUNT(sets.item) = 3;
Pomysł polega na tym, że podzapytanie s1
wybiera klucze wszystkich zestawów zawierających elementy inne niż te, których szukamy. Tak więc, kiedy wyszliśmy, dołączamy do sets
z s1
, s1.name
jest NULL
gdy zestaw zawiera tylko przedmioty, których szukamy. Następnie grupujemy według klucza zestawu i odfiltrowujemy wszystkie zestawy zawierające niewłaściwą liczbę elementów. Pozostają nam wtedy tylko zestawy, które zawierają tylko szukane przez nas przedmioty i mają odpowiednią długość. Ponieważ zestawy mogą zawierać element tylko raz, może istnieć tylko jeden zestaw spełniający te kryteria i to jest ten, którego szukamy.
Edytuj: Właśnie dotarło do mnie, jak to zrobić bez wykluczenia.
SELECT totals.name
FROM (
SELECT name, COUNT(*) count
FROM sets
GROUP BY name
) totals
INNER JOIN (
SELECT name, COUNT(*) count
FROM sets
WHERE item IN (1, 3, 5)
GROUP BY name
) matches
ON (totals.name = matches.name)
WHERE totals.count = 3 AND matches.count = 3;
Pierwsze podzapytanie znajduje całkowitą liczbę elementów w każdym zestawie, a drugie — liczbę pasujących elementów w każdym zestawie. Kiedy matches.count
wynosi 3, zestaw zawiera wszystkie elementy, których szukamy, a jeśli totals.count
ma również 3, zestaw nie zawiera żadnych dodatkowych elementów.