2009-11-05 14 views
5

Mam relację m: n między użytkownikami i tagami. Jeden użytkownik może mieć m tagów, a jeden tag może należeć do n użytkowników. Tabele wyglądać tak:SQL SELECT o relacji m: n

USER: 
ID 
USER_NAME 

USER_HAS_TAG: 
USER_ID 
TAG_ID 

TAG: 
ID 
TAG_NAME 

Powiedzmy, że muszę wybrać wszystkich użytkowników, którzy mają znaczniki „Apple”, „Orange” AND „banana”. Jaki byłby najskuteczniejszy sposób osiągnięcia tego przy użyciu SQL (MySQL DB)?

Odpowiedz

4

W uzupełnieniu do innych dobrych odpowiedzi, jest to również możliwe, aby sprawdzić stan w klauzuli 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 
) 

Dzięki count(distinct ...) tag jest liczony tylko raz, nawet jeśli użytkownik ma wiele tagów bananowych.

Nawiasem mówiąc, witryna fruitoverflow.com nie jest jeszcze zarejestrowana :)

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 

Usuwając HAVING COUNT(*), masz OR zamiast AND (choć nie będzie to najbardziej efektywny sposób)

Zastępując 3 z 2, masz użytkowników, którzy mają dokładnie dwa z trzech tagi zdefiniowane.

Po zastąpieniu = 3 przez >= 2 otrzymasz użytkowników, którzy mają zdefiniowane co najmniej dwa z trzech znaczników.

+0

, która z pewnością nie jest najskuteczniejsza, ponieważ zgromadzi wszystkie rekordy. Na przykład. jeśli żaden użytkownik nie spełnia kryteriów, wiele bezużytecznej pracy zostanie wykonanych 3 selfjoin to skuteczny sposób na przejście na – noonex

+0

'@ noonex': na danych rzeczywistych (wielu użytkowników, dużo tagów, duża liczność użytkowników) jest to efektywne droga. 'tag_name IN (...)' jest godnym pożałowania warunkiem, zagreguje tylko rekordy z tagami matematycznymi. A co, jeśli chcesz, aby zapytanie pasowało do znaczników '4' lub' 20'? Przy self-joinach będziesz musiał przepisać strukturę zapytania, używając 'GROUP BY' tylko parametrów. – 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

To nie działa – tputkonen

+0

Działa to, jeśli chcesz, aby użytkownicy z tagiem "jabłko", "pomarańczowy" lub "banan", nie wszystkie trzy. – MarthyM

3

Można zrobić to wszystko z łączy ...

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

technicznie bardziej efektywnym sposobem będzie użycie odpowiedniego tabele tag_id i selfjoin only user_has_tag (3 razy). Ale podejście jest poprawne – noonex

Powiązane problemy