Django:在多个到多个字段上进行过滤时重复

rol*_*one 6 django django-models django-queryset

我的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)
Run Code Online (Sandbox Code Playgroud)

我已经保存了以下关键字:

science-fiction
fiction
history
science
astronomy
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

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

我知道我可以添加distinct只返回独特的书籍,但我想知道为什么我会开始重复,并且真的想要了解为什么它的工作方式.由于我只调用filter()成功过滤的QuerySet,因此如何将重复的书添加到结果中?

Igo*_*gor 6

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%)
Run Code Online (Sandbox Code Playgroud)

加入后,您的数据看起来像

| 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         |
Run Code Online (Sandbox Code Playgroud)

然后,当数据从DB加载到Python中时,您只会从book表中接收数据。如您所见,Book 1在此处重复

这就是多对多关系和JOIN的工作方式

  • 此外,通过在从 `filter()` 生成的查询集之后附加 `distinct()`,Django ORM 添加语法 [`DISTINCT`](https://cloud.google.com/bigquery/docs/reference/standard-sql/ query-syntax#select_distinct) 用于低级 SQL `SELECT` 语句,这有助于减少结果集中的重复。有兴趣的可以查看 str(queryset.distinct().query) 的值 (2认同)