相当于Django ORM中的group_concat

时间:2014-12-08 13:10:41

标签: python django orm

我们再次尝试构建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 questionthe 'regroup' tag in the view。前者因连接的多步性而变得复杂,而后者感觉......复杂。我的直觉是,我应该能够将对应于特定Person的教师的所有Event对象附加到该事件,然后在我的视图中循环它们。如果你知道如何做到这一点,我会感激指针。

5 个答案:

答案 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命令。

https://docs.djangoproject.com/en/dev/topics/db/sql/

答案 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)。