【问题标题】:Django: duplicates when filtering on many to many fieldDjango:在多对多字段上过滤时重复
【发布时间】:2013-08-06 22:50:25
【问题描述】:

我的 Django 应用中有以下模型:

class Book(models.Model):
    name = models.CharField(max_length=100)
    keywords = models.ManyToManyField('Keyword')

class Keyword(models.Model)
    name = models.CharField(max_length=100)

我已保存以下关键字:

science-fiction
fiction
history
science
astronomy

在我的网站上,用户可以通过访问/keyword-slug/ 按关键字过滤书籍。在我的视图中,keyword_slug 变量被传递给一个函数,该函数按关键字过滤书籍,如下所示:

def get_books_by_keyword(keyword_slug):
    books = Book.objects.all()
    keywords = keyword_slug.split('-')
    for k in keywords:
        books = books.filter(keywords__name__icontains=k)

这在大多数情况下都有效,但是每当我使用包含在关键字表中多次出现的字符串(例如science-fictionfiction)的关键字进行过滤时,我就会得到同一本书出现的次数超过在生成的 QuerySet 中一次。

我知道我可以添加 distinct 以仅返回唯一的书籍,但我想知道为什么我一开始就会得到重复,并且真的很想了解为什么它会这样工作。由于我只在成功过滤的 QuerySet 上调用 filter(),所以重复的书如何添加到结果中?

【问题讨论】:

  • 请不要使用标题来标记问题。
  • 这是个人喜好问题,我更喜欢这样格式化我的问题。只要看看右边的相关问题,绝大多数都是以完全相同的方式开始的。

标签: django django-models django-queryset


【解决方案1】:

文档直接引用:https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships

连续的 filter() 调用进一步限制了 对象集,但对于多值关系,它们适用于任何 链接到主模型的对象,不一定是那些 被较早的 filter() 调用选中。

在您的情况下,因为 keywords 是一个多值关系,您的 .filter() 链仅根据原始模型而不是先前的查询集调用过滤器。

【讨论】:

    【解决方案2】:

    您示例中的 2 个模型用 3 个表表示:bookkeywordbook_keyword 关系表来管理 M2M 字段。

    当您在过滤器调用中使用keywords__name 时,Django 正在使用 SQL JOIN 来合并所有 3 个表。这允许您通过另一个表中的值过滤第一个表中的对象。

    SQL 将是这样的:

    SELECT `book`.`id`,
           `book`.`name`
    FROM `book`
    INNER JOIN `book_keyword` ON (`book`.`id` = `book_keyword`.`book_id`)
    INNER JOIN `keyword` ON (`book_keyword`.`keyword_id` = `keyword`.`id`)
    WHERE (`keyword`.`name` LIKE %fiction%)
    

    加入后你的数据看起来像

    | Book Table          | Relation table                     | Keyword table                |
    |---------------------|------------------------------------|------------------------------|
    | Book ID | Book name | relation_book_id | relation_key_id | Keyword ID | Keyword name    |
    |---------|-----------|------------------|-----------------|------------|-----------------|
    | 1       | Book 1    | 1                | 1               | 1          | Science-fiction |
    | 1       | Book 1    | 1                | 2               | 2          | Fiction         |
    | 2       | Book 2    | 2                | 2               | 2          | Fiction         |
    

    然后,当数据从 DB 加载到 Python 中时,您只会收到来自 book 表的数据。如您所见,书 1 在那里重复

    这就是多对多关系和 JOIN 的工作原理

    【讨论】:

    • 另外,通过在 filter() 生成的查询集之后附加 distinct(),Django ORM 为低级 SQL SELECT 语句添加语法 DISTINCT,这有助于减少结果集中的重复。有兴趣的可以查看str(queryset.distinct().query)的值
    猜你喜欢
    • 2018-06-07
    • 2015-07-18
    • 1970-01-01
    • 2020-03-20
    • 2011-07-18
    • 1970-01-01
    • 2014-11-14
    • 2016-12-13
    • 2013-01-22
    相关资源
    最近更新 更多