2012-04-30 16 views
6

Tengo tres tablas MySQL - fotos, etiquetas y fotos - y una relación m: n entre fotos y etiquetas.Seleccionar fotos por etiquetas múltiples

Photos:  id | filename | ... 
Tags:  id | name 
Tagsphotos: photo | tag 

Quiero seleccionar todas las fotos con esta condición:

(tagged as "dirty" AND tagged as "road") AND (tagged as "light.front" OR tagged as "light.side") AND (tagged as "perspective.two-point") 

... lo que significa que quiero encontrar todas las imágenes con camino de tierra, en perspectiva de dos puntos y ya sea con el lado o Luz delantera.

¿Cómo puedo hacerlo? Gracias.

Respuesta

3

creo que vas a tener que unirse a la tabla de etiquetas a la mesa de fotos cuatro veces ... bastante feas.

SELECT Photos.* 
FROM 
    Photos 
    JOIN (
    Tagsphotos JOIN Tags ON (Tags.id = Tagsphotos.tag) 
) t1 ON (t1.photo = Photos.id) 
    JOIN (
    Tagsphotos JOIN Tags ON (Tags.id = Tagsphotos.tag) 
) t2 ON (t2.photo = Photos.id) 
    JOIN (
    Tagsphotos JOIN Tags ON (Tags.id = Tagsphotos.tag) 
) t3 ON (t3.photo = Photos.id) 
    JOIN (
    Tagsphotos JOIN Tags ON (Tags.id = Tagsphotos.tag) 
) t4 ON (t4.photo = Photos.id) 
WHERE 
     (t1.name = 'dirty' AND t2.name = 'road') 
    AND (t3.name = 'light.front' OR t3.name = 'light.side') 
    AND (t4.name = 'perspective.two-point') 

subconsultas probablemente sería más rápido:

SELECT * 
FROM Photos 
WHERE 
    Photos.id IN (
    SELECT Tagspohotos.photo 
    FROM Tagsphotos JOIN Tags ON (Tags.id = Tagsphotos.tag) 
    WHERE Tags.name = 'dirty' 
) 
    AND Photos.id IN (
    SELECT Tagspohotos.photo 
    FROM Tagsphotos JOIN Tags ON (Tags.id = Tagsphotos.tag) 
    WHERE Tags.name = 'road' 
) 
    AND Photos.id IN (
    SELECT Tagspohotos.photo 
    FROM Tagsphotos JOIN Tags ON (Tags.id = Tagsphotos.tag) 
    WHERE Tags.name = 'light.front' OR Tags.name = 'light.side' 
) 
    AND Photos.id IN (
    SELECT Tagspohotos.photo 
    FROM Tagsphotos JOIN Tags ON (Tags.id = Tagsphotos.tag) 
    WHERE Tags.name = 'perspective.two-point' 
) 
+0

Gracias. La solución con subconsultas funciona bien. –

0

Suponiendo columnas de foto y etiqueta son los identificadores:

select p.* from Photos p, Tags t, Tagsphotos tp 
where p.id = tp.photo and t.id = tp.tag 
and t.tagged in ("dirty", "road", "perspective.two-point", "light.front") 
group by p.photo 
having count(distinct t.tagged) = 4 

UNION 

select p.* from Photos p, Tags t, Tagsphotos tp 
where p.id = tp.photo and t.id = tp.tag 
and t.tagged in ("dirty", "road", "perspective.two-point", "light.side") 
group by p.photo 
having count(distinct t.tagged) = 4 
+1

'DONDE t.tagged = x Y t.tagged = y ¿? ¿Cómo devolverá eso los resultados si 'x <> y'? – eggyal

+1

Esta fue mi primera idea y, por supuesto, no funciona. –

+0

¡De hecho! Lo cambiaré :) –

1

Disculpas, no me di cuenta que estaba usando MySQL - actualizado respuesta dada a continuación.

Suponiendo que cada etiqueta sólo se puede especificar una vez para cada foto (es decir, una foto no puede ser etiquetado como 'sucio' múltiples veces):

SELECT  P.id, 
      P.[filename] 
FROM  Photos P 
INNER JOIN Tagsphotos TP ON TP.photo = P.id 
INNER JOIN Tags T ON TP.tag = T.id 
INNER JOIN (
     SELECT 'dirty' name, 
       10 [weight] 
     UNION 
     SELECT 'road' name, 
       10 [weight] 
     UNION 
     SELECT 'perspective.two-point' name, 
       10 [weight] 
     UNION 
     SELECT 'light.front' name, 
       1 [weight] 
     UNION 
     SELECT 'light.side' name, 
       1 [weight] 
) R ON T.name = R.name 
GROUP BY P.id, 
      P.[filename] 
HAVING SUM(R.[weight]) >= 31 
+0

Actualizado. Se eliminó CTE. – weenoid

+0

Me encanta esto. +1 – eggyal

Cuestiones relacionadas