在Django ORM中:从每个组中选择具有给定属性的最大值的记录

时间:2016-04-19 02:07:30

标签: python django django-models

假设我有三个型号如下,代表同一公司的几个零售点销售的商品的价格:

class Store(models.Model):
    name = models.CharField(max_length=256)
    address = models.TextField()

class Product(models.Model):
    name = models.CharField(max_length=256)
    description = models.TextField()

class Price(models.Model):
    store = models.ForeignKey(Store)
    product = models.ForeignKey(Product)
    effective_date = models.DateField()
    value = models.FloatField()

设置价格时,会根据商店和产品特定设置价格。即同一商品在不同的商店可以有不同的价格。这些价格中的每一个都有生效日期。对于给定的store和给定的product,当前有效的价格是具有最新effective_date的价格。

编写查询的最佳方式是什么?它将返回所有商店中所有商品的当前有效价格?

如果我使用Pandas,我会给自己一个带有['store', 'product', 'effective_date', 'price']列的数据框,然后我会运行

dataframe\
    .sort_values(columns=['store', 'product', 'effective_date'], ascending=[True, True, False])\
    .groupby('store', 'product')['price'].first()

但必须有一些方法直接在数据库级别上执行此操作。想法?

3 个答案:

答案 0 :(得分:2)

如果您的DBMS是PostgreSQL,您可以通过以下方式使用 distinct order_by 结合使用:

Price.objects.order_by('store','product','-effective_date').distinct('store','product')

它将为您提供所有产品/商店组合的所有最新价格。

有关 distinct 的技巧,请查看此处的文档:https://docs.djangoproject.com/en/1.9/ref/models/querysets/#django.db.models.query.QuerySet.distinct

答案 1 :(得分:1)

如果您使用的是PostgreSQL,可以使用order_bydistinct获取所有商店中所有产品的当前有效价格,如下所示:

prices = Price.objects.order_by('store', 'product', '-effective_date')
                      .distinct('store', 'product')

现在,这与你Pandas所拥有的非常类似。

请注意,使用distinct中的字段名称仅适用于PostgreSQL。根据{{​​1}},store和降序product对价格进行排序后,effective date将仅保留每个商店 - 产品对的第一个条目,这将是您最近的价格当前条目。

不是PostgreSQL数据库:

如果您不使用PostgreSQL,可以使用两个查询来执行此操作:

首先,我们获取所有distinct('store', 'product')群组的最新生效日期:

store-product

一旦我们有这些日期,我们就可以得到这个日期的价格:

latest_effective_dates = Price.objects.values('store_id', 'product_id')
                             .annotate(led=Max('effective_date')).values('led')

免责声明:这假设任何prices = Price.objects.filter(effective_date__in=latest_effective_dates) 组的effective_date都不相同。

答案 2 :(得分:1)

如果没有Postgres增加的功能(你应该真正使用它),有一个更复杂的解决方案(基于ryanpitts' idea),这需要两个db命中:

latest_set = Price.objects
    .values('store_id', 'product_id')  # important to have values before annotate ...
    .annotate(max_date=Max('effective_date')).order_by()
    # ... to annotate for the grouping that results from values

# Build a query that reverse-engineers the Price records that contributed to 
# 'latest_set'. (Relying on the fact that there are not 2 Prices
# for the same product-store with an identical date)

q_statement = Q(product_id=-1)  # sth. that results in empty qs
for latest_dict in latest_set:          
    q_statement |= 
        (Q(product_id=latest_dict['product_id']) & 
         Q(store_id=latest_dict['store_id']) & 
         Q(effective_date=latest_dict['max_date']))

Price.objects.filter(q_statement)