使用Django ORM的Extra()包含重复表
我正在尝试使用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 个回答
我遇到了同样的问题,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语句。
我觉得你缺少了一个叫做选择(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")