【问题标题】:Django ORM QuerySet intersection by a fieldDjango ORM QuerySet 与字段的交集
【发布时间】:2011-06-02 19:28:30
【问题描述】:

这些是我拥有的(伪)模型。

Blog:
  name
  etc...

Article:
  name
  blog
  creator
  etc

User (as per django.contrib.auth)

所以我的问题是:我有两个用户。我想获取两个用户在 same 博客(无论是哪个博客)上发表的所有文章。我不能简单地按两个用户过滤文章模型,因为这会产生两个用户创建的文章集。显然不是我想要的。但是我可以通过某种方式过滤以获取对象的字段在两个查询集之间匹配的所有文章吗?

【问题讨论】:

    标签: sql django django-models django-orm


    【解决方案1】:

    我记得在核心团队的某个人的演示文稿中读过类似“Django ORM 85% 的情况下,其他 15% 是 raw SQL”之类的内容 - 但我再也找不到源代码了.

    您的问题似乎适合that 15% of raw SQL

    【讨论】:

    • 我希望避免这种情况,但我认为这将是我最有可能的选择。
    • @Sri Raghavan 一开始我也是这样。但过了一段时间,不得不面对它:ORM 只适用于大多数简单的查询。当你必须做一些复杂的事情时,你将不得不进入原始 SQL。
    【解决方案2】:

    这是练习 Django 的 ORM 的好问题 :-)

    根据预先知道的参数,有几种方法可以解决这个问题。

    场景 1:您了解用户和特定博客

    如果您有一个特定的博客并想简单地查找任一作者撰写的所有文章,您可以使用 Q 对象。我不认为这是你的情况,但我会把它放在这里以防万一:

    from django.db.models import Q
    Article.objects.filter(Q(creator=user1) | Q(creator=user2), blog=some_blog_instance)
    

    场景 2:您只知道用户

    如果您想要查找两个用户都发表过的所有博客,然后想要找到他们在这些博客中发表过哪些文章,您需要从 Blog 模型开始:

    from django.db.models import Q
    
    # Find all blogs where both user1 and user2 have written articles
    blogs = Blog.objects.filter(article__creator=user1).\
            filter(article__creator=user2).distinct()
    
    # Now find which articles those were
    for blog in blogs:
        articles = blog.article_set.filter(Q=(creator=user1) | Q=(creator=user2))
    

    根据 Paulo 的 cmets 进行编辑:

    这是一个我认为与 OP 的伪代码匹配的模型测试集,它演示了上述代码的工作原理(至少在 sqlite3 和 postgres 上):

    from django.db import models
    from django.contrib.auth.models import User
    
    class Blog(models.Model):
        name = models.CharField(max_length=128)
    
    class Article(models.Model):
        name = models.CharField(max_length=128)
        blog = models.ForeignKey(Blog)
        creator = models.ForeignKey(User)
    

    然后是user1和user2都在blog2上写文章的一些数据:

    u1 = User.objects.create(username='u1')
    u2 = User.objects.create(username='u2')
    
    b1 = Blog.objects.create(name='b1')
    b2 = Blog.objects.create(name='b2')
    b3 = Blog.objects.create(name='b3')
    
    b1_art1 = Article.objects.create(name='b1_art1', blog=b1, creator=u1)
    b2_art1 = Article.objects.create(name='b2_art1', blog=b2, creator=u1)
    b2_art2 = Article.objects.create(name='b2_art2', blog=b2, creator=u2)
    

    查询:

    [b.name for b in Blog.objects.filter(article__creator=user1).\
        filter(article__creator=user2).distinct()]
    

    产生:

    [b2]
    

    SQL 是(我的测试 django 应用程序被命名为 foo):

    SELECT "foo_blog"."id", "foo_blog"."name" 
    FROM "foo_blog" 
    INNER JOIN "foo_article" ON ("foo_blog"."id" = "foo_article"."blog_id") 
    INNER JOIN "foo_article" T4 ON ("foo_blog"."id" = T4."blog_id") 
    WHERE ("foo_article"."creator_id" = 1  AND T4."creator_id" = 2 )
    

    所以,虽然WHERE 子句确实有(A AND B)AB 引用不同的内部连接("foo_article"."creator_id"T4."creator_id"),而不是同一个表(这是什么过滤器( A, B) 会生成,(即:WHERE ("foo_article"."creator_id" = 1 and "foo_article"."creator_id" = 2)

    (如果没有distinct() 子句,如果您为任一作者添加了更多文章,您将在查询集结果中获得多个b2 条目。)

    就像我说的,这是一个很棒的 ORM 练习!

    【讨论】:

    • -1: 好像creator=user1 AND creator=user2 总是假的。
    • @paulo 看看 django 使用django.db.connection.queriesfilter().filter() 生成的SQL...它执行两个内部连接(这也是添加distinct() 的原因),并且是不同的比filter(creator=user1, creator=user2).
    • 至少在我的设置中(带有 MySQL 的 Django 1.2.1),这会导致与 WHERE (filter1 AND filter2) 的单个内部连接。对我来说似乎是一个合乎逻辑的优化......
    • 好的,棘手的一个,这取决于FK方向(master.filter(detail)!= detail.filter(master))。无论有没有不同,我都得到了相同的结果。
    • 这比原始 SQL 更简单。我个人有点惊讶 blog.filter(article).filter(article) 与 article.filter(blog).filter(blog) 不一致。可怕!
    猜你喜欢
    • 2018-02-13
    • 2019-01-22
    • 2020-02-06
    • 2019-08-05
    • 2013-09-29
    • 2017-08-29
    • 1970-01-01
    • 2019-03-01
    • 2019-11-24
    相关资源
    最近更新 更多