【问题标题】:Optimise two SQL queries and a list comprehension优化两个 SQL 查询和一个列表推导
【发布时间】:2013-04-10 01:45:48
【问题描述】:

我有这两个模型(简化):

class Place(OrderedModel):
    name = models.CharField(max_length=100)

class Team(models.Model):
    name = models.CharField(max_length=100)
    places = models.ManyToManyField(Place, blank=True)

假设有十个 Place 实例。但只有五个Team 实例。我想按Place 中的条目顺序返回一个布尔值列表,其中True 表示Team.places 包含该位置,而False(显然)表示它不包含。 Team.places 字段没有特定的顺序。

在以下示例中,Team 具有前四个 Place 对象的实例,最后一个对象位于其 places 字段中:

[True, True, True, True, False, False, False, False, False, True]

我在 Team 模型上用这个方法解决了这个问题:

class Team(models.Model):
    ...
    def done(self):
        """
        Returns a list of Booleans, one for each place
        in order. True means that the team has found the
        place. False, that they have not.
        """
        places = Place.objects.all()
        return [p in self.places.all() for p in places]

它可以工作,但似乎效率很低。它进行(我相信)两个不同的 SQL 查询,然后是列表理解。有没有更有效的方法来解决这个问题?

已解决

我最终这样做了:

def done(self):
    """
    Returns a list of Booleans, one for each place
    in order. True means that the team has found the
    place. False, that they have not.
    """
    sql = '''SELECT P.id, (TP.id IS NOT NULL) AS done
    FROM qrgame_place P
    LEFT OUTER JOIN qrgame_team_places TP
    ON P.id = TP.place_id AND TP.team_id = %s'''
    places = Place.objects.raw(sql, [self.id])
    for p in places:
        yield bool(p.done)

【问题讨论】:

    标签: python django optimization


    【解决方案1】:

    你需要这个 SQL 查询:

    SELECT P.id, (TP.id IS NOT NULL) AS done
    FROM myapp_place P
    LEFT OUTER JOIN myapp_team_places TP
    ON P.id = TP.place_id AND TP.team_id = %s
    

    (您还需要添加一个ORDER BY 子句以按您想要的顺序返回Place 对象,但是由于我看不到您模型的那部分,所以我不知道该子句是什么应该看起来像。)

    我不知道如何使用 Django 的对象关系映射系统来表达这个查询,但你总是可以使用 raw SQL query 来运行它:

    >>> sql = ''' ... as above ... '''
    >>> places = Place.objects.raw(sql, [team.id])
    >>> for p in places:
    ...     print p.id, bool(p.done)
    ...
    1 True
    2 True
    3 False
    

    【讨论】:

    • 你最后的循环应该是[bool(p.done) for p in places]
    • 哦,愚蠢的错误没有抓住。在您的代码示例中是正确的。像魅力一样工作。只是为了好玩,我会为不同的解决方案计时。但如果这不是最优化的,我会感到惊讶。它适用于所有主要的 SQL 服务器吗?
    • 这是标准 SQL,所以应该到处运行。
    【解决方案2】:

    更优化的解决方案是:

    def done(self):    
        places = Place.objects.values_list('pk', flat=True)
        team_places = self.places.values_list('pk', flat=True)
    
        return [place in team_places for place in places]
    

    【讨论】:

    • 哦。很好,我现在没有时间。但是会继续阅读文档以了解为什么它更优化(我很好地猜测 values_list 做了什么)以及不同查找的时间。
    • 我建议 team_places = set(...) 以便查找是 O(1)。
    猜你喜欢
    • 2021-12-08
    • 2015-11-26
    • 2012-11-17
    • 2016-12-08
    • 2016-10-16
    • 2013-02-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多