2009-11-05 12 views
5

Ho m: n relazione tra utenti e tag. Un utente può avere m tag e un tag può appartenere a n utenti. Le tabelle sono qualcosa del tipo:SQL SELECT con m: n relazione

USER: 
ID 
USER_NAME 

USER_HAS_TAG: 
USER_ID 
TAG_ID 

TAG: 
ID 
TAG_NAME 

Diciamo che ho bisogno di selezionare tutti gli utenti, che hanno tag "mela", "Orange" e "banana". Quale sarebbe il modo più efficace per farlo usando SQL (MySQL DB)?

risposta

4

In aggiunta alle altre buone risposte, è anche possibile controllare lo stato in una clausola 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 
) 

count(distinct ...) assicura che un tag venga conteggiato una sola volta, anche se l'utente ha più tag "banana".

Tra l'altro, il sito fruitoverflow.com non ancora registrati :)

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 

Rimuovendo HAVING COUNT(*), si ottiene OR invece di AND (anche se non sarà il modo più efficiente)

Sostituendo 3 con 2, si ottiene gli utenti che hanno esattamente due delle tre tag definiti.

Sostituendo = 3 con >= 2, si ottengono utenti che hanno almeno due di tre tag definiti.

+0

che è di sicuro non è più efficace come sarà aggregare tutti i record. Per esempio. se nessun utente corrisponde ai criteri, molto lavoro inutile sarà fatto 3 selfjoin è il modo efficace per andare – noonex

+0

'@ noonex': su un dato di mondo reale (un sacco di utenti, molti tag, cardinalità di tag utente alta) questo è un efficiente modo. 'tag_name IN (...)' è una condizione di sargable, aggregherà solo i record con i tag di matematica. E se fosse necessario far corrispondere la query ai tag '4' o' 20'? Con self-join, è necessario riscrivere la struttura della query, con 'GROUP BY' solo i parametri. – 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

Questo non funziona – tputkonen

+0

Questo funziona, se vuoi utenti con tag 'apple', 'orange' OR 'banana', non tutti e tre. – MarthyM

3

Si può fare tutto con unisce ...

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

tecnicamente il modo più efficiente utilizzerà tag_id appropriato e selfjoin solo user_has_tag table (3 volte). Ma l'approccio è corretto – noonex

Problemi correlati