Django中JSONField存营业时间及查询营业商家的方案咨询
解决方案
一、基于现有JSONField的查询实现
1. 数据库层面查询(仅PostgreSQL)
如果你的Django项目使用PostgreSQL作为数据库,可以利用jsonb的原生查询能力直接在数据库层面完成过滤,性能更优:
假设商家模型为Merchant,营业时间字段是opening_hours(JSONField类型)。要查询**周一(索引0)、用户时间为"8:00"**的营业商家,代码如下:
from django.db.models import Q, Func, Value # 格式化用户时间为HH:MM格式字符串 user_time = "8:00" # 构造查询条件 open_merchants = Merchant.objects.filter( # 指定日期索引的closed为false Q(opening_hours__0__closed=False) & # 检查该日期下存在有效时间段,且用户时间落在其中 Func( Value(user_time), function="jsonb_path_exists", template="%(function)s(opening_hours->%(expressions)s, '$[*] ? (@[0] != \"\" && @[1] != \"\" && @[0] <= $time && @[1] >= $time)')", time=user_time ) )
2. Python层面过滤(兼容所有数据库)
如果使用SQLite或其他对JSON查询支持有限的数据库,可以先拉取候选商家,再在Python中完成过滤逻辑:
from datetime import time def check_is_open(merchant, day_index, user_time_str): # 解析用户时间为time对象 try: hours, minutes = map(int, user_time_str.split(":")) user_time = time(hours, minutes) except ValueError: return False # 获取对应日期的营业配置 if day_index >= len(merchant.opening_hours): return False day_config = merchant.opening_hours[day_index] if day_config.get("closed"): return False # 遍历所有时间段检查 for slot in day_config.get("times", []): start_str, end_str = slot if not start_str or not end_str: continue try: start_h, start_m = map(int, start_str.split(":")) end_h, end_m = map(int, end_str.split(":")) start_time = time(start_h, start_m) end_time = time(end_h, end_m) except ValueError: continue if start_time <= user_time <= end_time: return True return False # 使用示例:筛选周一(索引0)、用户时间"8:00"的营业商家 day_index = 0 user_time = "8:00" all_merchants = Merchant.objects.all() open_merchants = [m for m in all_merchants if check_is_open(m, day_index, user_time)]
二、更优的营业时间存储方案
JSONField虽然灵活,但存在查询效率低、数据约束弱、维护成本高的问题,推荐采用关系型数据库规范化设计:
1. 模型设计
from django.db import models class Merchant(models.Model): name = models.CharField(max_length=255) # 商家其他字段... class BusinessHours(models.Model): DAY_CHOICES = [ (0, "周一"), (1, "周二"), (2, "周三"), (3, "周四"), (4, "周五"), (5, "周六"), (6, "周日"), ] merchant = models.ForeignKey(Merchant, on_delete=models.CASCADE, related_name="business_hours") day_of_week = models.IntegerField(choices=DAY_CHOICES) is_closed = models.BooleanField(default=False) class Meta: unique_together = ("merchant", "day_of_week") # 保证每个商家每天仅一条配置 class TimeSlot(models.Model): business_hour = models.ForeignKey(BusinessHours, on_delete=models.CASCADE, related_name="time_slots") start_time = models.TimeField() end_time = models.TimeField()
2. 核心优势
- 查询高效:通过字段直接过滤,可创建索引大幅提升查询速度
- 数据合规:利用TimeField保证时间格式合法,避免JSON中的无效值
- 易于维护:修改营业时间无需操作复杂JSON结构,直接操作模型实例即可
3. 对应查询示例
查询周一(day_of_week=0)、用户时间为8:00的营业商家:
from datetime import time user_time = time(8, 0) open_merchants = Merchant.objects.filter( business_hours__day_of_week=0, business_hours__is_closed=False, business_hours__time_slots__start_time__lte=user_time, business_hours__time_slots__end_time__gte=user_time ).distinct()
内容的提问来源于stack exchange,提问作者PietroPutelli
相关产品推荐
相关产品推荐

