Não comentarei se existe um esquema mais adequado para fazer isso (é bem possível), mas para um esquema com colunas
name
e item
, a consulta a seguir deve funcionar. (sintaxe 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;
A ideia é que tenhamos todas as chaves definidas em
k
, que então juntamos com os dados do item do conjunto em sets
uma vez para cada item do conjunto que estamos procurando, três neste caso. Cada uma das três associações internas com aliases de tabela i1
, i2
e i3
filtre todos os nomes de conjuntos que não contenham o item pesquisado com essa junção. Finalmente, temos uma junção esquerda com sets
com alias de tabela ix
, que traz todos os itens extras do conjunto, ou seja, todos os itens que não estávamos procurando. ix.name
é NULL
no caso de nenhum item extra ser encontrado, que é exatamente o que queremos, portanto, o WHERE
cláusula. A consulta retorna uma linha contendo a chave do conjunto se o conjunto for encontrado, caso contrário, nenhuma linha. Editar: A ideia por trás da resposta colapsars parece ser muito melhor que a minha, então aqui está uma versão um pouco mais curta disso com explicação.
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;
A ideia aqui é que a subconsulta
s1
seleciona as chaves de todos os conjuntos que contêm itens diferentes dos que estamos procurando. Assim, quando saímos do join sets
com s1
, s1.name
é NULL
quando o conjunto contém apenas itens que estamos procurando. Em seguida, agrupamos por chave de conjunto e filtramos quaisquer conjuntos com o número errado de itens. Ficamos então com apenas conjuntos que contêm apenas itens que estamos procurando e são do tamanho correto. Como os conjuntos só podem conter um item uma vez, só pode haver um conjunto que satisfaça esse critério, e é esse que estamos procurando. Editar: Apenas me ocorreu como fazer isso sem a exclusão.
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;
A primeira subconsulta encontra a contagem total de itens em cada conjunto e a segunda encontra a contagem de itens correspondentes em cada conjunto. Quando
matches.count
for 3, o conjunto terá todos os itens que estamos procurando e, se totals.count
também é 3, o conjunto não possui itens extras.