我有一堆django模型
class ReviewItem(Model):
review = models.ForegnKey("Review")
person = models.ForeignKey("Person")
category = models.ForeignKey("Category")
item = models.ForeignKey("item")
reviewed = models.DateTimeField(null=True)
class Person(Model):
name = models.CharField(max_length=255)
class Category(Model):
name = models.CharField(max_length=127)
class Item(Model):
name = models.CharField(max_length=127)
category = models.ForeignKey("Category")
(如您所见,ReviewItem中的“类别”fk是多余的)
最多将有NxM ReviewItem记录,其中N是人数,M是他们可能分配给他们的项目数,他们将在审核后设置“审核”日期。项目按类别分组。
我想要的是计算每件商品的审核项目数量以及未审核的商品数量。在SQL中,我可以做到
select category.name, item.name,
sum(case when reviewed is null then 1 else 0 end) as un_reviewed
sum(case when reviewed is null then 0 else 1 end) as reviewed
from reviewitem
join category on category.id = reviewitem.category_id
join item on item.id = reviewitem.item_id
group by category.id, item.id
order by category.name, item.name
如果不在django中执行两个单独的QuerySet,我无法弄清楚如何执行此操作。
使用两个QuerySet进行,我最终得到了:
uncompleted_items = Item.objects.filter(
reviewitem__review=current_review,
reviewitem__person__reports_to=eff_user,
reviewitem__reviewed__isnull=True
).select_related(
'category',
).annotate(num_uncompleted=Count('reviewitem'))
completed_items = Item.objects.filter(
reviewitem__review=current_review,
reviewitem__person__reports_to=eff_user,
reviewitem__reviewed__isnull=False
).select_related(
'category',
).annotate(num_completed=Count('reviewitem'))
答案 0 :(得分:6)
使用Django 1.8,可以使用Conditional Expressions完成此操作。
因此,您的示例查询可能如下所示:
from django.db.models import When, Case, Sum, IntegerField
items = Item.objects.annotate(
un_reviewed=Sum(Case(When(reviewed__isnull=True, then=1)
When(reviewed__isnull=False, then=0),
output_field=IntegerField())),
reviewed=Sum(Case(When(reviewed__isnull=True, then=0)
When(reviewed__isnull=False, then=1),
output_field=IntegerField())))
答案 1 :(得分:4)
尽管这样做不太方便,但Django ORM并非不可能这样做:)
好的,hacky解决方案没有用,所以这里是漂亮的解决方案;)
from django.db import models
from test_models.models import ReviewItem
class CountNullSql(models.sql.aggregates.Count):
sql_template = '%(function)s(%(distinct)s%(field)s IS NULL)'
class CountNotNullSql(CountNullSql):
sql_template = '%(function)s(%(distinct)s%(field)s IS NOT NULL)'
class CountNull(models.Count):
sql = CountNullSql
def add_to_query(self, query, alias, col, source, is_summary):
aggregate = self.sql(
col,
source=source,
is_summary=is_summary,
**self.extra)
query.aggregates[alias] = aggregate
def _default_alias(self):
return '%s__%s' % (self.lookup, self.sql.__class__.__name__.lower())
default_alias = property(_default_alias)
class CountNotNull(CountNull):
sql = CountNotNullSql
items = (ReviewItem.objects
.values(
'item__category__name',
'item__name',
).annotate(
unreviewed=CountNull('reviewed'),
reviewed=CountNotNull('reviewed'),
)
)
# Just debug stuff from here on, might be useful for others :)
sql, params = items.query.sql_with_params()
try:
import sqlparse
sql = sqlparse.format(sql, reindent=True)
except ImportError:
pass
try:
from pygments import highlight
from pygments.lexers import SqlLexer
from pygments.formatters import Terminal256Formatter
sql = highlight(sql, SqlLexer(), Terminal256Formatter(style='colorful'))
except ImportError:
pass
print sql
结果查询:
SELECT "test_models_category"."name",
"test_models_item"."name",
COUNT("test_models_reviewitem"."reviewed" IS NULL) AS "unreviewed",
COUNT("test_models_reviewitem"."reviewed" IS NOT NULL) AS "reviewed"
FROM "test_models_reviewitem"
INNER JOIN "test_models_item" ON ("test_models_reviewitem"."item_id" = "test_models_item"."id")
INNER JOIN "test_models_category" ON ("test_models_item"."category_id" = "test_models_category"."id")
GROUP BY "test_models_category"."name",
"test_models_item"."name"
示例结果:
[{'item__category__name': u'cat a',
'item__name': u'aa',
'reviewed': 1,
'unreviewed': 1},
{'item__category__name': u'cat a',
'item__name': u'ab',
'reviewed': 1,
'unreviewed': 1},
{'item__category__name': u'cat b',
'item__name': u'ba',
'reviewed': 1,
'unreviewed': 1},
{'item__category__name': u'cat b',
'item__name': u'bb',
'reviewed': 1,
'unreviewed': 1}]
Paul Tomblin的更新 所描述的CountNull和CountNotNull方法不起作用(显然布尔值计为1,无论它们是真还是假),所以我将其更改如下:
from django.db import models
class CountNullSql(models.sql.aggregates.Sum):
sql_template = '%(function)s((%(field)s IS NULL)::integer)'
class CountNotNullSql(CountNullSql):
sql_template = '%(function)s((%(field)s IS NOT NULL)::integer)'
class CountNull(models.Sum):
sql = CountNullSql
def add_to_query(self, query, alias, col, source, is_summary):
aggregate = self.sql(
col,
source=source,
is_summary=is_summary,
**self.extra)
query.aggregates[alias] = aggregate
def _default_alias(self):
return '%s__%s' % (self.lookup, self.sql.__class__.__name__.lower())
default_alias = property(_default_alias)
class CountNotNull(CountNull):
sql = CountNotNullSql
答案 2 :(得分:2)
这可以通过Django 1.8中的内置支持直接完成。请参阅https://docs.djangoproject.com/en/dev/ref/models/conditional-expressions/。
答案 3 :(得分:1)
试试这个:
params = {'ri_table': ReviewItem._meta.db_table, 'i_table': Item._meta.db_table}
reviewed_query = 'SELECT COUNT(*) FROM %(ri_table)s WHERE %(ri_table)s.item_id=%(i_table)s.id AND reviewed IS NOT NULL' % params
unreviewed_query = 'SELECT COUNT(*) FROM %(ri_table)s WHERE %(ri_table)s.item_id=%(i_table)s.id AND reviewed IS NULL' % params
items = Item.objects.extra(select={'reviewed': reviewed_query, 'unreviewed': unreviewed_query})
这将返回一个Items的查询集,该查询集基本上使用评论和未查看的计数进行注释。每个Item都是一个模型对象,所以从那里你可以获得Category等等。 print items.query
将向您显示SQL语句,但当然进一步的Django魔法会构建Model实例对象。
使用Django ORM在单个查询中收集所有这些数据并不优雅(公平地说,它也是非平凡的SQL)。这很复杂,我认为这不值得做,至少不是最初的。 Django ORM范例中的更简单代码将更容易理解,代码更快,更易于维护。它可能(或可能不)需要更多的数据库命中,但这种担心可能是过早的优化。架构规范化等问题可能会为您带来更大的性能提升。根据您的使用情况,抓住这样的计数实际上可能会更快。
reviewed = item_obj.reviewitem_set.exclude(reviewed__isnull=True).count()
unreviewed = item_obj.reviewitem_set.filter(reviewed__isnull=True).count()