Django (DRF) ORM - 具有反向关系的简单左连接

问题描述 投票:0回答:1

我正在尝试使用 Django 3.2.9 和 DRF 3.12.4 从我的 postgreSQL 数据库中选择数据。

这是我的模型:

class Headpiece(ValidatedModelbase):
    class Types(models.TextChoices):
        POWDER = "Powder"
        SOLUTION = "Solution"

    id_headpiece = models.AutoField(primary_key=True)
    name = models.TextField(null=False)
    structure = models.TextField(null=False)
    total_structure = models.TextField(null=False)
    molecular_weight = models.FloatField(null=False)
    type = models.CharField(null=False, choices=Types.choices, max_length=255)
    status = models.BooleanField(default=True)
    comment = models.TextField(null=True, blank=True)

    class Meta:
        db_table = "headpiece"
        managed = True

    def __str__(self):
        return self.name
class Matter(ValidatedModelbase):

    id_matter = models.AutoField(primary_key=True)
    dna_tag = models.ForeignKey(DNATag, on_delete=models.DO_NOTHING, null=True, blank=True)
    headpiece = models.ForeignKey(Headpiece, on_delete=models.DO_NOTHING, null=True, blank=True, related_name='matter')

    class Meta:
        db_table = "matter"
        managed = True

class Vial(ValidatedModelbase):

    id_vial = models.AutoField(primary_key=True)
    rack = models.ForeignKey(Rack, on_delete=models.DO_NOTHING)
    matter = models.ForeignKey(Matter, on_delete=models.DO_NOTHING, related_name='vial')
    barcode = models.CharField(null=False, max_length=255)
    position_column = models.IntegerField(null=False)
    position_row = models.IntegerField(null=False)
    quantity_left = models.FloatField(null=False)
    project = models.ForeignKey(Project, on_delete=models.DO_NOTHING, null=True, blank=True)
    reserving_business_entity_name = models.CharField(null=True, max_length=255)
    origin_name = models.CharField(null=True, max_length=255)

    class Meta:
        db_table = "vial"
        unique_together = ["rack", "position_column", "position_row"]
        managed = True

    def __str__(self):
        return self.barcode

我需要的数据是头饰列表,以及它们的小瓶数据。我想要为每个小瓶添加一条新行,如果没有相关小瓶,则仅显示头件数据。 在 SQL 中,它翻译成这样:

SELECT headpiece.*, vial.*
FROM headpiece 
LEFT JOIN matter on matter.headpiece_id = headpiece.id_headpiece
LEFT JOIN vial on vial.matter_id = matter.id_matter
LIMIT 100

此查询的结果正是我需要 API 返回的结果。

这是我目前使用 DRF 进行查询的方式:

queryset = Headpiece.objects.select_related(
                    'matter__vial',
                    'matter__vial__rack',
                    'matter__vial__project',
                ).values(
                    'id_headpiece',
                    'matter__vial',
                ).filter(
                    id_headpiece=5,
                )

    print('count')
    print(queryset.count())
    print(queryset.query)

    return queryset.all()

结果很好,但是如果我尝试过滤我的数据,如下所示:

queryset = Headpiece.objects.select_related(
                    'matter__vial',
                    'matter__vial__rack',
                    'matter__vial__project',
                ).values(
                    'id_headpiece',
                    'matter__vial',
                ).filter(
                    id_headpiece=5,
                    matter__vial__id_vial=4525
                )

    print('count')
    print(queryset.count())
    print(queryset.query)

    return queryset.all()

-> ORM 添加了不需要的连接,绕过了过滤器:这是生成的 SQL :

SELECT "headpiece"."id_headpiece", "vial"."id_vial" FROM "headpiece" 
LEFT OUTER JOIN "matter" ON ("headpiece"."id_headpiece" = "matter"."headpiece_id") 
LEFT OUTER JOIN "vial" ON ("matter"."id_matter" = "vial"."matter_id") 
INNER JOIN "matter" T4 ON ("headpiece"."id_headpiece" = T4."headpiece_id") 
INNER JOIN "vial" T5 ON (T4."id_matter" = T5."matter_id") 
WHERE ("headpiece"."id_headpiece" = 5 AND T5."id_vial" = 4525)

当我需要的是:

SELECT headpiece.*, vial.*
FROM headpiece 
LEFT JOIN matter on matter.headpiece_id = headpiece.id_headpiece
LEFT JOIN vial on vial.matter_id = matter.id_matter
where vial.id_vial=4525
LIMIT 100

我想我因为反向关系而陷入困境,虽然我可能是错的,但我认为这是使用 SQL 组织数据的一种非常标准的方式。

我知道我可以使用原始 SQL,但我需要对查询应用大量过滤器、分页和排序,使用 ORM 会容易得多。另外,如果使用 Django ORM 无法解决此问题,我会感到惊讶。

django django-models django-rest-framework django-queryset
1个回答
0
投票

这是将

.values(…)
 [Django-doc]
.filter(…)
 [Django-doc]
组合在一起的结果:
.values(…)
将生成
LEFT OUTER JOIN
,因为项目可以是
NULL
,而
.filter(…)
看到结果不是
NULL
,因此它会将其优化为
INNER JOIN
s。

但实际上你(可能)不应该首先使用

.values(…)
:它是一种 反模式 [Django 反模式]

queryset = Headpiece.objects.select_related(
    'matter__vial',
    'matter__vial__rack',
    'matter__vial__project',
).filter(id_headpiece=5, matter__vial__id_vial=4525)

但是,根据查询,您也不应该预取

rack
project
,所以:

queryset = Headpiece.objects.select_related(
    'matter__vial',
).filter(id_headpiece=5, matter__vial__id_vial=4525)
© www.soinside.com 2019 - 2024. All rights reserved.