Django ORM:选择最新状态为“成功”的项目

时间:2014-11-14 16:51:08

标签: django database orm

我有这个模型。

class Item(models.Model):
    name=models.CharField(max_length=128)

Item多次转移。转移可以成功与否。

class TransferLog(models.Model):
    item=models.ForeignKey(Item)
    timestamp=models.DateTimeField()
    success=models.BooleanField(default=False)

如何查询最新 Items成功的所有TransferLog

使用"最新"我的意思是按timestamp排序。

TransferLog表

这是一个数据样本。此处item1不应包括在内,因为上次转帐不成功:

ID|item_id|timestamp           |success
---------------------------------------
1 | item1 |2014-11-15 12:00:00 | False
2 | item1 |2014-11-15 14:00:00 | True
3 | item1 |2014-11-15 16:00:00 | False

我知道如何用python中的循环解决这个问题,但我想在数据库中进行查询。

4 个答案:

答案 0 :(得分:4)

如果日志中的时间戳增加,那么有效的技巧就是,即传输结束被记录为时间戳(不是转移的开始),或者如果你可以预期的那么转移已经在新的开始之前结束。您可以使用TransferLog最高id而不是最高时间戳的对象。

from django.db.models import Max
qs = TransferLog.objects.filter(id__in=TransferLog.objects.values('item')
           .annotate(max_id=Max('id')).values('max_id'), success=True)

它在子查询中按item_id创建组,并将每个组的最高id发送到主查询,在该查询中,它按组中最新行的success进行过滤。 您可以看到它被Django直接编译为最佳可能的一个查询。

验证了如何编译到SQL:print(qs.query.get_compiler('default').as_sql())

SELECT L.id, L.item_id, L.timestamp, L.success FROM app_transferlog L
  WHERE L.success = true AND L.id IN
   ( SELECT MAX(U0.id) AS max_id FROM app_transferlog U0 GROUP BY U0.item_id )

(我编辑了示例结果编译SQL,以便通过短别名替换许多 "app_transferlog"."字段" 来提高可读性 {{1} } field,通过将 L. 参数直接替换为SQL并编辑空格和括号。)


可以通过添加一些示例过滤器并在同一查询中选择相关的True改进

Item

验证了如何编译到SQL:kwargs = {} # e.g. filter: kwargs = {'timestamp__gte': ..., 'timestamp__lt':...} qs = TransferLog.objects.filter( id__in=TransferLog.objects.filter(**kwargs).values('item') .annotate(max_id=Max('id')).values('max_id'), success=True).select_related('item')

print(qs.query.get_compiler('default').as_sql()[0])

最新的TransferLog和相关项目的有用字段由一个查询获取:

SELECT L.id, L.item_id, L.timestamp, L.success, I.id, I.name
FROM app_transferlog L INNER JOIN app_item I ON ( L.item_id = I.id ) 
  WHERE L.success = %s AND L.id IN
   ( SELECT MAX(U0.id) AS max_id FROM app_transferlog U0
     WHERE U0.timestamp >= %s AND U0.timestamp < %s
     GROUP BY U0.item_id )

print(qs.query.get_compiler('default').as_sql()[1])
# result
(True, <timestamp_start>, <timestamp_end>)

可以根据情况更加优化查询,例如如果您对时间戳不感兴趣,那么您需要使用大数据...

如果没有增加时间戳的假设,它实际上比普通的Django ORM更复杂。我的解决方案可以找到here

接受后

编辑

两个查询可以为非增量数据集提供精确解决方案:

  • 获取一组for logitem in qs: item = logitem.item # the item is still cached in the logitem ... 上次失败的转移。 (使用失败列表,因为它比成功转移列表小得多。)
  • 迭代所有上次转移的列表。排除在失败转移列表中找到的项目。

这种方式可以有效地模拟查询,否则需要自定义SQL:

id

我正在考虑使用PostgresQL实现聚合函数(例如SELECT a_boolean_field_or_expression, rank() OVER (PARTITION BY parent_id ORDER BY the_maximized_field DESC) FROM ... WHERE rank = 1 GROUP BY parent_object_id )作为Django的扩展,它是如何有用的。

答案 1 :(得分:2)

试试这个

# your query    
items_with_good_translogs = Item.objects.filter(id__in=
                      (x.item.id for x in TransferLog.objects.filter(success=True)) 

因为您说过&#34;如何查询所有项目哪个最新的TransferLog成功?&#34;,如果您使用{{1}启动查询,则在逻辑上很容易理解模型。

我使用的Q Object在这样的地方非常有用。 (否定,或......)

Item

提供(x.item.id for x in TransferLog.objects.filter(success=True) 的列表TransferLog为真。

答案 2 :(得分:0)

您可能会更容易从ItemLog接近这个:

dataset = ItemLog.objects.order_by('item','-timestamp').distinct('item')

可悲的是,没有清除False项目,我找不到一种方法来应用不同的过滤器。但是你可以用python listcomprehension过滤掉它:

dataset = [d.item for d in dataset if d.success]

如果您在给定的时间段内为日志文件执行此操作,则最好在排序和区分之前对其进行过滤:

dataset = ItemLog.objects.filter(
    timestamp__gt=start,
    timestamp__lt=end
        ).order_by(
    'item','-timestamp'
        ).distinct('item')

答案 3 :(得分:0)

如果您可以修改模型,我实际上认为您可以更轻松地使用ManyToMany关系而不是显式的ForeignKey - Django有一些内置的便捷方法,可以使您的查询更容易。 ManyToMany上的文档为here。我建议使用以下模型:

class TransferLog(models.Model):
    item=models.ManyToManyField(Item)
    timestamp=models.DateTimeField()
    success=models.BooleanField(default=False)

然后你可以做(​​我知道,不是一个很好的,单行代码,但我想明确更清楚):

results = []
for item in Item.objects.all():
    if item.transferlog__set.all().order_by('-timestamp')[0].success:
        results.append(item)

然后您的结果数组将包含其最新传输成功的所有项目。我知道,它仍然是Python中的一个循环......但也许是一个更清晰的循环。