使用Django ORM的Extra()包含重复表

8 投票
2 回答
6099 浏览
提问于 2025-04-16 22:28

我正在尝试使用Django的ORM来实现一个简单的三元组存储。我希望能够搜索任意复杂的三元组模式(就像使用SparQL那样)。

为此,我打算使用.extra()这个方法。不过,尽管文档提到它理论上可以处理对同一表的重复引用,并自动为重复的表引用创建别名,但我发现实际上并不是这样。

举个例子,假设我在我的“triple”应用中有以下模型:

class Triple(models.Model):
    subject = models.CharField(max_length=100)
    predicate = models.CharField(max_length=100)
    object = models.CharField(max_length=100)

而我在数据库中存储了以下三元组:

subject predicate object
bob has-a hat .
bob knows sue .
sue has-a house .
bob knows tom .

现在,假设我想查询bob认识的所有有房子的人名。在SQL中,我只需这样做:

SELECT t2.subject AS name
FROM triple_triple t1
INNER JOIN triple_triple t2 ON
    t1.subject = 'bob'
AND t1.predicate = 'knows'
AND t1.object = t2.subject
AND t2.predicate = 'has-a'
AND t2.object = 'house'

我不太确定用Django的ORM应该怎么写,不过我觉得大概是这样的:

q = Triple.objects.filter(subject='bob', predicate='knows')
q = q.extra(tables=['triple_triple'], where=["triple_triple.object=t1.subject AND t1.predicate = 'has-a' AND t1.object = 'house'"])
q.values('t1.subject')

不幸的是,这样做会出现错误“DatabaseError: no such column: t1.subject”。

运行print q.query显示:

SELECT "triple_triple"."subject" FROM "triple_triple" WHERE ("triple_triple"."subject" = 'bob' AND "triple_triple"."predicate" = 'knows'
AND triple_triple.object = t1.subject AND t1.predicate = 'has-a' AND t1.object = 'house')

这似乎表明我对.extra()的调用中的tables参数被忽略了,因为没有任何地方插入第二个对triple_triple的引用。

这是为什么呢?在使用Django的ORM时,如何正确地引用同一表中记录之间的复杂关系呢?

补充:我找到了一段有用的代码片段,可以通过.extra()包含自定义SQL,以便在模型管理器中使用。

2 个回答

0

我遇到了同样的问题,Django会对我的表名加上反引号,这样我就不能手动添加别名了;结果生成的FROM语句看起来是这样的:

"mytable" AS T100

同时,如果表已经被提到,Django又不会自动为你创建别名;它会忽略这些表,只是在WHERE条件中添加内容,就好像这些条件是指向原始表一样。

Django 1.8的文档建议使用.extra()方法可以为你创建别名:

https://docs.djangoproject.com/en/1.8/ref/models/querysets/#django.db.models.query.QuerySet.extra

但在我的查询中似乎并不是这样,可能是因为原始表是左外连接的一部分,而不是简单的FROM x,y,z语句。

15

我觉得你缺少了一个叫做选择(select)的参数(用于额外的方法)。

这个方法看起来是有效的:

qs = Triple.objects.filter(subject="bob", predicate="knows").extra(
          select={'known': "t1.subject"},
          tables=['"triple_triple" AS "t1"'],
          where=['''triple_triple.object=t1.subject 
                    AND t1.predicate="has-a" AND t1.object="'''])

qs.values("known")

撰写回答