Python / Django筛选组中具有最大值的行

时间:2019-12-03 07:06:42

标签: python django group-by orm inner-join

我对此有多个答案,但是没有建议的解决方案对我有帮助。

模型描述了各种单元的生产计划。生产计划每小时更新一次。每个生产计划在一天中彼此“堆叠”时称为“层”。自然,下一个“层”比上一个“层”短一小时。

模型如下:

class PlanData(models.Model):
    plan_type = models.ForeignKey(PlanType, on_delete = models.CASCADE) # we only need type 2 here
    plan_ident = models.ForeignKey(ObjectConfig, on_delete = models.CASCADE) # decribes production unit
    plan_for_day = models.DateField() # the day of production cycle
    layer = models.IntegerField(null = True) 
      #'layer' production plan from specified hour to then of the day. 
      # layer 1 contains 24 values, layer 10 - 14 values
    hour = models.IntegerField() # hour of production
    val = models.FloatField(blank = True, null = True) # how much the unit should produce at that hour

我需要的是通过按plan_ident和hour分组来获取那些层数最大的层,从而过滤PlanData。

我想做的事情可以在SQL中完成

select a.plan_ident, a.hour, a.layer, a.val
from dbo.asbr_plandata a
inner join (
    select max(layer) 'mlayer',plan_ident_id, hour
    from dbo.asbr_plandata
    where datediff(day,plan_for_day,getdate()) = 0
    and plan_type_id = 2 and plan_ident_id in (24)
    group by plan_ident_id, hour) b 
        on a.hour  = b.hour 
       and a.layer = b.mlayer 
       and a.hour  = b.hour 
       and a.plan_ident_id = b.plan_ident_id
where datediff(day,a.plan_for_day,getdate()) = 0
and a.plan_type_id = 2 and a.plan_ident_id in (24)

是的,我可以使用以下方法获得每个组的最大层数:

pbr = PlanData.objects.filter(plan_for_day = timezone.now().date(), plan_type = 2, plan_ident__in = [10,12,13]).values('hour','plan_ident').annotate( Max('layer'))

但是我需要全部数据,如果最终在某个地方添加值,我将获得所有数据,而不仅仅是分组的值。

我当然可以获取所有层的字典列表,然后进行过滤,但是我的知识有限,我什至不知道如何查找。

如何通过仅选择具有最大值的行来过滤QuerySet? 或如何内部联接两个查询集? 或者如何通过将字典分组并获取最大值来过滤字典列表?

任何解决方案都可以使用。

2 个答案:

答案 0 :(得分:0)

假设我已经正确理解了您的问题,根据您的SQL方言,一种方法可能是使用Window函数,然后过滤结果。例如:

from django.db.models import Window, Max, F

result = PlanData.objects.filter(
    **your_filters
).annotate(
    max_layer=Window(
        expression=Max('layer'),
        partition_by=[F('hour'), F('plan_ident')],
    )
)

有关Window函数的更多信息,请参见Django docs

EDIT :是的,您忘记了WHERE子句中没有Window函数。但是您将能够在Python中更轻松地过滤结果,例如:

filtered = filter(lambda row: row.max_layer == row.layer, result)

或者,如果您想将结果保留为Subquery格式,则可以使用QuerySet,例如:

from django.db.models import F, OuterRef, Subquery, IntegerField

sub_query = PlanData.objects.filter(
    **your_filters,
    hour=OuterRef('hour'),
    plan_ident=OuterRef('plan_ident'),
)

result = PlanData.objects.filter(
    **your_filters
).annotate(
    max_layer=Subquery(
        subquery.order_by('-layer').values('layer')[:1],
        output_field=IntegerField(),
    )
).filter(
    layer=F('max_layer')
)

答案 1 :(得分:0)

将代码略微修改为上面的正确答案。

result = PlanData.objects.filter( 
           **other_filters,
           layer = Subquery( 
               PlanData.objects.filter( 
                       plan_for_day = OuterRef('plan_for_day'), 
                       plan_ident = OuterRef('plan_ident'), 
                       hour = OuterRef('hour')                   
               ).values( 'plan_for_day',
                       'plan_ident',
                       'hour'
               ).annotate(
                    max_layer = Max('layer')
               ).values_list('max_layer')
         )
)