Django ORM使用distinct与不同order_by报错,求获取最近发信联系人方案
解决PostgreSQL下Django distinct与order_by冲突的问题
问题原因
你遇到的错误是PostgreSQL的DISTINCT ON语法限制导致的:它要求ORDER BY的第一个字段必须和DISTINCT ON指定的字段完全一致,所以直接按-created_at排序再distinct('contact_id')会触发报错。
可行解决方法
方法1:调整排序顺序后二次排序
先按contact_id排序,再按created_at倒序,这样distinct('contact_id')会保留每个联系人的最新消息,之后再重新按时间倒序取前10:
latest_messages = Message.objects.order_by('contact_id', '-created_at').distinct('contact_id').order_by('-created_at')[:10]
Django会合并两次order_by逻辑,最终SQL先按contact_id、-created_at排序去重,再重新按-created_at排序取前10,刚好满足获取最近10个联系人的需求。
方法2:子查询获取每个联系人的最新消息时间
通过子查询先拿到每个联系人的最新消息时间,再筛选对应消息:
from django.db.models import Subquery, OuterRef, Max # 子查询:获取每个contact_id的最新消息时间 max_created_subquery = Message.objects.filter(contact_id=OuterRef('contact_id')).values('contact_id').annotate(max_created=Max('created_at')).values('max_created') # 筛选出每个联系人的最新消息,按时间倒序取前10 latest_messages = Message.objects.filter(created_at=Subquery(max_created_subquery)).order_by('-created_at')[:10]
这种方法逻辑更清晰,适合需要明确筛选条件的复杂场景。
方法3:利用自增ID获取最新消息(如果ID是自增的)
如果你的Message表id是自增主键,最新的消息ID对应最新的创建时间,可以用聚合函数先拿到每个联系人的最新消息ID:
from django.db.models import Max # 获取每个contact_id对应的最新消息ID latest_msg_ids = Message.objects.values('contact_id').annotate(latest_msg_id=Max('id')).values('latest_msg_id') # 根据ID筛选消息并排序 latest_messages = Message.objects.filter(id__in=latest_msg_ids).order_by('-created_at')[:10]
这种方法性能通常更好,因为主键查询的效率更高。
注意事项
- 如果同一个联系人在同一时间发送了多条消息,方法2和3可能返回多条该联系人的消息,此时可以在最后加上
.distinct('contact_id')去重。 - 以上方法针对PostgreSQL优化,其他数据库(如MySQL)的去重逻辑不同,需要调整写法。
内容的提问来源于stack exchange,提问作者Rupert
相关产品推荐
相关产品推荐

