获取用户各token最新Qty_holding:SQL及Django查询修正求助
问题描述
我需要从userholding表中查询每个用户对应每个token的最新Qty_holding值,表结构及数据如下:
+----+-------------+--------------+----------------------------+------------+--------+ | id | Qty_holding | Qty_reserved | created | tokenid_id | uid_id | +----+-------------+--------------+----------------------------+------------+--------+ | 1 | 10 | 0 | 2018-01-18 10:52:14.957027 | 1 | 1 | | 2 | 20 | 0 | 2018-01-18 11:20:08.205006 | 8 | 1 | | 3 | 110 | 0 | 2018-01-18 11:20:21.496318 | 14 | 1 | | 4 | 10 | 0 | 2018-01-23 14:26:49.124607 | 1 | 2 | | 5 | 3 | 0 | 2018-01-23 15:00:26.876623 | 11 | 2 | | 6 | 7 | 0 | 2018-01-23 15:08:41.887240 | 11 | 2 | | 7 | 11 | 0 | 2018-01-23 15:22:48.424224 | 11 | 2 | | 8 | 15 | 0 | 2018-01-23 15:24:03.419907 | 11 | 2 | | 9 | 19 | 0 | 2018-01-23 15:24:26.531141 | 11 | 2 | | 10 | 23 | 0 | 2018-01-23 15:27:11.549538 | 11 | 2 | | 11 | 27 | 0 | 2018-01-23 15:27:24.162944 | 11 | 2 | | 12 | 7.7909428 | 0.11459088 | 2018-01-23 15:27:24.168643 | 1 | 2 | | 13 | 3 | 0 | 2018-01-23 15:36:51.412340 | 14 | 2 | | 14 | 7.5585988 | 0.11459088 | 2018-01-23 15:36:51.417177 | 1 | 2 | | 15 | 6 | 0 | 2018-01-24 08:43:46.635069 | 14 | 2 | | 16 | 7.3262548 | 0.11459088 | 2018-01-24 08:43:46.639984 | 1 | 2 | | 17 | 9 | 0 | 2018-01-24 10:09:08.207816 | 14 | 2 | | 18 | 7.0939108 | 0.11459088 | 2018-01-24 10:09:08.212842 | 1 | 2 | | 19 | 6 | 3 | 2018-01-24 13:43:08.929586 | 14 | 2 | | 20 | 3 | 6 | 2018-01-24 14:49:56.960112 | 14 | 2 | | 21 | 0 | 9 | 2018-01-24 14:50:33.423671 | 14 | 2 | | 22 | 30 | 9 | 2018-01-24 14:51:14.865453 | 14 | 2 | | 23 | 4.7704708 | 0.11459088 | 2018-01-24 14:51:14.870256 | 1 | 2 | | 24 | 27 | 12 | 2018-01-24 14:56:56.914009 | 14 | 2 | | 25 | 24 | 15 | 2018-01-24 14:57:56.475939 | 14 | 2 | | 26 | 21 | 15 | 2018-01-24 14:58:06.750903 | 14 | 2 | | 27 | 18 | 15 | 2018-01-24 15:02:43.203878 | 14 | 2 | | 28 | 4.7705074 | 0.11459088 | 2018-01-24 15:02:43.224901 | 1 | 2 | | 29 | 24 | 0 | 2018-01-24 15:03:40.421943 | 11 | 2 | | 30 | 4.9535074 | 0.11459088 | 2018-01-24 15:03:40.441552 | 1 | 2 | | 31 | 1 | 0 | 2018-01-26 10:35:33.173801 | 18 | 2 | +----+-------------+--------------+----------------------------+------------+--------+
我最初使用的MySQL语句:
select uid_id,tokenid_id,Qty_holding from accounts_userholding group by uid_id,tokenid_id order by created desc;
但返回的Qty_holding并非最新值。同时编写的等效Django查询:
UserHolding.objects.values('tokenid_id','uid_id','Qty_holding').order_by().annotate(Count('tokenid_id'),Count('uid_id'))
也没有得到正确结果,恳请帮忙修正。
问题分析
你的MySQL查询问题在于:当使用GROUP BY时,如果没有对非分组字段(比如Qty_holding)使用聚合函数,MySQL会随机返回分组内的某一行值,而不是你期望的最新行。ORDER BY created desc只是对最终结果排序,不会影响分组时的行选择。
Django的查询同理,单纯用annotate(Count(...))并不能筛选出最新记录,只是统计数量,完全没关联到created时间来获取最新行。
MySQL 修正方案
这里提供两种常用的正确写法:
方法1:子查询获取最新时间后关联
先查询每个uid_id和tokenid_id对应的最大created时间,再关联原表获取对应的Qty_holding:
SELECT uh.uid_id, uh.tokenid_id, uh.Qty_holding FROM accounts_userholding uh INNER JOIN ( SELECT uid_id, tokenid_id, MAX(created) AS latest_created FROM accounts_userholding GROUP BY uid_id, tokenid_id ) latest_uh ON uh.uid_id = latest_uh.uid_id AND uh.tokenid_id = latest_uh.tokenid_id AND uh.created = latest_uh.latest_created;
方法2:使用窗口函数(MySQL 8.0+)
用ROW_NUMBER()窗口函数按uid_id和tokenid_id分组,按created降序排序,取每组的第一行(最新记录):
SELECT uid_id, tokenid_id, Qty_holding FROM ( SELECT uid_id, tokenid_id, Qty_holding, ROW_NUMBER() OVER (PARTITION BY uid_id, tokenid_id ORDER BY created DESC) AS rn FROM accounts_userholding ) t WHERE rn = 1;
这个方法更简洁,适合MySQL 8.0及以上版本。
Django ORM 修正方案
对应上面的两种方法,Django也有两种实现方式:
方法1:子查询关联最新时间
from django.db.models import Subquery, OuterRef, Max # 先获取每个(uid_id, tokenid_id)的最新created时间 latest_created_subquery = UserHolding.objects.filter( uid_id=OuterRef('uid_id'), tokenid_id=OuterRef('tokenid_id') ).values('uid_id', 'tokenid_id').annotate( latest_created=Max('created') ).values('latest_created') # 筛选出符合最新时间的记录 latest_holdings = UserHolding.objects.filter( created=Subquery(latest_created_subquery) ).values('uid_id', 'tokenid_id', 'Qty_holding')
方法2:使用窗口函数(Django 2.0+)
如果你的Django版本支持窗口函数(2.0及以上),可以直接用Window和RowNumber:
from django.db.models import Window, F from django.db.models.functions import RowNumber # 给每条记录添加行号,按用户和token分组,按created降序排序 ranked_holdings = UserHolding.objects.annotate( rn=Window( expression=RowNumber(), partition_by=[F('uid_id'), F('tokenid_id')], order_by=F('created').desc() ) ) # 筛选出每组的第一行(最新记录) latest_holdings = ranked_holdings.filter(rn=1).values('uid_id', 'tokenid_id', 'Qty_holding')
这样就能正确获取每个用户对应每个token的最新Qty_holding值了。
内容的提问来源于stack exchange,提问作者Manish Kumar
相关产品推荐
相关产品推荐

