如何减少嵌套循环中查询的数量(575)?

时间:2017-03-01 22:54:49

标签: python sql django performance django-models

函数的一部分是计算一些较低,相等且高于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)

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

EDIT 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(...)

1 个答案:

答案 0 :(得分: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;

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

希望它有所帮助。

修改 我试图用你的模型将查询翻译成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文档。