問題描述
最近在使用 Django 的時(shí)候,發(fā)現(xiàn) DateTimeField__date__range
查詢不到數(shù)據(jù)瞳秽。
class FootPrint(models.Model):
"""
我的足跡
"""
goodsbase = models.ForeignKey(GoodsBase, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
visited_at = models.DateTimeField(
auto_now_add=True,
help_text="訪問時(shí)間",
verbose_name="訪問時(shí)間"
)
end_date = datetime.now().date()
start_date = end_date - timedelta(days=30)
queryset = FootPrint.objects.filter(
user_id=user_id,
visited_at__date__range=[start_date, end_date],
).order_by('-visited_at')
我想查詢某個(gè)用戶30天之內(nèi)的訪問足跡蹦疑,但是傳入正確的條件蚓挤,卻總是查詢不出數(shù)據(jù)。
問題排查及解決
首先我通過原生 SQL 直接查詢數(shù)據(jù)庫车份,發(fā)現(xiàn)能查詢到結(jié)果谋减,排除條件及 MySQL DATA
函數(shù)的問題。
SELECT * FROM `footprints_footprint` WHERE (`user_id` = 1 AND DATE(`visited_at`) BETWEEN '2019-07-22' AND '2019-08-21');
我打印 Django orm 生成的 SQL 語句扫沼,發(fā)現(xiàn) DATE 部分如下:
DATE(CONVERT_TZ(`footprints_footprint`.`visited_at`, 'UTC', 'Asia/Shanghai')) BETWEEN '2019-07-22' AND '2019-08-21')
到這里出爹,可以猜測問題出在 CONVERT_TZ
函數(shù)上。查詢 MySQL 官方文檔缎除,發(fā)現(xiàn) CONVERT_TZ
有兩種傳參方式严就,一種是時(shí)區(qū)名,如上面 Django 生成的 SQL 語句器罐。另一種是直接寫時(shí)差梢为。
SELECT CONVERT_TZ('2004-01-01 12:00:00','+00:00','+10:00');
MySQL 默認(rèn)是不支持寫時(shí)區(qū)名的,如需支持時(shí)區(qū)名方式轰坊,需執(zhí)行如下命令
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
執(zhí)行完上面的命令铸董,發(fā)現(xiàn)就能正常查詢出數(shù)據(jù)了。
雜談
問題解決了肴沫,但是有一個(gè)限制粟害。如果將代碼部署到其他機(jī)器,又還得執(zhí)行一遍上面的命令颤芬”可以將 range 查詢方式優(yōu)化為大于小于比較。
end_date = datetime.now()
start_date = end_date - timedelta(days=30)
queryset = FootPrint.objects.filter(
user_id=user_id,
visited_at__gte=start_date,
visited_at_lte=end_date,
).order_by('-visited_at')
如果仍查詢不到數(shù)據(jù)站蝠,請確保你的 TIME_ZONE
和 USE_TZ
設(shè)置正確汰具。參考 Django Time zones。在中國菱魔,數(shù)據(jù)庫使用 MySQL留荔,這兩個(gè)值應(yīng)該設(shè)置如下:
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False
如果仍想用 range 方式,可以自定義 lookup
from django.db.models import Lookup
from django.db.models import DateTimeField, DateField
class DateEqLookup(Lookup):
"""
自定義 lookup豌习,解決Django __date 轉(zhuǎn)換時(shí)區(qū)默認(rèn)用時(shí)區(qū)名存谎,而 MySQL 默認(rèn)不支持
"""
lookup_name = 'date_eq'
def as_sql(self, compiler, connection):
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params
return f'DATE({(lhs, rhs)}) = DATE({params})'
DateField.register_lookup(DateEqLookup)
DateTimeField.register_lookup(DateEqLookup)
不過 lookup 方式不太靈活拔疚,需確保查詢之前 lookup 正確被注冊肥隆。