Django使用CBV计算同车辆Refuel模型相邻行odometer_reading差值及油耗
实现方案
你可以通过Django内置的窗口函数LAG在数据库层面直接计算相邻记录的差值,性能更好,也可以用Python层遍历处理,逻辑更简单,两种方案适配不同场景:
方案1:窗口函数实现(推荐,适合数据量大的场景)
实现逻辑
利用数据库的窗口函数按车辆ID分组,按加油时间排序,直接提取上一条加油记录的里程数,再计算行驶距离和每公里油耗。
代码示例
首先导入依赖:
from django.db.models import Window, F, ExpressionWrapper, DecimalField, Case, When from django.db.models.functions import Lag from django.views.generic import ListView from .models import Refuel
在CBV中重写get_queryset方法即可:
class RefuelListView(ListView): model = Refuel context_object_name = "refuel_list" def get_queryset(self): # 可根据需求增加filter条件,比如筛选指定车辆: filter(vehicle_id=xxx) return Refuel.objects.select_related("vehicle").annotate( # 按车辆分组,按加油时间升序取上一条记录的里程数 prev_odometer = Window( expression=Lag("odometer_reading", 1), partition_by=F("vehicle_id"), order_by=F("created_at").asc() ), # 计算两次加油之间的行驶距离 driving_distance = ExpressionWrapper( F("odometer_reading") - F("prev_odometer"), output_field=DecimalField(max_digits=8, decimal_places=2) ), # 计算每公里油耗,自动处理空值和除零问题 fuel_per_km = ExpressionWrapper( Case( When(driving_distance__gt=0, then=F("fuel_quantity") / F("driving_distance")), default=None, output_field=DecimalField(max_digits=6, decimal_places=4) ) ) # 显式指定排序,覆盖模型默认的排序规则 ).order_by("vehicle_id", "created_at")
在模板或者序列化器中可以直接读取每条记录的driving_distance(行驶距离)和fuel_per_km(每公里油耗)字段,第一条加油记录没有上一条数据,这两个字段会自动返回空值。
方案2:Python层遍历实现(适合小数据量场景)
如果数据量不大,不想写复杂的ORM,可以直接在获取查询集后遍历计算:
class RefuelListView(ListView): model = Refuel context_object_name = "refuel_list" def get_queryset(self): queryset = list(Refuel.objects.select_related("vehicle").order_by("vehicle_id", "created_at")) prev_odometer_map = {} # 存储每辆车的上一次加油里程 for record in queryset: vid = record.vehicle_id current_odo = record.odometer_reading # 跳过没有里程数的记录 if not current_odo: record.driving_distance = None record.fuel_per_km = None continue # 计算差值和油耗 if vid in prev_odometer_map: prev_odo = prev_odometer_map[vid] distance = current_odo - prev_odo if distance > 0: record.driving_distance = distance record.fuel_per_km = round(record.fuel_quantity / distance, 4) else: record.driving_distance = None record.fuel_per_km = None else: # 车辆第一条记录 record.driving_distance = None record.fuel_per_km = None # 更新当前车辆的上一次里程数 prev_odometer_map[vid] = current_odo return queryset
注意事项
- 原
Refuel模型的默认排序是按加油站和创建时间倒序,会影响相邻记录的计算逻辑,所以查询时必须显式指定order_by("vehicle_id", "created_at")保证排序正确 - 如果是使用DRF的CBV,只需把上述查询集逻辑放到对应视图的
get_queryset方法中,再在序列化器新增driving_distance和fuel_per_km两个字段即可返回对应数据 - 两种方案都自动处理了空里程、里程差为0的除零异常,无需额外判断
内容的提问来源于stack exchange,提问作者E.Mohammed
相关产品推荐
相关产品推荐

