Existem duas maneiras de fazer isso. Eu prefiro a primeira maneira, que é auto-juntar para cada tag:
SELECT l.*
FROM Locations l
JOIN LocationsTagsAssoc a1 ON a1.LocationID = l.ID
JOIN Tags t1 ON a1.TagID = t1.ID AND t1.Name = ?
JOIN LocationsTagsAssoc a2 ON a2.LocationID = l.ID
JOIN Tags t2 ON a2.TagID = t2.ID AND t2.Name = ?
JOIN LocationsTagsAssoc a3 ON a3.LocationID = l.ID
JOIN Tags t3 ON a3.TagID = t3.ID AND t3.Name = ?;
A outra maneira também funciona, mas usando
GROUP BY
no MySQL tende a incorrer em uma tabela temporária e o desempenho é lento:SELECT l.*
FROM Locations l
JOIN LocationsTagsAssoc a ON a.LocationID = l.ID
JOIN Tags t ON a.TagID = t.ID
WHERE t.Name IN (?, ?, ?)
GROUP BY l.ID
HAVING COUNT(*) = 3;
Comentário de @Erikoenig:
Se você quiser ter certeza de que não há tags extras, você pode fazer desta forma:
SELECT l.*
FROM Locations l
JOIN LocationsTagsAssoc a ON a.LocationID = l.ID
JOIN Tags t ON a.TagID = t.ID
GROUP BY l.ID
HAVING COUNT(*) = 3 AND SUM(t.Name IN (?, ?, ?)) = 3;
Retirar a cláusula WHERE permite que outras tags sejam contadas, se houver. Portanto, COUNT() pode ser maior que 3.
Ou se a contagem for exatamente três tags, mas algumas dessas três não forem as tags corretas, a condição SUM() na cláusula HAVING garante que todas as três tags desejadas estejam presentes no grupo.