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é])