首页 > 解决方案 > 如何将 Django 查询过滤器放在 ON 子句而不是 WHERE 子句中?

问题描述

在 SQL 中,条件的放置,无论是在 ON 子句还是 WHERE 子句中,都会影响结果。filter()在 Django 中似乎将条件放在 WHERE 子句中。

假设我有这些模型:

class Nations(models.Model):
    deleted = models.BooleanField(default=False)


class People(models.Model):
    nation = models.ForeignKey(Nations, on_delete=models.CASCADE)
    deleted = models.BooleanField(default=False)

我想找到没有人的国家,我可以这样做:

SELECT nations.id, nations.deleted
FROM nations
LEFT JOIN people
   ON people.nation_id = nations.id
WHERE people.id IS NULL;

但是假设可以软删除人员,我想找到其中没有未删除人员的国家。我会做:

SELECT nations.id, nations.deleted
FROM nations
LEFT JOIN people
   ON people.nation_id = nations.id
   AND people.deleted IS FALSE
WHERE people.id IS NULL;

但是,在 Django 中,如果我这样做:

Nations.objects.filter(people__id__isnull=True, people__deleted=False)

这等效于查询:

SELECT nations.id, nations.deleted
FROM nations
LEFT JOIN people
   ON people.nation_id = nations.id
WHERE people.id IS NULL
   AND people.deleted IS FALSE;

与所需的查询不同,这将不包括仅删除人员的国家!如何让 Django 将软删除检查移到 ON 子句中?

标签: djangodjango-modelsdjango-rest-framework

解决方案


自 Django 2.0 命名以来,有一个鲜为人知的功能FilteredRelation,它允许向关系添加额外的过滤器。ON结果是连接子句中的附加标准:

nations = (Nations.objects
    .annotate(no_deleted=FilteredRelation(
        'people',
        condition=Q(people__deleted=False)))
    .filter(no_deleted=None)
)

print(str(nations.query))

生成的 SQL 看起来很像您所追求的:

SELECT "app_nations"."id",
       "app_nations"....
FROM "app_nations"
LEFT OUTER JOIN "app_people" no_deleted ON 
    ("app_nations"."id" = no_deleted."nations_id"
     AND (no_deleted."deleted" = FALSE))
WHERE no_deleted."id" IS NULL

推荐阅读