3 votes

Django Requête - Trier par Tags en commun

Par exemple, supposons que j'ai une classe Produit

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

Ma classe Tag ressemble à ceci

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

Étant donné un produit, comment trierais-je un ensemble de résultats de tous les autres produits par tags les plus courants?

Par exemple j'ai ce qui suit:

P1 avec les tags A, B et C
P2 avec les tags B, C
P3 avec le tag B
P4 avec les tags A, B et C

Je voudrais que mon ensemble de résultats pour P1 soit P4, P2, P3 dans cet ordre, en supposant que nous excluons P1 de l'ensemble de résultats.

1voto

okm Points 12374

C'est une utilisation typique de self-join, le SQL ressemble à :

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_de_produit_donnné)
     INNER JOIN product_tbl t3 ON (t2.product_id = t3.id)
GROUP BY t3.id, t3.name
ORDER BY similar_tags_count DESC;

Ensuite, la requête pourrait être passée à .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),
    [pk_du_produit_donnné])

Ou utilisez l'query.join() NON DOCUMENTÉ (également dans la docstring du query.join()) pour gérer le join si vous avez VRAIMENT besoin d'un QuerySet:

m2m_tbl = Product.tags.through._meta.db_table
qs = Product.objects.exclude(pk=pk_du_produit_donnné)
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=[pk_du_produit_donnné])

0voto

myusuf3 Points 3924

En supposant que les deux listes de tours vous pouvez faire quelque chose comme ça

P1 = ['A', 'B', 'C'] # ces étant des produits
P3 = ['B']
P4 = ['A', 'B', 'C']

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

Chacun renverra respectivement 1 et 3, puis je l'utiliserais pour ordonner vos résultats. Si vous avez besoin de le faire, vous voudrez peut-être définir votre propre gestionnaire pour faire cet ordonnancement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X