这是一个典型的自连接用法,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()
: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])