【问题标题】:django - prefetch only the newest record?django - 只预取最新的记录?
【发布时间】:2018-10-17 14:18:38
【问题描述】:

我正在尝试仅根据父记录预取最新记录。

我的模型就是这样

class LinkTargets(models.Model):
    device_circuit_subnet = models.ForeignKey(DeviceCircuitSubnets, verbose_name="Device", on_delete=models.PROTECT)
    interface_index = models.CharField(max_length=100, verbose_name='Interface index (SNMP)', blank=True, null=True)
    get_bgp = models.BooleanField(default=False, verbose_name="get BGP Data?")
    dashboard = models.BooleanField(default=False, verbose_name="Display on monitoring dashboard?")


class LinkData(models.Model):
    link_target = models.ForeignKey(LinkTargets, verbose_name="Link Target", on_delete=models.PROTECT)
    interface_description = models.CharField(max_length=200, verbose_name='Interface Description', blank=True, null=True)
...

以下查询失败并出现错误

AttributeError: 'LinkData' object has no attribute '_iterable_class'

查询:

link_data = LinkTargets.objects.filter(dashboard=True) \
                            .prefetch_related(
                                Prefetch(
                                    'linkdata_set',
                                    queryset=LinkData.objects.all().order_by('-id')[0]
                                    )
                                )

我考虑过获取 LinkData 并进行相关选择,但我不知道如何为每个 link_target_id 仅获取 1 条记录

link_data = LinkData.objects.filter(link_target__dashboard=True) \
                            .select_related('link_target')..?   

编辑:

使用 rtindru 的解决方案,预取的似乎是空的。目前有 6 条记录,为 3 个 LinkTargets 中的每一个测试 1 条记录

>>> link_data[0]
<LinkTargets: LinkTargets object>
>>> link_data[0].linkdata_set.all()
<QuerySet []>
>>>

【问题讨论】:

  • 你试过用.distinct('link_target_id)`吗?
  • NotImplementedError:此数据库后端不支持 DISTINCT ON 字段。我的开发环境使用 sqlite,我的实时站点使用 mysql

标签: python django django-queryset


【解决方案1】:

棘手,但它似乎有效:

class ForeignKeyAsOneToOneField(models.OneToOneField):
    def __init__(self, to, on_delete, to_field=None, **kwargs):
        super().__init__(to, on_delete, to_field=to_field, **kwargs)
        self._unique = False

class LinkData(models.Model):
    # link_target = models.ForeignKey(LinkTargets, verbose_name="Link Target", on_delete=models.PROTECT)
    link_target = ForeignKeyAsOneToOneField(LinkTargets, verbose_name="Link Target", on_delete=models.PROTECT, related_name='linkdata_helper')
    interface_description = models.CharField(max_length=200, verbose_name='Interface Description', blank=True, null=True)


link_data = LinkTargets.objects.filter(dashboard=True) \
                               .prefetch_related(
                                    Prefetch(
                                        'linkdata_helper',
                                        queryset=LinkData.objects.all().order_by('-id'),
                                        'linkdata'
                                    )
                                )

# Now you can access linkdata:
link_data[0].linkdata

当然,使用这种方法,您不能使用 linkdata_helper 来获取相关对象。

【讨论】:

    【解决方案2】:

    以下适用于 PostgreSQL。我知道它不会帮助 OP,但它可能对其他人有用。

    from django.db.models import Count, Prefetch
    from .models import LinkTargets, LinkData
    
    link_data_qs = LinkData.objects.order_by(
        'link_target__id',
        '-id',
    ).distinct(
        'link_target__id',
    )
    
    qs = LinkTargets.objects.prefetch_related(
        Prefetch(
            'linkdata_set',
            queryset=link_data_qs,
        )
    ).all()
    

    【讨论】:

      【解决方案3】:

      LinkData.objects.all().order_by('-id')[0] 不是查询集,它是模型对象,因此您的错误。

      您可以尝试LinkData.objects.all().order_by('-id')[0:1],它确实是一个查询集,但它不起作用。鉴于prefetch_related 的工作方式,queryset 参数必须返回一个查询集,其中包含您需要的所有LinkData 记录(然后进一步过滤,其中的项目与 LinkTarget 对象结合)。此查询集仅包含一项,因此不好。 (并且 Django 会抱怨“一旦获取切片就无法过滤查询”并引发异常,因为它应该这样做)。

      让我们备份。本质上,您是在问一个聚合/注释问题 - 对于每个 LinkTarget,您想知道最新的 LinkData 对象,或“id”列的“最大值”。最简单的方法是只用 id 进行注释,然后进行单独的查询以获取所有对象。

      所以,它看起来像这样(我在我的项目中检查了一个类似的模型,所以它应该可以工作,但下面的代码可能有一些拼写错误):

      linktargets = (LinkTargets.objects
                     .filter(dashboard=True)
                     .annotate(most_recent_linkdata_id=Max('linkdata_set__id'))
      
      # Now, if we need them, lets collect and get the actual objects
      linkdata_ids = [t.most_recent_linkdata_id for t in linktargets]
      linkdata_objects = LinkData.objects.filter(id__in=linkdata_ids)
      
      # And we can decorate the LinkTarget objects as well if we want:
      
      linkdata_d = {l.id: l for l in linkdata_objects}
      for t in linktargets:
          if t.most_recent_linkdata_id is not None:
              t.most_recent_linkdata = linkdata_d[t.most_recent_linkdata_id]
      

      我故意把它变成了一个预取来掩盖linkdata_set,因为结果是你有对你说谎的对象——linkdata_set 属性现在缺少结果。你真的想在某个地方被咬吗?最好创建一个具有您想要的东西的新属性。

      【讨论】:

      • 最后一点是不想屏蔽linkdata_set,我认为您可以使用Prefetch(..., to_attr="most_recent_linkdata_set_of_one") 来获得类似的结果,但使用Prefetch,除非我弄错了。跨度>
      【解决方案4】:

      原因是 Prefetch 期望 Django Queryset 作为 queryset 参数,而您正在提供对象的实例。

      如下更改您的查询:

      link_data = LinkTargets.objects.filter(dashboard=True) \
                                  .prefetch_related(
                                      Prefetch(
                                          'linkdata_set',
                                          queryset=LinkData.objects.filter(pk=LinkData.objects.latest('id').pk)
                                          )
                                      )
      

      这确实在很大程度上取消了Prefetch 的用途。

      更新 这会在全球范围内预取一条记录;不是LinkTarget 的最新LinkData 记录。

      要为每个 LinkTarget 预取最大 LinkData,您应该从 LinkData 开始:您可以按如下方式实现:

      LinkData.objects.filter(link_target__dashboard=True).values('link_target').annotate(max_id=Max('id'))

      这将返回 {link_target: 12, max_id: 3223} 的字典

      然后您可以使用它返回正确的对象集;也许根据max_id 的值过滤LinkData。

      看起来像这样:

      latest_link_data_pks = LinkData.objects.filter(link_target__dashboard=True).values('link_target').annotate(max_id=Max('id')).values_list('max_id', flat=True)
      link_data = LinkTargets.objects.filter(dashboard=True) \
                                  .prefetch_related(
                                      Prefetch(
                                          'linkdata_set',
                                          queryset=LinkData.objects.filter(pk__in=latest_link_data_pks)
                                          )
                                      )   
      

      【讨论】:

      • TypeError: int() argument must be a string, a bytes-like object or a number, not 'LinkData' 我需要使用值吗?
      • 已更新 - 需要在 get 上执行 .pk
      • 预取为空
      • 我在问题中添加了更多输出,似乎预取是空的
      • 当然,这是因为您的过滤器查询没有按LinkTarget 过滤-> 它在全局范围内预取一条LinkData 记录;不是每个LinkTarget对应的记录->看我的更新
      猜你喜欢
      • 2013-07-27
      • 1970-01-01
      • 2022-12-07
      • 1970-01-01
      • 2017-11-24
      • 1970-01-01
      • 2020-05-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多