Django - 使用.0查询。在使用.first()时不起作用。创建重复的查询

时间:2018-03-22 09:24:24

标签: django django-models django-queryset

我需要只从queryset_set获取一条记录。 (因为它只返回1.所以我通常使用.0或.first()。

然而,当我使用.0时,我没有得到任何数据。当我使用.first()时,我得到重复的查询(尽管有预取)

我尝试过使用子查询但是从我看过的样本中我无法解释它以满足我的需求。

使用0时生成重复但在使用0

时无效的查询
circuits = SiteCircuits.objects.all() \
                        .exclude(circuit__decommissioned=True) \
                        .select_related('site') \
                        .select_related('circuit') \
                        .prefetch_related(
                            Prefetch(
                            'circuit__devicecircuitsubnets_set',
                            queryset=DeviceCircuitSubnets.objects.all().select_related('subnet')
                            ) \
                        ) \

在模板中:

{% for item in circuits  %}
    {{ item.circuit.devicecircuitsubnets_set.0.subnet }}{{ item.circuit.devicecircuitsubnets_set.0.mask }}
    ...

模型:

class Circuit(models.Model):
    name = models.CharField(max_length=200, verbose_name="Name")
    order_no = models.CharField(max_length=200, verbose_name="Order No")
    ref_no = models.CharField(max_length=200, verbose_name="Reference No")
    expected_install_date = models.DateField()
    install_date = models.DateField(blank=True, null=True)
    ...

class SiteCircuits(models.Model):
    site = models.ForeignKey(Site, on_delete=models.CASCADE)
    circuit = models.ForeignKey(Circuit, on_delete=models.CASCADE)
    site_count = models.IntegerField(verbose_name="How many sites circuit is used at?", blank=True, null=True)
    active_link = models.BooleanField(default=False, verbose_name="Active Link?")

class Device(models.Model):
    site = models.ForeignKey(Site, verbose_name="Site device belongs to", on_delete=models.PROTECT)
    hostname = models.CharField(max_length=200)

class Subnet(models.Model):     
    subnet = models.GenericIPAddressField(protocol='IPv4', \
                                        verbose_name="Subnet", blank=True, null=True)
    mask = models.CharField(max_length=4, verbose_name="Mask", \

class DeviceCircuitSubnets(models.Model):
    device = models.ForeignKey(Device, on_delete=models.CASCADE)
    circuit = models.ForeignKey(Circuit, on_delete=models.CASCADE, blank=True, null=True)
    subnet = models.ForeignKey(Subnet, on_delete=models.CASCADE)

控制台中的示例输出:

>>> circuits[100].circuit.devicecircuitsubnets_set.first().subnet.subnet
'10.10.10.4'
>>> circuits[100].circuit.devicecircuitsubnets_set.all()[0].subnet.subnet
'10.10.10.4'
>>>

2 个答案:

答案 0 :(得分:1)

编辑: 我不确定您的查询失败的原因,但the docs表示您需要注意调用预取的顺序。我的下一个猜测是尝试除去绝对必要的所有预取。对不起,这不是专家建议,我只是糊涂自己。此外,如果您使用的是Django 1.11,您可以重新编写查询,如下所示,因为您只对每个SiteCircuit的第一个子网/掩码对象感兴趣。

from django.db.models import Subquery, OuterRef

subnet = Subquery(
    DeviceCircuitSubnets.objects.filter(circuit_id=OuterRef(
        'circuit_id')).values('subnet__subnet')[:1])
mask = Subquery(
    DeviceCircuitSubnets.objects.filter(circuit_id=OuterRef(
        'circuit_id')).values('subnet__mask')[:1])

circuits = SiteCircuits.objects.filter(
    circuit__decommissioned=False
).annotate(
    circuit_subnet=subnet,
    cicuit_mask=mask
).select_related(
    'site'
).select_related(
    'circuit'
)

然后您可以通过以下方式访问:

{% for item in circuits  %}
    {{ item.circuit_subnet }}{{ item.circuit_mask }}
...

您不应该对不是多对多或一对多的关系调用prefetch_related。对于多对一和一对一的关系,您应该使用select_related代替。例如。假设.select_related('circuit__circuit_type')不是多对多字段,则应使用.prefetch_related('circuit__circuit_type')而不是circuit_type

==从OP == 调试工具栏输出:

SELECT "config_devicecircuitsubnets"."id", "config_devicecircuitsubnets"."device_id", "config_devicecircuitsubnets"."circuit_id", "config_devicecircuitsubnets"."subnet_id", "config_subnet"."id", "config_subnet"."subnet", "config_subnet"."mask", "config_subnet"."subnet_type_id" FROM "config_devicecircuitsubnets" INNER JOIN "circuits_circuit" ON ("config_devicecircuitsubnets"."circuit_id" = "circuits_circuit"."id") INNER JOIN "config_subnet" ON ("config_devicecircuitsubnets"."subnet_id" = "config_subnet"."id") WHERE (NOT ("circuits_circuit"."decommissioned" = 'True' AND "circuits_circuit"."decommissioned" IS NOT NULL) AND "config_devicecircuitsubnets"."circuit_id" = '339') ORDER BY "config_devicecircuitsubnets"."id" ASC LIMIT 1
  Duplicated 526 times.

此行在调试工具栏中突出显示:

{{ item.circuit.devicecircuitsubnets_set.first.subnet }}{{ item.circuit.devicecircuitsubnets_set.first.mask }}

答案 1 :(得分:0)

我对内部结构并不是很熟悉,但我怀疑first()会因filter()的类似原因而生成额外的查询。有关详细信息,请参阅this question

关于为什么.0不会返回任何结果的问题的第二部分 - 您的模型和代码非常复杂,我无法理解它们。如果您提供了一个更简单的示例(包括模型)来回答问题,您可能会得到更好的响应。