2012-01-16 20 views
6

Actualmente estoy escribiendo una aplicación que permite almacenar imágenes y luego etiquetarlas. Estoy usando Python y el Peewee ORM (http://charlesleifer.com/docs/peewee/), que es muy similar al ORM de Django.Búsqueda de artículos en una relación muchos a muchos

Mi modelo de datos es el siguiente (simplificado):

class Image(BaseModel): 
    key = CharField() 

class Tag(BaseModel): 
    tag = CharField() 

class TagRelationship(BaseModel): 
    relImage = ForeignKeyField(Image) 
    relTag = ForeignKeyField(Tag) 

Ahora, entiendo conceptualmente cómo consultar todas las imágenes que tienen un determinado conjunto de etiquetas:

SELECT Image.key 
    FROM Image 
INNER JOIN TagRelationship 
    ON Image.ID = TagRelationship.ImageID 
INNER JOIN Tag 
    ON TagRelationship.TagID = Tag.ID 
WHERE Tag.tag 
     IN ('A' , 'B')  -- list of multiple tags 
GROUP BY Image.key 
HAVING COUNT(*) = 2   -- where 2 == the number of tags specified, above 

Sin embargo, también quiero poder hacer búsquedas más complejas. Específicamente, me gustaría poder especificar una lista de "todas las etiquetas", es decir, una imagen debe tener todas las etiquetas especificadas para ser devueltas, junto con una lista de "cualquiera" y una lista de "ninguna".

EDIT: Me gustaría aclarar esto un poco. Específicamente, la consulta anterior es una consulta al estilo de "todas las etiquetas". Devuelve imágenes que tienen todas las etiquetas dadas. Quiero poder especificar algo como: "Dame todas las imágenes que tengan las etiquetas (verde, montaña), cualquiera de las etiquetas (fondo, paisaje) pero no las etiquetas (digital, dibujo)".

Ahora, idealmente, me gustaría que esto sea una consulta SQL, porque la paginación se vuelve muy fácil con LIMIT y OFFSET. De hecho, tengo una implementación en funcionamiento por la cual solo cargo todo en los conjuntos de Python y luego uso los diversos operadores de intersección. Lo que me pregunto es si hay un método para hacer esto de una vez.

Además, para los interesados, he escrito el autor de Peewee acerca de cómo representar la consulta anterior usando Peewee, y él respondió con la siguiente solución:

Image.select(['key']).group_by('key').join(TagRelationship).join(Tag).where(tag__in=['tag1', 'tag2']).having('count(*) = 2') 

O, de forma alternativa, una versión más corta :

Image.filter(tagrelationship_set__relTag__tag__in=['tag1', 'tag2']).group_by(Image).having('count(*) = 2') 

Gracias de antemano por su tiempo.

+0

Si entiendo correctamente, usted tiene la respuesta que necesita en el código peewee, pero ¿desea saber cómo hacer lo mismo en sql recto? –

+0

puede explicar la parte "Sin embargo, también deseo poder realizar búsquedas más complejas. Específicamente, me gustaría poder especificar una lista de" todas las etiquetas ", es decir, una imagen debe tener todas las especificaciones etiquetas que se devolverán, junto con una lista de "cualquiera" y una lista de "ninguno". " – naresh

+0

@naresh Específicamente, la consulta anterior es una consulta al estilo de "todas las etiquetas". Devuelve imágenes que tienen todas las etiquetas dadas. Quiero poder especificar algo como: "Dame todas las imágenes que tengan las etiquetas (verde, montaña), cualquiera de las etiquetas (fondo, paisaje) pero no las etiquetas (digital, dibujo)". Háganme saber si eso no está claro. –

Respuesta

4
SELECT Image.key 
    FROM Image 
    JOIN TagRelationship 
    ON Image.ID = TagRelationship.ImageID 
    JOIN Tag 
    ON TagRelationship.TagID = Tag.ID 
GROUP BY Image.key 
HAVING SUM(Tag.tag IN (mandatory tags)) = N /*the number of mandatory tags*/ 
    AND SUM(Tag.tag IN (optional tags )) > 0 
    AND SUM(Tag.tag IN (prohibited tags)) = 0 

ACTUALIZACIÓN

Una versión más universalmente aceptado de la consulta anterior (convierte los resultados booleano de la en los predicados en enteros utilizando expresiones CASE):

SELECT Image.key 
    FROM Image 
    JOIN TagRelationship 
    ON Image.ID = TagRelationship.ImageID 
    JOIN Tag 
    ON TagRelationship.TagID = Tag.ID 
GROUP BY Image.key 
HAVING SUM(CASE WHEN Tag.tag IN (mandatory tags) THEN 1 ELSE 0 END) = N /*the number of mandatory tags*/ 
    AND SUM(CASE WHEN Tag.tag IN (optional tags ) THEN 1 ELSE 0 END) > 0 
    AND SUM(CASE WHEN Tag.tag IN (prohibited tags) THEN 1 ELSE 0 END) = 0 

o con COUNTs en lugar de SUM:

SELECT Image.key 
    FROM Image 
    JOIN TagRelationship 
    ON Image.ID = TagRelationship.ImageID 
    JOIN Tag 
    ON TagRelationship.TagID = Tag.ID 
GROUP BY Image.key 
HAVING COUNT(CASE WHEN Tag.tag IN (mandatory tags) THEN 1 END) = N /*the number of mandatory tags*/ 
    AND COUNT(CASE WHEN Tag.tag IN (optional tags ) THEN 1 END) > 0 
    AND COUNT(CASE WHEN Tag.tag IN (prohibited tags) THEN 1 END) = 0 
+0

¿Funcionará correctamente la sentencia de verificación? Como haces un GROUP BY, solo tendrás 1 valor para la etiqueta. Dado que está dentro de SUM, ¿iterará sobre todas las etiquetas y preformará la verificación IN? Esto es mucho más elegante que el mío si funciona. – JustinDanielson

+0

Pensé que después de agruparlo por cualquier atributo, no podría preformar el análisis en filas individuales dentro de grupos como Tag.tag IN (etiquetas obligatorias)) N. Podría tener que configurar algunas tablas y probar esta consulta por mí mismo. Sé que las operaciones agregadas, como SUM, funcionarán, pero no sé si Tag.tag IN (etiquetas obligatorias) = ​​N funcionará. No porque dude de ti, sino porque no he visto o hecho esto. Es nuevo para mí – JustinDanielson

+1

@JustinDanielson: Sí, puede tener expresiones que hagan referencia a columnas que no sean GROUP BY dentro de funciones agregadas, si es eso lo que pregunta. Los resultados de las expresiones se agregan en consecuencia. Sabes que puedes 'SUMAR (Valor)', pero de la misma manera puedes 'SUMAR (Valor * Cantidad)' o 'SUMAR (Valor> 10)'.En el sabor MySQL de SQL, que es mi consulta, 'IN' se puede ver como otro operador, como' * 'o' + ', solo devuelve un booleano (que se convierte implícitamente en un entero), como' < 'o' = 'también. –

0

siguiente consulta debe devolver todas las imágenes que estén etiquetadas tanto ('A' y 'B') y ('C' o 'D'), pero no 'E' y 'F'

SELECT Image.key 
FROM Image 
INNER JOIN TagRelationship 
    ON Image.ID = TagRelationship.ImageID 
INNER JOIN Tag tag1 
    ON TagRelationship.TagID = tag1.ID 
INNER JOIN Tag tag2 
    ON TagRelationship.TagID = tag2.ID 
WHERE tag1.tag 
    IN ('A' , 'B') 
AND tag2.tag NOT IN ('E', 'F') 

GROUP BY Image.key 
HAVING COUNT(*) = 2 

UNION 

SELECT Image.key 
FROM Image 
INNER JOIN TagRelationship 
    ON Image.ID = TagRelationship.ImageID 
INNER JOIN Tag tag1 
    ON TagRelationship.TagID = tag1.ID 
INNER JOIN Tag tag2 
    ON TagRelationship.TagID = tag2.ID 
WHERE tag1.tag 
    IN ('C' , 'D') 
AND tag2.tag NOT IN ('E', 'F') 
+0

Si 'Tag.tag' está en' ('A', 'B') ', nunca se puede encontrar en' ('E', 'F') '. Ambas condiciones 'NOT IN' parecen redundantes en su consulta particular. –

+0

@AndriyM gracias por el comentario .. He corregido la consulta – naresh

+0

Si quiere más de 2 etiquetas, tendrá que hacer otra combinación para tag3. Esto se saldrá rápidamente de control y muy lento si quiere muchas etiquetas. – JustinDanielson

2

La mitad superior recibe las palabras que coinciden con las etiquetas obligatorias. La mitad inferior muestra las etiquetas donde al menos 1 debe estar presente. La consulta inferior no tiene un GROUP BY porque quiero saber si una imagen aparece dos veces. Si lo hace, tiene fondo y paisaje. El recuento ORDER BY (*) hará que las imágenes con AMBAS etiquetas de fondo y paisaje aparezcan en la parte superior. Así que verde, montaña, paisaje de fondo será el más relevante. Luego, verde, montaña, fondo O imágenes de paisajes.

SELECT Image.key, count(*) AS 'relevance' 
FROM 
    (SELECT Image.key 
     FROM 
     --good image candidates 
     (SELECT Image.key 
     FROM Image 
     WHERE Image.key NOT IN 
      --Bad Images 
      (SELECT DISTINCT(Image.key) --Will reduce size of set, remove duplicates 
      FROM Image 
      INNER JOIN TagRelationship 
       ON Image.ID = TagRelationship.ImageID 
      INNER JOIN Tag 
       ON TagRelationship.TagID = Tag.ID 
       WHERE Tag.tag 
        IN ('digital', 'drawing'))) 
    INNER JOIN TagRelationship 
     ON Image.ID = TagRelationship.ImageID 
    INNER JOIN Tag 
     ON TagRelationship.TagID = Tag.ID 
    WHERE Tag.tag 
      IN ('green', 'mountain') 
    GROUP BY Image.key 
    HAVING COUNT(*) = count('green', 'mountain') 
    --we need green AND mountain 

    UNION ALL 

    --Get all images with one of the following 2 tags 
    SELECT * 
    FROM 
     (SELECT Image.key 
     FROM Image 
     INNER JOIN TagRelationship 
      ON Image.ID = TagRelationship.ImageID 
     INNER JOIN Tag 
      ON TagRelationship.TagID = Tag.ID 
      WHERE Tag.tag 
      IN ('background' , 'landscape')) 
) 
GROUP BY Image.key 
ORDER BY relevance DESC 
Cuestiones relacionadas