2009-11-05 27 views
5

Tengo la relación m: n entre usuarios y etiquetas. Un usuario puede tener m etiquetas, y una etiqueta puede pertenecer a n usuarios. Tablas tienen el siguiente aspecto:SQL SELECT con la relación m: n

USER: 
ID 
USER_NAME 

USER_HAS_TAG: 
USER_ID 
TAG_ID 

TAG: 
ID 
TAG_NAME 

Digamos que necesito para seleccionar todos los usuarios, que tienen etiquetas "Apple", "naranja" Y "banana". ¿Cuál sería la forma más efectiva de lograr esto usando SQL (MySQL DB)?

Respuesta

4

Además de las otras buenas respuestas, también es posible comprobar la condición en una cláusula WHERE:

select * 
from user u 
where 3 = (
    select count(distinct t.id) 
    from user_has_tag uht 
    inner join tag t on t.id = uht.tag_id 
    where t.name in ('apple', 'orange', 'banana') 
    and uht.user_id = u.userid 
) 

El count(distinct ...) se asegura de que una etiqueta se cuente solo una vez, incluso si el usuario tiene varias etiquetas 'banana'.

Por cierto, el fruitoverflow.com sitio aún no está :)

8
SELECT u.* 
FROM (
     SELECT user_id 
     FROM tag t 
     JOIN user_has_tag uht 
     ON  uht.tag_id = t.id 
     WHERE tag_name IN ('apple', 'orange', 'banana') 
     GROUP BY 
       user_id 
     HAVING COUNT(*) = 3 
     ) q 
JOIN user u 
ON  u.id = q.user_id 

Al eliminar HAVING COUNT(*), se obtiene OR en lugar de AND (aunque no va a ser la forma más eficiente)

Al reemplazar 3 con 2, se obtiene usuarios que tienen exactamente dos de las tres etiquetas definidas.

Al reemplazar = 3 con >= 2, obtiene usuarios que tienen al menos dos de tres etiquetas definidas.

+0

que es seguro que no es más eficiente como será agregar todos los registros. P.ej. si ningún usuario coincide con los criterios, se realizará mucho trabajo inútil 3 selfjoin es la manera más eficiente de hacerlo – noonex

+0

'@ noonex': en un mundo real de datos (muchos usuarios, muchas etiquetas, alta cardinalidad de etiqueta de usuario) esto es un eficiente camino. 'tag_name IN (...)' es una condición sargable, agregará solo los registros con las etiquetas malignas. ¿Y si necesita hacer que la consulta coincida con las etiquetas '4' o' 20'? Con las autocombinaciones, deberá volver a escribir la estructura de la consulta, con 'GROUP BY' solo los parámetros. – Quassnoi

0
SELECT * 
FROM USER u 
INNER JOIN USER_HAS_TAG uht 
ON u.id = uht.user_id 
INNER JOIN TAG t 
ON uht.TAG_ID = t.ID 
WHERE t.TAG_NAME IN ('apple','orange','banana') 
+0

Esto no funciona – tputkonen

+0

Esto funciona, si desea que los usuarios con la etiqueta 'apple', 'orange' O 'banana', no los tres. – MarthyM

3

Puede hacerlo todo con une ...

select u.* 
from user u 

inner join user_has_tag ut1 on u.id = ut1.user_id 
inner join tag t1 on ut1.tag_id = t1.id and t1.tag_name = 'apple' 

inner join user_has_tag ut2 on u.id = ut2.user_id 
inner join tag t2 on ut2.tag_id = t2.id and t2.tag_name = 'orange' 

inner join user_has_tag ut3 on u.id = ut3.user_id 
inner join tag t3 on ut3.tag_id = t3.id and t3.tag_name = 'banana' 
+0

manera técnicamente más eficiente utilizará el tag_id apropiado y la unión automática de la tabla user_has_tag (3 veces). Pero el enfoque es correcto – noonex

Cuestiones relacionadas