You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 00:39:04