Django:按两列分组且不区分顺序去重,获取对话最新消息
解决方法
因为PostgreSQL支持DISTINCT ON语法,我们可以通过统一对话的用户标识来将(A,B)和(B,A)视为同一对话,具体实现如下:
1. 封装数据库函数
通过Django的Func类封装PostgreSQL的LEAST和GREATEST函数,用来将对话的两个用户ID按大小排序,生成固定的对话唯一标识:
from django.db.models import Func, F, Q, models class Least(Func): function = 'LEAST' arity = 2 class Greatest(Func): function = 'GREATEST' arity = 2
2. 编写查询语句
通过annotate为每条消息生成统一的对话标识,再按对话标识+最新修改时间排序,最后用distinct保留每个对话的最新消息:
conversations = ( models.TextMessage.objects.filter(Q(sender=user) | Q(receiver=user)) # 生成对话唯一标识:user1为两个用户ID的较小值,user2为较大值 .annotate( user1=Least(F('sender_id'), F('receiver_id')), user2=Greatest(F('sender_id'), F('receiver_id')) ) # 先按对话标识分组排序,再按修改时间倒序(最新消息在前) .order_by('user1', 'user2', '-modified_at') # 按对话标识去重,保留每个对话的第一条(最新)消息 .distinct('user1', 'user2') ).all()
原理说明
LEAST和GREATEST函数会把(A,B)和(B,A)的组合统一成(min_id, max_id),确保同一对话的标识唯一,不会被拆分为两个对话。DISTINCT ON(user1, user2)配合order_by的顺序,会保留每个对话分组中排序后的第一条记录,也就是修改时间最新的消息。
内容的提问来源于stack exchange,提问作者thisisjab
相关产品推荐
相关产品推荐

