使用 Django ORM 查询具有两个 FK 的模型,这两个 FK 通过对第三个模型的 unique_together 约束相关联

Using Django ORM to query a Model with two FKs that are related through an unique_together constraint on a third Model

提问人:Henrique 提问时间:10/25/2023 更新时间:11/16/2023 访问量:34

问:

我目前在创建一个 QuerySet 时面临着一个挑战,该 QuerySet 可以准确表示我的 Django 模型中的关系。我有一个配备两个外键的模型,FK1 和 FK2。这两个外键组合在一起时,会创建一个表示分类 (FK1, FK2 -> CLS.pk) 的元组。 这种关系通过另一个模型进行管理和表示,在该模型中,我存储了与这些外键和对应的记录以及生成的分类(FK1、FK2、CLS.pk)。

定义我的问题本身就是一个挑战,所以我试图简化这种关系,以说明我在这里试图实现的核心思想。

class Event(models.Model):
    value = models.DecimalField(decimal_places=2, max_digits=11, default=0.0)
    event_date = models.DateField()
    local = models.ForeignKey(Local, on_delete=models.CASCADE, related_name='event_set')
    origin = models.ForeignKey(Origin, on_delete=models.CASCADE, related_name='event_set')

    class Meta:
        db_table = 'db_event'
        
class Local(models.Model):
    name = models.CharField(max_length=100, unique=True)

    class Meta:
        db_table = 'db_local'
        
class Origin(models.Model):
    name = models.CharField(max_length=100, unique=True)

    class Meta:
        db_table = 'db_origin'
        
class Classification(models.Model):
    name = models.CharField(max_length=100, unique=True)

    class Meta:
        db_table = 'db_classification'
        
class ClassificationPerOriginAndLocal(models.Model):
    local = models.ForeignKey(Local, on_delete=models.CASCADE, related_name='classification_related_set')
    origin = models.ForeignKey(Origin, on_delete=models.CASCADE, related_name='classification_related_set')
    classification = models.ForeignKey(Classification, on_delete=models.CASCADE, related_name='classification_related_set')
    
    class Meta:
        db_table = 'db_classification_per_origin_and_local'
        unique_together = ('local', 'origin',)

考虑一个“事件”模型,该模型旨在存储发生在特定来源和特定本地的事件的记录。稍后,我需要对每个事件进行分类,这种分类是通过元组“origin”和“local”实现的。

这个分类系统通过名为“ClassificationPerOriginAndLocal”的第三个模型进行管理,我在其中指定哪个“Classification”对象对应于一对给定的“origin”和“local”对象。

在 SQL 原始查询中,我会制定如下内容来获取“Classification.pk”:

SELECT
        ev.*,
        clsf.classification_id
    FROM db_event as ev 
    INNER JOIN db_classification_per_origin_and_local as clsf
     ON clsf.local_id = ev.local_id
      AND clsf.origin_id = ev.origin_id

如何使用(如果可能的话)Django ORM 来创建这个查询?

sql django orm django-queryset

评论


答:

0赞 Fabricio Jallaza 10/25/2023 #1
from django.db.models import F

events_with_classification = Event.objects.annotate(
    classification_id=F('local__classification_related_set__classification_id')
)

# Now, you can access the classification_id field for each Event object
for event in events_with_classification:
    classification_id = event.classification_id

您应该能够访问查询集中每个“事件”的“classification_id”字段,并且它将包含“ClassificationPerOriginAndLocal”模型中指定的“Classification.pk”。

评论

0赞 Henrique 10/26/2023
此注解仅求解其中一种关系。“local”可以对不同的“origin”具有不同的“分类”对象。它两者一起定义了关系(本地 + 原产地 ->不同的分类)。
0赞 Henrique 11/16/2023 #2

经过长时间的挖掘其他 Stack Overflow 线程和 Django 文档,我设法使用 Subquery 和 OuterRef 解决了这个问题:

from django.db.models import OuterRef, Subquery

# Creating the subquery
subquery = (
    ClassificationPerOriginAndLocal
    .objects
    .filter(local=OuterRef('local'), origin=OuterRef('origin'))
)

# Constructing the final query with annotation
final_query = (
    Event
    .objects
    .annotate(classification=Subquery(subquery.values('classification')))
)

对于上下文,我找到的解决方案确保子查询仅返回每个关系的奇异值(本地 + origin -> 分类)。这是通过利用本地 + 源的唯一约束(使用 unique_together 设置)来强制执行的。

此外,如果你需要访问分类对象中的特定字段,你可以使用 Django 的 '__' 查找来实现这一点:

final_query = (
    Event
    .objects
    .annotate(
        classification=Subquery(subquery.values('classification')),
        classification__name=Subquery(subquery.values('classification__name')
    )
)