8

Tengo una tabla de canciones, tengo otra tabla de etiquetas y una última tabla con la relación entre las canciones y las etiquetas.

Necesito obtener toda aquella canción que contenga el tagID de una o más etiquetas, pero debe devolver sólo un resultado por canción, sin duplicados.

Siguiendo las fotos adjuntadas necesitaría obtener el registro 590 porque cumple la condición de tener las etiquetas 4 y 7. Y no recibir dos registros de 590, sólo uno.

He probado esta consulta pero me devuelve varios registros. Y cuando cambio OR por AND no me devuelve nada porque estoy comparando la misma columna. ¿Alguna solución?

SELECT * FROM tracks_tags WHERE tagId LIKE "%4%" OR tagId LIKE "%7%"

Dejo un ejemplo de mis tablas.

TABLA TRACKS

introducir la descripción de la imagen aquí

TABLA TAGS

TABLA TAGS

TABLA TRACKS_TAGS CON LA RELACIÓN DE CANCIONES Y ETIQUETA introducir la descripción de la imagen aquí

  • 1
    Con esa QUERY daría de resultado las 3 filas que aparece en la tabla de TRACKS_TAGS – DomingoMG Mar 17 '22 at 17:13
  • 1
    Te explico el porque al usar OR está dando igual si es uno o es el otro, en cambio en la publicación de Jonatan indica 4 y 7 es decir un AND pero claro un AND no se puede usar ya que es por columnas y no por filas, debería usar ```IN (4, 7)``` pero aún no sé exactamente si es lo que el busca. – DomingoMG Mar 17 '22 at 17:15
  • Puedes poner un ejemplo del resultado q esperas? – Japv Mar 17 '22 at 20:27
  • Jonatan podrias decir si las respuestas dadas te resultaron utiles, gracias – Japv Mar 29 '22 at 15:31

6 Answers6

5

ACTUALIZADO, La consulta anterior que propuse como solución estaba escrita de manera rápida y no me parecía del todo correcto.

La solución a tu problema es:

  1. Comparamos el ID de las canciones con la tabla tracks_tags
  2. TG.tagID in(4, 7) indicas los tagsId que desees comprobar simultáneamente
  3. Agrupamos el resultado por Tracks.id para evitar duplicados.
  4. Según los tags consultados devolverá 2 resultados por cada una de las canciones entonces según el tamaño del array lo agrupará.
SELECT
    *
FROM
    tracks_tags AS TG,
    tracks AS T
WHERE
    T.id = TG.trackId
    AND
    TG.tagId IN (4,7)
GROUP BY T.id
HAVING count(distinct TG.tagId) = 2; /* TAMAÑO TOTAL DEL ARRAY DE TG.tagId().... */```
DomingoMG
  • 166
  • 6
  • 1
    Sí, aunque estoy reajustando la QUERY ya que fue lo que me vino a la cabeza – DomingoMG Mar 17 '22 at 18:13
  • 1
    Actualizado, creo que es lo que busca, si hay otra solución será bienvenido, pero estoy totalmente seguro que es su solución. – DomingoMG Mar 17 '22 at 19:41
0

Analiza esta posible solución, quizá te sea útil en lo que deseas hacer:

select trackId, group_concat(cast(tagId as varchar), ', ') as Etiquetas from TRACKS_TAGS
group by trackId

La clave está en usar la función de agregación group_concat y la cláusula group by, de esta forma mostrarás todas etiquetas que corresponden a una misma canción, separando las etiquetas por el caracter ','. Obtendrías algo como esto:

introducir la descripción de la imagen aquí

Japv
  • 9,317
  • 8
  • 11
  • 29
0

Si quieres las canciones que tengan las dos etiquetas, la suma (agregación condicional) de etiquetas buscadas debe ser 2, suponiendo que las etiquetas no se repiten para cada canción.

SELECT tt.trackId
     , t.title
  FROM tracks_tags tt
    JOIN tracks t
      ON t.id = tt.trackId
  GROUP BY tt.trackId
         , t.title
  HAVING sum( tagId in(4,7) ) = 2

Para tres etiquetas cambiaría:

  HAVING sum( tagId in(4,7,9) ) = 3
Sal
  • 6,626
  • 1
  • 7
  • 17
0

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"]

ffflabs
  • 21,223
  • 25
  • 48
0

Para obtener las canciones que tienen el tag 4 y 7, podrías hacer 2 INNER JOIN (uno por cada tag)

Ejemplo:

SELECT T.* 
    FROM tracks AS T

    INNER JOIN tracks_tags AS TT1
        ON TT1.trackId = T.id
        AND TT1.tag_id = 4

    INNER JOIN tracks_tags AS TT2
        ON TT2.trackId = T.id
        AND TT2.tag_id = 7
Marcos
  • 30,626
  • 6
  • 28
  • 63
-1

Si necesitas obtener todas las canciones que tengan al menos una etiqueta, esto te debería funcionar:

SELECT distinct T.artist,T.title FROM TRACKS T join TRACKS_TAGS TT on T.id = TT.trackId;
  • Por qué este código resuelve la pregunta? Procura siempre incluir una breve descripción de lo que haces diferente, por qué y cómo usarlo – Alfabravo Mar 17 '22 at 19:50
  • @Javier El punto y coma se necesita según el contexto; en ésta respuesta ni abona ni quita. – Sal Mar 18 '22 at 02:08