使用select_related和extra子句

1 投票
3 回答
1362 浏览
提问于 2025-04-16 01:30

我想在一个查询集中进行一些额外的选择,并希望通过使用select_related方法将所需的表添加到查询的表池中,以便能使用'__'语法。

这里有一个简单模型的例子:

from django.db import models

# Create your models here.

class testA(models.Model):
    code = models.TextField(unique = True)
    date = models.DateTimeField(auto_now_add = True)

class testB(models.Model):
    text = models.TextField()
    a = models.ForeignKey(testA)

这是我想要构建的查询:

SELECT (extract(hour from testa.date)) AS hour, testb.text FROM testb INNER JOIN testa ON (testb.a_id = testa.id)

下面是我在Python中构建它的方式:

testB.objects.all().select_related('a').extra(select = {'hour' : 'extract(hour from testa.date)'}).values('hour','text')

但是,当Django发现我没有使用“testa”表(因为有'values'语句)时,它就会去掉select_related。因此,生成的SQL查询会失败:

SELECT (extract(hour from testa.date)) AS "hour", "testb"."text" FROM "testb"

如果我去掉“values”语句,它就能正常工作:

SELECT (extract(hour from testa.date)) AS "hour", "testb"."id", "testb"."text", "testb"."a_id", "testa"."id", "testa"."code", "testa"."date" FROM "testb" INNER JOIN "testa" ON ("testb"."a_id" = "testa"."id")

但我必须加上values语句,因为我想进行聚合,比如“按日期中的小时对b对象进行计数”:

testB.objects.all().select_related('a').extra(select = {'hour' : 'extract(hour from testa.date)'}).values('hour').annotate(count = Count('pk'))

那么,有什么好的方法来实现这个呢?“按另一个对象中的某个东西对对象进行分组计数”?或者有没有办法“强制”Django保留“select_related”表,即使它认为这些表是没用的?

附注:我知道我可以使用extra语句的“tables”参数,但那样我就得自己重写连接,而我想利用Django的ORM。

3 个回答

0

我不能直接回答你的主要问题,但值得注意的是,select_related__这种写法没有关系。select_related只是一个优化手段,它会返回一些额外的相关对象,如果需要的话会在查询中添加连接。但使用双下划线的写法来查询相关表,无论有没有select_related都能正常工作。

0

我在本地也遇到了同样的错误:一切正常,直到“values”被添加上去,然后Django就无法正确放置FROM子句了。我打算把这个例子发到django-users小组,看看有没有懂行的人能确认这是不是个bug,或者有没有什么快速的解决办法。

1

我开发了一个Django应用,专门用来解决这类问题:django-cube。它的基本思路是模拟一个多维数据库,这样可以更方便地进行数据汇总计算。

你需要的功能('__hour':一个用于查找小时的字段)还没有实现,不过实现起来大概只需要15分钟。所以接下来看看它是怎么工作的,如果符合你的需求,给我发个消息,我会帮你实现这个功能。

主页和API文档上的示例还没有更新(几天后会更新),不过这些代码片段是最新的。如果你想试试,下面是用django-cube解决这个问题的方法:

#install the app first ...
from cube.models import Dimension, Cube

class MyCube(Cube):
    #declare a dimension called 'hour_a',
    #that is related to the field 'a__date__hour'.
    hour_a = Dimension(field='a__date__hour')

    #declare how to calculate the aggregation on a queryset
    @staticmethod
    def aggregation(queryset):
        return queryset.count()

然后,有多种方法可以计算结果,具体可以查看代码片段……你可以比如使用:

cube(testB.objects.all()).measure_dict('hour_a', full=False)

这样会返回类似于:

{   
    12: {measure: 889},
    13: {measure: 6654},
    14: {measure: 77},
    #<hour>: <count>
}

另外,不要下载推荐的版本,建议从源代码(0.3分支)获取。

我不太清楚你的具体需求,这个工具可能对你来说有点重(最初是为了数据可视化而开发的)。

撰写回答