我们再次尝试构建a web-based tool以帮助我们管理Software Carpentry研讨会。我们正在使用Django,尽管它的年龄仍然是Python最广泛使用(并且记录最好的)Web编程框架。但是,自从我在其中构建任何东西已经过去几年了,而且我在一些事情上磕磕绊绊。
例如,我的数据模型看起来像这样(删除了许多不相关的东西):
class Person(models.Model):
'''Someone we know.'''
email = models.CharField(max_length=STR_LONG, unique=True, null=True)
class Event(models.Model):
'''A workshop or other event.'''
slug = models.CharField(max_length=STR_LONG, unique=True)
class Role(models.Model):
'''The kinds of things people can do at workshops.'''
name = models.CharField(max_length=STR_MED)
class Task(models.Model):
'''Someone did something at some workshop.'''
event = models.ForeignKey(Event)
person = models.ForeignKey(Person)
role = models.ForeignKey(Role)
应用程序中的一个页面显示有关特定事件的信息。我想将该活动的所有教师的姓名添加到该页面。如果我直接使用SQL,我会写一些类似的东西:
select Event.slug, group_contact(Person.email, ', ')
from Person join Event join Role join Task
on Person.id=Task.person and Event.id=Task.event and Role.id=Task.role
where Role.name='instructor'
group by Event.id;
我如何使用Django的ORM做到这一点?根据{{3}},我可以使用this Stack Overflow question或the 'regroup' tag in the view。前者因连接的多步性而变得复杂,而后者感觉......复杂。我的直觉是,我应该能够将对应于特定Person
的教师的所有Event
对象附加到该事件,然后在我的视图中循环它们。如果你知道如何做到这一点,我会感激指针。
答案 0 :(得分:2)
以下内容(Django 1.7 +):
from django.db.models import Prefetch
prefetch = Prefetch(
'task_set',
queryset=Task.objects.filter(role__name='instructor')
.select_related('person'), to_attr='instructor_tasks'
)
events = Event.objects.all().prefetch_related(prefetch)
for event in events:
print event
for task in event.instructor_tasks:
print task.person.email
以这种方式使用prefetch_related应该阻止O(n)数据库查询(尽管循环)。
上述查询/循环的Django数据库查询日志输出(具有2个事件的微小数据集):
DEBUG django.db.backends: (0.001) SELECT "myapp_event"."id", "myapp_event"."slug" FROM "myapp_event"; args=()
DEBUG django.db.backends: (0.001) SELECT "myapp_task"."id", "myapp_task"."event_id", "myapp_task"."person_id", "myapp_task"."role_id", "myapp_person"."id", "myapp_person"."email" FROM "myapp_task" INNER JOIN "myapp_role" ON ( "myapp_task"."role_id" = "myapp_role"."id" ) INNER JOIN "myapp_person" ON ( "myapp_task"."person_id" = "myapp_person"."id" ) WHERE ("myapp_role"."name" = 'instructor' AND "myapp_task"."event_id" IN (1, 2)); args=('instructor', 1, 2)
因此,即使事件数量增加,此查询代码也应继续仅发出两个数据库查询。
答案 1 :(得分:1)
我认为你不需要这么复杂的查询。
对于任何特定事件,您可以这样做:
instructors = Task.objects.filter(event=my_event, role__name='instructor').select_related('person').values_list('person__email')
可以使用'讲师'为您提供与该活动的任务相关的所有人员的电子邮件列表。角色。不可否认,这是每个事件的一个查询,而不是一个单独的大量查询,但除非您计划每页显示数百个事件,否则在清晰度方面的权衡可能是值得的。
答案 2 :(得分:1)
在即将推出的Django 1.8中你可以实现GroupConcat表达式,然后查询看起来像:
Event.objects.values('slug').annotate(emails=GroupConcat('task__person__email'))
.values()。annotate()组合将GROUP BY设置为slug,当然GroupConcat实现进行实际聚合。
关于如何编写GroupConcat实现,请查看https://docs.djangoproject.com/en/dev/ref/models/expressions/#writing-your-own-query-expressions
答案 3 :(得分:0)
由于您已熟练使用mysql命令,请尝试从Django执行Raw mysql命令。
答案 4 :(得分:0)
这里通常的解决方案是不尝试优化,只为每个事件执行一次查找:
for event in Event.objects.filter(**some_lookup):
instructors = Person.objects.filter(
task__role__name="instructor",
task__event=event
).values_list("name", flat=True)
print "Event: %s - instructors : %s" % (event.slug, ", ".join(instructors))
如果它成为性能瓶颈,那么就应该找到一个更好的解决方案(可能包括或不包括回退到原始sql)。