Pau*_*lin 7 django django-models
我有一堆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")
Run Code Online (Sandbox Code Playgroud)
(如您所见,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
Run Code Online (Sandbox Code Playgroud)
如果不在django中执行两个单独的QuerySet,我无法弄清楚如何做到这一点.
使用两个QuerySets,我最终得到:
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'))
Run Code Online (Sandbox Code Playgroud)
使用Django 1.8,可以使用条件表达式完成.
所以你的示例查询可能如下所示:
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())))
Run Code Online (Sandbox Code Playgroud)
虽然这样做不太方便,但使用 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
Run Code Online (Sandbox Code Playgroud)
结果查询:
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"
Run Code Online (Sandbox Code Playgroud)
结果示例:
[{'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}]
Run Code Online (Sandbox Code Playgroud)
来自 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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2882 次 |
| 最近记录: |