2013-02-21 2 views
3

Как простой пример, скажем, у меня есть класс продуктаДжанго запросы - Сортировка по Тегам в общих

class Product(models.Model): 
    tags = models.ManyToManyField('Tag',blank=True,null=True) 

Мой класс Tag выглядит как этого

class Tag(models.Model): 
    name = models.CharField(max_length=50, unique=True, db_index=True) 

Учитывая один продукт, как бы я сортировать результат всех других продуктов по большинству распространенных тегов?

Например, я следующее:

Р1 с бирками А, В, и С
Р2 с бирками В, С
Р3 с бирками B
P4 с бирками A, B, и C

Я бы хотел, чтобы мой результирующий набор для P1 был P4, P2, P3 в этом порядке, предполагая, что мы исключаем P1 из набора результатов.

ответ

1

Это типичное автообъединением использование, SQL выглядит следующим образом:

SELECT t3.*, count(t2.tag_id) as similar_tags_count 
FROM m2m_tbl t1 INNER JOIN m2m_tbl t2 
    ON (t1.tag_id = t2.tag_id and t1.product_id != t2.product_id and t1.product_id = pk_of_the_given_product) 
    INNER JOIN product_tbl t3 ON (t2.product_id = t3.id) 
GROUP BY t3.id, t3.name 
ORDER BY similar_tags_count DESC; 

Тогда запрос можно кормить .raw():

Product.objects.raw(""" 
SELECT t3.*, count(t2.tag_id) as similar_tags_count 
FROM {m2m_tbl} t1 INNER JOIN {m2m_tbl} t2 
    ON (t1.tag_id = t2.tag_id and t1.product_id != t2.product_id and t1.product_id = %s) 
    INNER JOIN {product_tbl} t3 ON (t2.product_id = t3.id) 
GROUP BY t3.id, t3.name 
ORDER BY similar_tags_count DESC; 
""".format(m2m_tbl=Product.tags.through._meta.db_table, product_tbl=Product._meta.db_table), 
    [the_given_product.pk]) 

Или используйте недокументированные query.join() (также in the docstring of the query.join()) для обработки соединение, если вам действительно нужен QuerySet:

m2m_tbl = Product.tags.through._meta.db_table 
qs = Product.objects.exclude(pk=the_given_product.pk) 
alias_1 = qs.query.get_initial_alias() 
alias_2 = qs.query.join((alias_1, m2m_tbl, 'id', 'product_id')) 
alias_3 = qs.query.join((alias_2, m2m_tbl, 'tag_id', 'tag_id')) 
qs = qs.annotate(similar_tags_count=models.Count('tags__id')).extra(where=[ 
    '{alias_2}.product_id != {alias_3}.product_id'.format(alias_2=alias_2, alias_3=alias_3), 
    '{alias_3}.product_id = %s'.format(alias_3=alias_3) 
], params=[the_given_product.pk]) 
0

Предполагая, что оба получается списки вы можете сделать что-то вроде так

P1 = ['A', 'B', 'C'] # these being products 
P3 = ['B'] 
P4 = ['A', 'B', 'C'] 

P1 = set(P1) 
P3_INTERSECT = len(P1.intersection(P3)) 
P4_INTERSECT = len(P1.intersection(P4)) 

Те будут каждый возвращает 1 и 3 соответственно, я бы тогда использовать, чтобы заказать результаты. Если вам это нужно, вы можете определить своего менеджера для выполнения этого заказа.

+0

Я немного скорректировал формулировку на свой вопрос, так как считаю, что я неправильно описал свою проблему. Мои извинения. – andersra

+0

Как вы можете видеть из моего примера выше; что вам нужно сделать, это получить соответствующие теги в списке и сравнить их с p1, например, и упорядочить их по количеству, содержащемуся в общем. Я обновил свой ответ, чтобы отразить недавно измененный вопрос. – myusuf3

+0

Я не уверен, будет ли этот метод хорошо масштабироваться для больших наборов результатов. Я здесь не прав? – andersra

Смежные вопросы