【问题标题】:Django Q Queries & on the same field?Django Q 查询 & 在同一个领域?
【发布时间】:2019-08-19 09:50:12
【问题描述】:

这是我的模型:

class Event(models.Model):
    user = models.ForeignKey(User, blank=True, null=True, db_index=True)
    name = models.CharField(max_length = 200, db_index=True)
    platform = models.CharField(choices = (("ios", "ios"), ("android", "android")), max_length=50)

class User(AbstractUser):
    email = models.CharField(max_length=50, null=False, blank=False, unique=True)

Event 就像一个分析事件,所以如果一个用户在多个设备上登录,我很可能会为一个用户创建多个事件,有些是 platform=ios,有些是 platform=android。我想查询有多少用户同时拥有 ios 和 android 设备。所以我写了一个这样的查询:

User.objects.filter(Q(event__platform="ios") & Q(event__platform="android")).count()

返回 0 个结果。我知道这是不正确的。然后我想我会尝试只查询 iOS 用户:

User.objects.filter(Q(event__platform="ios")).count()

返回 6,717,622 个结果,这是出乎意料的,因为我只有 39,294 个用户。我猜这不是计算用户,而是计算Event 实例,这对我来说似乎是不正确的行为。有人对这个问题有任何见解吗?

【问题讨论】:

  • 第二个查询看起来不错,尝试在 count 之前添加.order_by() 以删除任何默认排序,然后查看它是否有效。模型的Meta 中定义的默认排序可能会以微妙的方式破坏您。
  • 我添加了.order_by('user_id'),结果相同。起作用的是添加.distinct('id'),尽管查询仍然需要很长时间。我的猜测是,如果不扁平化我的数据库结构,我不会让它更快。
  • 我假设您在platform 上已经有一个index。 Navid 建议使用整数而不是字符串也有帮助。最后,不使用连接但只访问Event 表的原始 SQL 应该可以将查询速度提高两个数量级(同时对于这种大小的表仍然不会产生即时结果)。
  • 2个数量级应该是完美的。

标签: sql django django-queryset django-q


【解决方案1】:

您可以改用注释:

django.db.models import Count

User.objects.all().annotate(events_count=Count('event')).filter(events_count=2)

所以它会过滤掉任何有两个事件的用户。

您也可以使用链式过滤器:

User.objects.filter(event__platform='android').filter(event__platform='ios')

第一个过滤器将获得所有使用 android 平台的用户,第二个过滤器将获得同时拥有 iOS 平台的用户。

【讨论】:

  • 第一个查询注释每个用户的计数。第二个工作正常,为我之前的评论道歉。它似乎与提问者的第一个查询相同,但不是像这里那样过滤 x 对多关系。
  • 我不应该挑剔,但我不是特别喜欢这个答案。调用 User.objects.filter(event__platform='android') 会导致连接并返回 >6M 结果,而我的问题是为什么我没有得到 39k 结果。我认为答案是我需要使用.distinct(),这似乎与文档所说的相矛盾。然后将它与另一个 .filter() 链接起来会导致另一个连接,当你有一个超过 6M 行的表时,它不会很快返回。
  • 您将根据另一个表过滤您的结果,因此您将拥有联接。这就是您设计数据库的方式,这就是数据库的工作方式。您可以获取 android 和 ios 的 id 并使用它们,这比通过字符串过滤要快。您还可以对一些更快的不同查询集执行第二个过滤器。如何对这些类型的查询进行优化超出了这个问题,您可以打开另一个问题并开始讨论,但这是使用 django orm 的问题的答案,除非您想使用原始 sql,这仍然是另一个关于 sql 的主题。
【解决方案2】:

这通常是具有两个或多个与子对象相关的条件的查询集的答案。

解决方案:具有两个子查询的简单解决方案是可能的,即使没有任何连接:

base_subq = Event.objects.values('user_id').order_by().distinct()
user_qs = User.objects.filter(
    Q(pk__in=base_subq.filter(platform="android")) &
    Q(pk__in=base_subq.filter(platform="ios"))
)

如果模型 Event 具有默认排序,则方法 .order_by() 很重要(请参阅有关 distinct() 方法的文档)。


注意事项

验证将执行的唯一 SQL 请求:(通过删除“app_”前缀进行简化。)

>>> print(str(user_qs.query))
SELECT user.id, user.email FROM user WHERE (
    user.id IN (SELECT DISTINCT U0.user_id FROM event U0 WHERE U0.platform = 'android')
    AND
    user.id IN (SELECT DISTINCT U0.user_id FROM event U0 WHERE U0.platform = 'ios')
)
  • 使用函数Q() 是因为不能在同一filter() 中重复相同的条件参数(pk__in),但也可以使用链式过滤器:.filter(...).filter(...)。 (过滤条件的顺序并不重要,它被 SQL 服务器优化器估计的偏好所抵消。)
  • 临时变量base_subq 是一个“别名”查询集,只是为了不重复从未单独计算过的表达式的同一部分。
  • 用户(父)和事件(子)之间的连接不会成为问题,也可以使用一个子查询的解决方案,但是使用事件和事件的连接(具有重复子对象或两个子对象的连接)对象)在任何情况下都应该通过子查询来避免。两个子查询的可读性很好,可以证明两个过滤条件的对称性。

另一种具有两个嵌套子查询的解决方案如果我们知道一个子查询(我们放在最里面)的过滤器比另一个具有大量结果。 (例如,如果 Android 用户数量很大)

ios_user_ids = (Event.objects.filter(platform="ios")
                .values('user_id').order_by().distinct())
user_ids = (Event.objects.filter(platform="android", user_id__in=ios_user_ids)
            .values('user_id').order_by().distinct())
user_qs = User.objects.filter(pk__in=user_ids)

验证它是如何编译成 SQL 的:(通过删除 app_ 前缀和 " 再次简化。)

>>> print(str(user_qs.query))
SELECT user.id, user.email FROM user 
WHERE user.id IN (
    SELECT DISTINCT V0.user_id FROM event V0
    WHERE V0.platform = 'ios' AND V0.user_id IN (
        SELECT DISTINCT U0.user_id FROM event U0
        WHERE U0.platform = 'android'
    )
)

(这些解决方案也适用于旧的 Django,例如 1.8。自 Django 1.11 以来存在一个特殊的子查询函数Subquery(),用于更复杂的情况,但对于这个简单的问题,我们不需要它。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多