2012-01-16 4 views
6

В настоящее время я пишу приложение, которое позволяет хранить изображения, а затем помечать эти изображения. Я использую Python и Peewee ORM (http://charlesleifer.com/docs/peewee/), который очень похож на ORM Django.Поиск элементов в отношениях «многие ко многим»

Моя модель данных выглядит следующим образом (упрощенно):

class Image(BaseModel): 
    key = CharField() 

class Tag(BaseModel): 
    tag = CharField() 

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

Теперь я понимаю, концептуально, как запрос для всех изображений, которые имеют определенный набор тегов:

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 

Однако я также хотят иметь возможность выполнять более сложные поисковые запросы. В частности, я хотел бы указать список «всех тегов», то есть изображение должно иметь все указанные теги для возврата, а также список «any» и список «none».

EDIT: Я хотел бы пояснить это немного. В частности, вышеуказанный запрос является запросом «все теги». Он возвращает изображения, у которых есть все теги. Я хочу иметь возможность указать что-то вроде: «Дайте мне все изображения, в которых есть теги (зеленый, горный), любой из тегов (фон, пейзаж), но не теги (цифровые, чертежи)».

Теперь, в идеале, я хотел бы, чтобы это был один запрос SQL, потому что разбиение на страницы становится очень простым с помощью LIMIT и OFFSET. У меня фактически есть реализация, в которой я просто загружаю все в наборы Python, а затем использую различные операторы пересечения. Мне интересно, есть ли способ сделать это все сразу?

Кроме того, для тех, кто заинтересован, я по электронной почте автору Peewee о том, как представить вышеупомянутый запрос с использованием Peewee, и он ответил следующее решение:

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

Или, в качестве альтернативы, более короткая версия :

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

Заранее благодарим за ваше время.

+0

Если я правильно понимаю, у вас есть ответ, который вам нужен в коде peewee, но вы хотите знать, как сделать то же самое в прямом sql? –

+0

вы можете объяснить часть «Однако я также хочу иметь возможность выполнять более сложные поиски. В частности, я хотел бы указать список« всех тегов »- то есть изображение должно иметь все указанные теги, которые будут возвращены, вместе со списком «any» и списком «none». » – naresh

+0

@naresh В частности, приведенный выше запрос является запросом «все теги». Он возвращает изображения, у которых есть все теги. Я хочу иметь возможность указать что-то вроде: «Дайте мне все изображения, у которых есть теги (зеленый, горный), любой из тегов (фон, пейзаж), но не теги (цифровые, чертежи)». Пожалуйста, дайте мне знать, если это не ясно. –

ответ

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 

ОБНОВЛЕНИЕ

Более общепризнанной версии вышеупомянутого запроса (преобразовывает логические результаты в предикатах в целые числа с использованием 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 

или с COUNT вместо 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

Будет ли иметь заявление работать правильно? Поскольку вы выполняете GROUP BY, у вас будет только 1 значение для тега. Поскольку он находится внутри СУММЫ, будет ли он перебирать все теги и предварительно проверять IN? Это гораздо более элегантно, чем мое, если оно работает. – JustinDanielson

+0

Я думал, что после группировки по любому атрибуту вам не удалось выполнить предварительный анализ отдельных строк в таких группах, как Tag.tag IN (обязательные теги)) = N. Мне, возможно, придется настроить некоторые таблицы и проверить этот запрос для себя. Я знаю, что агрегированные операции, такие как SUM, будут работать, но я не знаю, будет ли Tag.tag IN (обязательные теги)) = N работать. Не потому, что я вас сомневаюсь, а потому, что я этого не видел и не делал. Это ново для меня. – JustinDanielson

+1

@JustinDanielson: Да, вы можете иметь выражения, ссылающиеся на столбцы не GROUP BY внутри агрегатных функций, если об этом вы спрашиваете. Результаты выражений агрегируются соответственно. Вы знаете, что вы можете «SUM (Value)», но так же вы можете «SUM (Value * Qty)» или «SUM (Value> 10)».В MySQL-стиле SQL, какой мой запрос является «IN», можно рассматривать как просто другой оператор, например '*' или '+', только он возвращает логическое значение (которое неявно преобразуется в целое число), например '< 'или' = 'тоже. –

0

После запроса должен вернуть все изображения, которые помечены как ('A' и 'B') и ('C' или 'D'), но не 'Е' и '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

Если 'Tag.tag' находится в' ('A', 'B') ', он никогда не может быть найден в' ('E', 'F') '. Условия «НЕ IN» выглядят излишними в вашем конкретном запросе. –

+0

@AndriyM спасибо за комментарий .. Я скорректировал запрос – naresh

+0

Если ему нужно больше двух тегов, вам нужно будет сделать еще одно соединение для tag3. Это быстро выйдет из-под контроля и очень медленно, если он хочет много тегов. – JustinDanielson

2

Верхняя половина получает слова, соответствующие обязательным тегам. Нижняя половина содержит теги, в которых должно присутствовать как минимум 1. В нижнем запросе нет GROUP BY, потому что я хочу знать, появляется ли изображение дважды. Если это так, у него есть фон и пейзаж. Счетчик ORDER BY (*) будет делать снимки с BOTH фоном и альбомными тегами, чтобы они отображались вверху. Таким образом, зеленый, горный, фоновой пейзаж будет наиболее актуальным. Затем зеленый, гора, фон или пейзаж.

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 
Смежные вопросы