Sólo para proponer un enfoque alternativo, yo lo haría con las funciones JSON de mysql. Sean tags_array
un campo de tipo JSON (específicamente, un array json) proveniente de una subconsulta, y array_buscado
un arreglo de ids que queremos filtrar.
La función JSON_CONTAINS(tags_array, array_buscado)
retorna verdadero cuando todos los elementos de array_buscado
están presentes en tags_array
.
Poblando la BBDD con canciones y tags, tal que
SELECT song_name,
song_id,
CONCAT('[', GROUP_CONCAT(tag_id), ']') tag_ids,
CAST(JSON_ARRAYAGG(tag_name) as JSON) tagnames
FROM songs
JOIN tag_song USING (song_id)
JOIN tags USING (tag_id)
GROUP BY song_name,song_id
Se vea como:
song_name |
song_id |
tag_ids |
tagnames |
despacito |
4 |
[2] |
["español"] |
muñeca de trapo |
2 |
[1,2] |
["triste","español"] |
penelope |
3 |
[1,2,3] |
["triste","español","clásica"] |
Sultans of swing |
5 |
[3,4] |
["clásica","anglo"] |
yesterday |
1 |
[1,3,4] |
["triste","clásica","anglo"] |
Entonces:
WITH tagged AS (
SELECT song_name,
song_id,
concat('[', GROUP_CONCAT(tag_id), ']') tag_ids,
cast(JSON_ARRAYAGG(tag_name) as JSON) tagnames
FROM songs
JOIN tag_song USING (song_id)
JOIN tags USING (tag_id)
GROUP BY song_name,song_id
)
SELECT * from tagged WHERE JSON_CONTAINS(tagnames,'["clásica","triste"]')
Mostrará solamente yesterday y penélope (las únicas clásicas Y tristes en la lista).
Si quisieras aplicar el equivalente a un OR
, y estuviésemos en MySQL 8, la función JSON_OVERLAPS
listaría todas las canciones donde al menos un elemento está entre los buscados. O sea, se habría listado además "Sultans of Swing" porque cumple con al menos un elemento (anglo).
Lo que me gusta de la sintaxis es que podrías encapsular la subquery en una vista, y buscar en ella directamente, sin tener que hacer la búsqueda y luego la agregación una y otra vez, cosa que puede ser ineficiente si la agregación es costosa. Pasar de un conjunto de AND a uno de OR sólo afecta a una función. No hay que añadir ni quitar cláusulas, sino que todo se delega mediante el argumento.
obviamente también se puede buscar por ID
SELECT * FROM tagged WHERE JSON_CONTAINS(tag_ids,'[3,1]')
pero quise mostrar la coincidencia por slugs porque tu capa de negocios recibirá o al menos podrá computar los slugs, mientras que los ID son desconocidos en esa parte del pipeline.
dejo un fiddle funcionando
PD: Por supuesto el orden de los elementos es irrelevante. Buscar ["triste","clásico"]
es igual a ["clásico","triste"]