首页 > 解决方案 > Django - 过滤同一张表上的多个外键

问题描述

假设我有这样的模型:

class Story(...):
    name = models.CharField(...)

class Chapter(...):
    title = models.CharField(...)
    length = models.CharField(...)
    story = models.ForeignKey('Story', ..., related_name='chapters')

如何过滤具有 N 个特定章节的故事,即:

titles = ['Beginning', 'Middle', 'End']
length = 'Long'

# is there a better way to do this?
stories_with_these_chapters = Story.objects.filter(
    chapters__title = titles[0],
    chapters__length = length
).filter(
    chapters__title = titles[1],
    chapters__length = length
).filter(
    chapters__title = titles[2],
    chapters__length = length
)

编辑:

所以例如说我有这个数据:

故事:

ID | Name
-- | ----
 1 | First Story
 2 | Second Story
 3 | Third Story

章节:

ID | Story ID | Title     | Length
-- | -------- | --------- | ------
 1 |        1 | Beginning | Long
 2 |        1 | End       | Long
 3 |        2 | Beginning | Short
 4 |        2 | Middle    | Short
 5 |        2 | End       | Short
 6 |        3 | Beginning | Long
 7 |        3 | Middle    | Long
 8 |        3 | End       | Long

我想过滤标题为“开始”、“中间”和“结束”并且是“长”的章节的故事——在这个例子中这只是故事 3,因为故事 1 没有标题为“中间”的章节和故事 2 中的所有章节都是“短”的。

标签: pythondjango

解决方案


好的,尝试解释过滤和等效的方法。

如果您正在使用模型字段或ForeignKey关系:

  • .filter(A, B)方法WHERE A AND B
  • .filter(Q(A) & Q(B))方法WHERE A AND B
  • .filter(A).filter(B)方法WHERE A AND B

具有反向ForeignKey关系:

  • .filter(A, B)方法WHERE A AND B
  • .filter(Q(A) & Q(B))方法WHERE A AND B
  • .filter(A).filter(B)表示WHERE A AND B A 和 B 由于多个 INNER JOIN 对具有重复项的表进行操作

看到您的编辑后,您需要链接 Q 查询。

from functools import reduce

import operator
from django.db.models import Q

query = reduce(operator.and_, (Q(chapter__title__contains=x) for x in titles))

qs = Story.objects.filter(query) 

将其放入终端以显示 reduce 正在为您做什么;

>>> from functools import reduce
>>> 
>>> import operator
>>> from django.db.models import Q
>>> 
>>> titles = ['Beginning', 'Middle', 'End']
>>> query = reduce(operator.and_, (Q(chapter__title__contains=x) for x in titles))
>>> query
<Q: (AND: ('chapter__title__contains', 'Beginning'), ('chapter__title__contains', 'Middle'), ('chapter__title__contains', 'End'))>

reduce应用一些数学来构建一个Q对象,该对象AND为列表匹配中的每个术语执行一个对象也是如此chapter__title__contains

添加长度列的更新

从上面,您可以添加到query添加长度查询;

>>> query.add(Q(chapter__length='Long'), query.connector)
<Q: (AND: ('chapter__title__contains', 'Beginning'), ('chapter__title__contains', 'Middle'), ('chapter__title__contains', 'End'), ('chapter__length', 'Long'))>

附带说明一下,要查看 django 查询背后的 SQL 是什么,您可以执行以下操作;

>>> query = User.objects.all()
>>> query.query
<django.db.models.sql.query.Query object at 0x7f910f250eb0>
>>> print(query.query)
SELECT "authentication_user"."id", "authentication_user"."password", "authentication_user"."last_login", "authentication_user"."is_superuser", "authentication_user"."email", "authentication_user"."first_name", "authentication_user"."last_name", "authentication_user"."date_joined", "authentication_user"."is_active", "authentication_user"."is_staff" FROM "authentication_user"

推荐阅读