【问题标题】:How to sort queryset by annotated attr from ManyToMany field如何通过ManyToMany字段中的注释属性对查询集进行排序
【发布时间】:2018-03-15 23:42:48
【问题描述】:

最简单的例子:

class User(models.Model):
    name = ...

class Group(models.Model):
    members = models.ManyToManyField(User, through='GroupMembership')

class GroupMembership(models.Model):
    user = ...
    group = ...

我想获取按成员注释字段排序的组列表。

我正在使用三元组搜索来过滤和注释用户查询集。 为了获得带注释的用户,我有类似的东西:

User.objects.annotate(...).annotate(similarity=...)

现在我正在尝试按用户的“相似性”对 Groups 查询集进行排序:

ann_users = User.objects.annotate(...).annotate(similarity=...)
qs = Group.objects.prefetch_related(Prefetch('members', 
    queryset=ann_users))
qs.annotate(similarity=Max('members__similarity')).order_by('similarity')

但它不起作用,因为prefetch_related 在 Python 中进行了“加入”;所以我有错误:

"FieldError: Cannot resolve keyword 'members' into field."

【问题讨论】:

    标签: python django django-models django-orm


    【解决方案1】:

    我希望您有一个数据库功能,用于通过 trigram 搜索及其 Django 绑定来确定名称的相似性,或者您可以创建任何:

    from django.db.models import Max, Func, Value, Prefetch
    
    class Similarity(Func):
        function = 'SIMILARITY'
        arity = 2
    
    SEARCHED_NAME = 'searched_name'
    ann_users = User.objects.annotate(similarity=Similarity('name', Value(SEARCHED_NAME)))
    qs = Group.objects.prefetch_related(Prefetch('members', queryset=ann_users))
    qs = qs.annotate(
        similarity=Max(Similarity('members__name', Value(SEARCHED_NAME)))
    ).order_by('similarity')
    

    主查询被编译为

    SELECT app_group.id, MAX(SIMILARITY(app_user.name, %s)) AS similarity
    FROM app_group
    LEFT OUTER JOIN app_groupmembership ON (app_group.id = app_groupmembership.group_id)
    LEFT OUTER JOIN app_user ON (app_groupmembership.user_id = app_user.id)
    GROUP BY app_group.id
    ORDER BY similarity ASC;
    -- params: ['searched_name']
    

    这不是你想要的标题,但结果是一样的。

    注意:SIMILARITY 函数评估多少次的效率取决于数据库查询优化器。如果原始查询在某些简化情况下的原始想法更好,那么 EXPLAIN 命令的查询计划将是一个有趣的答案。

    【讨论】:

    • 感谢您的回答。我明白了你的想法。不幸的是,我没有单一功能“相似性”;我的搜索更复杂 - 它使用多个字段的双元音位 + 三元组搜索。
    • 还有一件事:我猜相似度不应该被使用两次,不是吗?
    • 如果你写一个相似度表达式,我可以将它重写为一个函数。注释不能被相关字段重用(或者我不能)。关系是模型而不是查询或查询集。因此,目前无法通过相关字段重用注释。对于您的后端,检查相似性评估的数量是否为 len(User) + len(GroupMembership) 或 2 * len(User) 更为重要。比手写 SQL 慢两倍的 ORM 还不错。
    • @MarkMishyn 标注最大相似度的部分完全独立于 prefetch_related。一个人可以在没有其他人的情况下工作。我想没有人可以编写一个原始的 SQL 来通过一个相似度评估来评估两者。即使是依赖于更多字段的复杂相似性,也可以写成一个查询表达式使用两次或写成一个函数。
    猜你喜欢
    • 2020-08-18
    • 1970-01-01
    • 1970-01-01
    • 2012-02-13
    • 1970-01-01
    • 1970-01-01
    • 2011-11-11
    • 2012-09-30
    • 1970-01-01
    相关资源
    最近更新 更多