【问题标题】:How to decrease number (575) of queries in a nested loop?如何减少嵌套循环中的查询数(575)?
【发布时间】:2017-07-21 11:40:52
【问题描述】:

函数的一部分是计算最后有效报价的数量,这些报价低于、等于和高于a_price。想不出更好的方法,所以我现在使用两个循环。问题是这会执行 575 个查询,这太多了。

一种产品可以有很多买家,而买家有很多优惠(日期时间不同)。我尝试添加prefetch_related('buyers'),但它根本没有帮助。

编辑:在这种情况下,有 32 个产品,每个产品有 0 到 30 个买家。

    reset_queries()
    products = Product.objects.filter(user=user)
    my_active_products = products.filter(active=True).prefetch_related('buyers')

    for product in my_active_products:
        for buyer in product.buyers.filter(valid=True):
            last_valid_offer = buyer.get_last_valid_offer()
            a_price = product.a_price
            if a_price:
                if a_price < last_valid_offer.eur_price:
                    cheaper += 1
                elif a_price > last_valid_offer.eur_price:
                    more_expensive += 1
                elif a_price == last_valid_offer.eur_price:
                    equal += 1
            else:
                unknown += 1
    print len(connection.queries)

你知道我应该怎么做才能减少查询次数吗?

编辑 Models.py:

class Product(Model):
    name... 
    active = BooleanField(...)

class Buyer(Model):
    product = ForeignKey('Product',related_name='buyers')


    def get_last_valid_offer(self):
        return self.offers.filter(valid=True).latest('datetime')

class Offer(Model):
    buyer = ForeignKey('Buyer', related_name='offers')
    valid = BooleanField(...)
    datetime = DateTimeField(...)
    eur_price = MoneyField(...)

【问题讨论】:

    标签: python sql django performance django-models


    【解决方案1】:

    我相信你可以通过一些连接和求和的查询来完成。

    如果您为您的数据库提供架构,我可以尝试详细说明答案,现在我将假设以下内容。

    create table user (
        id integer primary key,
        active boolean);
    
    create table product (
        id integer primary key, 
        user integer non null, 
        price integer non null,
        foreign key(user) references user(id));
    
    create table product_buyer (
        id integer primary key,
        product integer,
        buyer integer,
        foreign key(product) references product(id),
        foreign key(buyer) references buyer(id));
    
    create table buyer (
        id integer primary key,
        active boolean,
        last_offer integer);
    

    你应该得到你想要的:

    select (
        user.id, 
        sum(case when product.price > buyer.last_offer then 1 end) as expensive, 
        sum(case when product.price = buyer.last_offer then 1 end) as same,
        sum(case when product.price < buyer.last_offer then 1 end) as cheap)
    from 
        user join product on user.id=product.user 
        join product_buyer on product.id=product_buyer.product 
        join buyer on product_buyer.buyer=buyer.id 
    where user.active=1 and buyer.active=1 
    group by user.id;
    

    您可以查看 django 文档以了解 CASE 语句 here 的条件表达式。

    希望对你有帮助。

    编辑: 我尝试使用您的模型将查询转换为 django(未经测试)。

    Product.objects.filter(
        user=my_user, 
        active=True, 
        buyers__valid=True,
        buyers__offers__valid=True
    ).annotate(
        max_date=Max("buyers__offers__datetime")
    ).filter(
        datetime=F("max_date")
    ).annotate(
        expensive=Case(
            When(buyers__a_price__gt=F("buyers__offers__eur_price"),
                 then=Value(1))
        ), 
        same=Case(
            When(buyers__a_price=F("buyers__offers__eur_price"),
                 then=Value(1))
        ),
        cheap=Case(
            When(buyers__a_price__lt=F("buyers__offers__eur_price"),
                 then=Value(1))
        )
    ).annotate(
        n_expensive=Sum("expensive"),
        n_same=Sum("same"),
        n_cheap=Sum("cheap")
    ).values("user", "n_expensive", "n_same", "n_cheap")
    

    我不确定是否有办法以更简洁的方式编写它,这是我在没有实际制作 django 测试应用程序来检查它的情况下最远的地方。 我将把改进留给你,因为你最终拥有了测试模型,但是鉴于上面的 SQL,翻译过程应该只是浏览 django 文档的问题。

    【讨论】:

    • 谢谢。我的架构看起来有点不同。我在问题的底部添加了一个非常简化的模型,来自 models.py。我在买家表中没有“last_offer”属性。我像buyer.offers.latest('datetime')一样计算它。
    • 添加了查询的 django 翻译,我没有对其进行测试,所以它肯定会在某个地方中断 :D 但它的价值更多的是让您了解要查看的 django 文档的哪些部分。
    猜你喜欢
    • 2022-01-04
    • 1970-01-01
    • 2019-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-03
    • 1970-01-01
    相关资源
    最近更新 更多