Jeg vil ikke kommentere på, om der er et bedre egnet skema til at gøre dette (det er meget muligt), men for et skema med kolonner name
og item
, bør følgende forespørgsel fungere. (mysql-syntaks)
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;
Ideen er, at vi har alle de indstillede nøgler i k
, som vi så forbinder med de indstillede varedata i sets
én gang for hvert sæt i det sæt, vi søger efter, tre i dette tilfælde. Hver af de tre indre joinforbindelser med tabelaliasser i1
, i2
og i3
filtrere alle sætnavne fra, der ikke indeholder det element, der blev søgt efter med det pågældende join. Endelig har vi en venstre join med sets
med tabelalias ix
, som bringer alle de ekstra varer ind i sættet, det vil sige alle de ting, vi ikke ledte efter. ix.name
er NULL
i tilfælde af, at der ikke findes ekstra genstande, hvilket er præcis det, vi ønsker, således WHERE
klausul. Forespørgslen returnerer en række, der indeholder sætnøglen, hvis sættet findes, ellers ingen rækker.
Rediger: Ideen bag collapsars svar ser ud til at være meget bedre end mit, så her er en lidt kortere version af det med forklaring.
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;
Ideen her er, at underforespørgsel s1
vælger nøglerne for alle sæt, der indeholder andre elementer end dem, vi leder efter. Så da vi forlod join sets
med s1
, s1.name
er NULL
når sættet kun indeholder elementer, vi søger efter. Vi grupperer derefter efter sætnøgle og filtrerer alle sæt fra med det forkerte antal varer. Vi står så tilbage med kun sæt, som kun indeholder varer, vi søger efter, og som har den rigtige længde. Da sæt kun kan indeholde et element én gang, kan der kun være ét sæt, der opfylder disse kriterier, og det er det, vi leder efter.
Rediger: Det gik lige op for mig, hvordan man gør dette uden udelukkelsen.
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;
Den første underforespørgsel finder det samlede antal elementer i hvert sæt, og den anden finder ud af antallet af matchende elementer i hvert sæt. Når matches.count
er 3, har sættet alle de varer, vi leder efter, og hvis totals.count
er også 3, har sættet ingen ekstra genstande.