Django ORM与原生SQL查询的记录计数不一致问题
问题:Django ORM与原生SQL查询计数及结果不匹配
核心差异
Django ORM查询返回记录计数为 6529,但直接执行原生SQL查询得到的计数为 6540,同时两者返回的时间字段值存在明显偏移。
Django视图查询代码
start_date = datetime(2024, 10, 19, 0, 0, 0) end_date = datetime(2024, 10, 19, 23, 59, 59) dbug = Reservations.objects.all().filter(updated_at__range=(start_date, end_date)) print(dbug.count())
Django配置(settings.py)
TIME_ZONE = 'Asia/Tehran' USE_TZ = False
原生SQL查询及结果
SELECT COUNT(*) FROM "consultant_reservations" WHERE updated_at BETWEEN '2024-10-19 00:00:00' AND '2024-10-19 23:59:59';
返回结果:
count ------- 6540 (1 row)
具体查询结果对比
原生SQL查询示例
SELECT * FROM "consultant_reservations" WHERE updated_at BETWEEN '2024-10-19 00:00:00' AND '2024-10-19 23:59:59' LIMIT 4;
返回结果片段(注意updated_at为UTC时间,带+00后缀):
id | idd | updated_at -------+---------+------------------------ 76407 | 2011050 | 2024-10-19 20:28:01+00 1408 | 1958653 | 2024-10-19 06:28:01+00 1514 | 1958759 | 2024-10-19 10:28:01+00 60371 | 1997347 | 2024-10-19 13:28:02+00
Django ORM查询示例
start_date = datetime(2024, 10, 19, 0, 0, 0) end_date = datetime(2024, 10, 19, 23, 59, 59) dbug = Reservations.objects.all().filter(updated_at__range=(start_date, end_date))[:4] data = list(dbug.values()) df = pd.DataFrame(data) print(df.head(4))
返回结果片段(updated_at为Asia/Tehran时区时间):
id idd updated_at 0 76407 2011050 2024-10-19 23:58:01 1 1408 1958653 2024-10-19 09:58:01 2 1514 1958759 2024-10-19 13:58:01 3 60371 1997347 2024-10-19 16:58:02
原因分析
从时间字段的偏移可以看出:
- 数据库中
updated_at存储的是UTC时间(带+00后缀)。 - 由于
USE_TZ = False,Django会将数据库的UTC时间转换为TIME_ZONE设置的Asia/Tehran时间(UTC+3:30)返回给应用,但在构造查询条件时,直接使用本地时间(未时区感知的datetime对象)去过滤数据库中的UTC时间,导致查询范围不匹配:- 原生SQL查询的是UTC时间的2024-10-19全天。
- ORM的查询条件是Asia/Tehran时间的2024-10-19全天,对应UTC时间范围为
2024-10-18 20:30:00到2024-10-19 20:29:59,因此漏掉了UTC时间在2024-10-19 20:30:00到23:59:59之间的记录,这就是计数差异的根源。
解决方案
方案1:启用时区支持(推荐)
修改settings.py:
USE_TZ = True
然后使用时区感知的datetime对象构造查询条件:
from django.utils import timezone start_date = timezone.make_aware(datetime(2024, 10, 19, 0, 0, 0)) end_date = timezone.make_aware(datetime(2024, 10, 19, 23, 59, 59)) dbug = Reservations.objects.filter(updated_at__range=(start_date, end_date))
此时Django会自动将时区时间转换为UTC时间去数据库查询,与原生SQL的范围一致。
方案2:手动转换查询条件为UTC时间
如果保持USE_TZ = False,需要将Asia/Tehran时间的范围转换为UTC时间:
import pytz tehran_tz = pytz.timezone('Asia/Tehran') start_date_tehran = tehran_tz.localize(datetime(2024, 10, 19, 0, 0, 0)) end_date_tehran = tehran_tz.localize(datetime(2024, 10, 19, 23, 59, 59)) # 转换为UTC时间 start_date_utc = start_date_tehran.astimezone(pytz.utc).replace(tzinfo=None) end_date_utc = end_date_tehran.astimezone(pytz.utc).replace(tzinfo=None) dbug = Reservations.objects.filter(updated_at__range=(start_date_utc, end_date_utc))
内容的提问来源于stack exchange,提问作者parmer_110
相关产品推荐
相关产品推荐

