Django ORM 用子查询注释
Django ORM annotate with subquery
我有以下型号:
class Store(models.Model):
name = models.CharField()
class Goods(models.Model):
store = models.ForeigKey(Store, on_delete=models.CASCADE)
total_cost = models.DecimalField()
different values ...
所以,我根据参数筛选了所有的商品,现在我的目标是从每家店中拿一件商品,这是该店所有商品中价格最低的商品
stores = Store.objects.all() - all stores
goods = Good.objects.filter(..) - filter goods
goods.annotate(min_price=Subquery(Min(stores.values('goods__total_cost'))))
我试过类似的方法,但出现错误:
AttributeError: 'Min' object has no attribute 'query'
我认为在你的上下文中,你需要一个 Group By
特性而不是 Django 注释,
来自 this SO answer、
>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors
1
q
是图书的queryset,但是每本书都标注了作者的数量。
也就是说,如果您 annotate
您的 goods
查询集,它们将不会返回一些 sorted/filtered 对象集。它只会用新字段 min_price
进行注释。
所以我建议你做一个 Group By
操作如下
from django.db.models import Min
result = Goods.objects.values('store').annotate(min_val=Min('total_cost'))
例子
In [2]: from django.db.models import Min
In [3]: Goods.objects.values('store').annotate(min_val=Min('total_cost'))
Out[3]: <QuerySet [{'store': 1, 'min_val': 1}, {'store': 2, 'min_val': 2}]>
In [6]: Goods.objects.annotate(min_val=Min('total_cost'))
Out[6]: <QuerySet [<Goods: Goods object>, <Goods: Goods object>, <Goods: Goods object>, <Goods: Goods object>, <Goods: Goods object>]>
In [7]: Goods.objects.annotate(min_val=Min('total_cost'))[0].__dict__
Out[7]:
{'_state': <django.db.models.base.ModelState at 0x7f5b60168ef0>,
'id': 1,
'min_val': 1,
'store_id': 1,
'total_cost': 1}
In [8]: Goods.objects.annotate(min_val=Min('total_cost'))[1].__dict__
Out[8]:
{'_state': <django.db.models.base.ModelState at 0x7f5b6016af98>,
'id': 2,
'min_val': 123,
'store_id': 1,
'total_cost': 123}
UPDATE-1
我认为,这不是一个好主意,可能会出现一些优化问题,但如果您愿意,可以尝试
from django.db.models import Min
store_list = Store.objects.values_list('id', flat=True) # list of id's od Store instance
result_queryset = []
for store_id in store_list:
min_value = Goods.objects.filter(store_id=store_id).aggregate(min_value=Min('total_cost'))
result_queryset = result_queryset|Goods.objects.filter(store_id=store_id, total_cost=min_value)
UPDATE-2
我认为我的 Update-1
部分存在大量性能问题,因此我找到了您问题的一个可能答案,即,
goods_queryset = Goods.objects.filter(**you_possible_filters)
result = goods_queryset.filter(store_id__in=[good['store'] for good in Goods.objects.values('store').annotate(min_val=Min('total_cost'))])
我有以下型号:
class Store(models.Model):
name = models.CharField()
class Goods(models.Model):
store = models.ForeigKey(Store, on_delete=models.CASCADE)
total_cost = models.DecimalField()
different values ...
所以,我根据参数筛选了所有的商品,现在我的目标是从每家店中拿一件商品,这是该店所有商品中价格最低的商品
stores = Store.objects.all() - all stores
goods = Good.objects.filter(..) - filter goods
goods.annotate(min_price=Subquery(Min(stores.values('goods__total_cost'))))
我试过类似的方法,但出现错误:
AttributeError: 'Min' object has no attribute 'query'
我认为在你的上下文中,你需要一个 Group By
特性而不是 Django 注释,
来自 this SO answer、
>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors
1
q
是图书的queryset,但是每本书都标注了作者的数量。
也就是说,如果您 annotate
您的 goods
查询集,它们将不会返回一些 sorted/filtered 对象集。它只会用新字段 min_price
进行注释。
所以我建议你做一个 Group By
操作如下
from django.db.models import Min
result = Goods.objects.values('store').annotate(min_val=Min('total_cost'))
例子
In [2]: from django.db.models import Min
In [3]: Goods.objects.values('store').annotate(min_val=Min('total_cost'))
Out[3]: <QuerySet [{'store': 1, 'min_val': 1}, {'store': 2, 'min_val': 2}]>
In [6]: Goods.objects.annotate(min_val=Min('total_cost'))
Out[6]: <QuerySet [<Goods: Goods object>, <Goods: Goods object>, <Goods: Goods object>, <Goods: Goods object>, <Goods: Goods object>]>
In [7]: Goods.objects.annotate(min_val=Min('total_cost'))[0].__dict__
Out[7]:
{'_state': <django.db.models.base.ModelState at 0x7f5b60168ef0>,
'id': 1,
'min_val': 1,
'store_id': 1,
'total_cost': 1}
In [8]: Goods.objects.annotate(min_val=Min('total_cost'))[1].__dict__
Out[8]:
{'_state': <django.db.models.base.ModelState at 0x7f5b6016af98>,
'id': 2,
'min_val': 123,
'store_id': 1,
'total_cost': 123}
UPDATE-1
我认为,这不是一个好主意,可能会出现一些优化问题,但如果您愿意,可以尝试
from django.db.models import Min
store_list = Store.objects.values_list('id', flat=True) # list of id's od Store instance
result_queryset = []
for store_id in store_list:
min_value = Goods.objects.filter(store_id=store_id).aggregate(min_value=Min('total_cost'))
result_queryset = result_queryset|Goods.objects.filter(store_id=store_id, total_cost=min_value)
UPDATE-2
我认为我的 Update-1
部分存在大量性能问题,因此我找到了您问题的一个可能答案,即,
goods_queryset = Goods.objects.filter(**you_possible_filters)
result = goods_queryset.filter(store_id__in=[good['store'] for good in Goods.objects.values('store').annotate(min_val=Min('total_cost'))])