Django:将价格注释到行,然后按字段对行进行分组,并将价格总和注释到组

Posted

技术标签:

【中文标题】Django:将价格注释到行,然后按字段对行进行分组,并将价格总和注释到组【英文标题】:Django: Annotating price to row, then grouping rows by field, and annotating price sums to groups 【发布时间】:2021-04-14 19:41:06 【问题描述】:

我的情况:做快递。我有一个客户,它有一个价目表。价目表具有基于邮政编码的定价区域。客户有DeliveriesOrders,可以有多个Deliveries(取货、送件),价格根据邮政编码计算。

我计算交货行价格的方式是:

    sheet_zones = customer.price_sheet.price_sheet_zones.filter(postal_codes__code=OuterRef("postal_code"))
    delivery_orders = DeliveryOrder.objects.select_related("customer") \
        .prefetch_related(
        Prefetch(
            'deliveries',
            queryset=Delivery.objects.annotate(price=Subquery(sheet_zones.values("price"))).order_by("-is_pickup"),
            to_attr="priced_deliveries"
        )
    )

这使我能够为每个交货行注释价格。

但是我不能注释预取字段,因为它会导致错误,所以.annotate(Sum("priced_deliveries")) 不起作用。

我很难得到一个交付订单中所有交付的总和。但更让我摸不着头脑的是,如何通过一个名为“reference”的字段对所有交货进行分组,并对每个参考的所有交货价格求和。

相关模型:

class DeliveryOrder(models.Model):
    customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, related_name="delivery_orders")

class Delivery(models.Model):
    delivery_order = models.ForeignKey(DeliveryOrder, on_delete=models.SET_NULL, null=True, related_name="deliveries")
    is_pickup = models.BooleanField(default=True)
    reference = models.CharField(max_length=64, blank=True, null=True)
    postal_code = models.IntegerField()

客户相关模型:

class PriceSheet(models.Model):
name = models.CharField(max_length=255)


class Customer(models.Model):
    name = models.CharField(max_length=255)
    price_sheet = models.ForeignKey(PriceSheet,
                                on_delete=models.SET_NULL, blank=True, null=True, related_name="customers")

class PriceSheetZoneItem(models.Model):
    price_sheet = models.ForeignKey(PriceSheet, on_delete=models.SET_NULL, null=True, related_name="price_sheet_zones")
    zone = models.ForeignKey(Zone, on_delete=models.SET_NULL, null=True, related_name="price_sheet_zones")
    postal_codes = models.ManyToManyField(PostalCode, related_name="price_sheet_postal_codes")
    price = models.DecimalField(decimal_places=2, max_digits=9)

邮政编码相关:

class Town(models.Model):
    name = models.CharField(max_length=64)

class Zone(models.Model):
    name = models.CharField(max_length=32)

class PostalCode(models.Model):
    code = models.IntegerField()
    town = models.ForeignKey(Town, on_delete=models.SET_NULL, null=True, related_name="postal_codes")

我愿意接受所有的建议和帮助。我很可能试图以错误的方式做到这一点。

使用 Django 3.2

提前谢谢你!

【问题讨论】:

【参考方案1】:

首先,在Delivery 模型中,postal_code 是否应该是PostalCode 模型的 ForeignKey?在下面我将假设它是。

我认为您想使用子查询进行注释。在 SQL 中,这看起来像:

SELECT
  delivery_order.*
  (SELECT SUM(<price computation from a single delivery>)
   FROM delivery
   JOIN <join to price sheet table so you can calculate price>
   WHERE delivery.delivery_order_id=delivery_order.id
  ) AS total_price;
FROM
  delivery_order;

您的 ORM 代码类似于:

delivery_orders = DeliveryOrder.objects.annotate(
    total_price=Subquery(
        Delivery.objects.values(
            'delivery_order'  # This is necessary to get the proper group by
        ).annotate(
            'customer_id'=OuterRef('id')
        ).annotate(
            price=Sum(postal_code__pricesheetzoneitem__price)
        ).filter(
            delivery_id=OuterRef('id'),
            postal_code__pricesheetzoneitem__pricesheet__customer_id=OuterRef('id')
        ).values(
            'price'  # This is necessary to select only one value
        )
    ) 
)

使用django-sql-utils package 可能会更简单

from sql_util.aggregates import SubquerySum

delivery_orders = DeliveryOrder.objects.annotate(
    total_price=SubquerySum('delivery__postal_code__pricesheetzoneitem__price',
                            filter=Q(pricesheet__customer_id=OuterRef('id'))
                           )
)

我无法测试这个确切的代码,所以我不确定它是否 100% 正确。

编辑:postal_code 不是外键

首先,如果您要存储邮政编码的实际数字,例如90210,在 postal_code 字段中,它应该是一个字符串,而不是一个整数。一些邮政编码以 0 开头。

您有几个选择。一种是创建两个字段,postal_code_string 存储原始邮政编码信息以防出错,postal_codePostalCode 的 ForeignKey。

另一种选择是使用JoinField。这允许您保持一切不变,但 ORM 可以在 postal_code 字段上进行连接。对于这个问题,JoinField 有点离题

【讨论】:

感谢您的回复。在 IntegerField 上设置 postal_code 的原因是允许使用无效邮政编码的订单通过。然后它将定价为零,并且对邮政编码有更多了解的管理员可以在计费中修复它。 添加了一些关于 postal_code 不是外键的附加信息。 再次感谢您的支持。邮政编码为整数的原因是它可以处理范围内的邮政编码。我确实有帮助函数将邮政编码归零以实现人类可读的表示

以上是关于Django:将价格注释到行,然后按字段对行进行分组,并将价格总和注释到组的主要内容,如果未能解决你的问题,请参考以下文章

FineReport报表开发工具中,如何对行进行强制分页,但是列全部显示?

NetSuite - 对行项目进行排序

Access 2010 SQL--在交叉表查询中按聚合函数对行进行排序

AngularJS按表头对行进行排序

如何按多个字段对对象数组进行排序?

使用 MySQL 进行有序分页