Sql Django-同一个表上的多个联接-结果不正确?
我有以下型号:Sql Django-同一个表上的多个联接-结果不正确?,sql,django,django-orm,Sql,Django,Django Orm,我有以下型号: class Document(models.Model): ... class DocumentAttributes(models.Model): document = models.ForeignKey(Document) key = models.TextField() value = models.TextField() 我想根据属性查询文档。指定的键必须与其中一个值匹配 最好举个例子: self.d1 = document_fa
class Document(models.Model):
...
class DocumentAttributes(models.Model):
document = models.ForeignKey(Document)
key = models.TextField()
value = models.TextField()
我想根据属性查询文档。指定的键必须与其中一个值匹配
最好举个例子:
self.d1 = document_factory(attributes={'a': '1', 'b': '1'})
self.d2 = document_factory(attributes={'a': '2', 'b': '2'})
self.d3 = document_factory(attributes={'a': '2', 'b': '1'})
self.d4 = document_factory(attributes={'a': '3', 'b': '4'})
self.d5 = document_factory(attributes={'a': '3', 'b': '2'})
self.d6 = document_factory(attributes={'a': '1', 'b': '4'})
self.d7 = document_factory(attributes={'a': '2', 'b': '4'})
docs = whitelist_keyvalue_in({'a': ['1', '3'], 'b': ['1', '4']}, doc_qs).all()
文档现在应该包含d1、d4、d6
以下是我的实现:
def whitelist_keyvalue_in(json_obj, doc_qs):
qs = doc_qs
for key in json_obj:
values = [json_obj[key]] if isinstance(json_obj[key], basestring) else json_obj[key]
q_values = Q()
for v in values:
q_values |= Q(value=v)
qs = qs.filter(attributes=DocumentAttributes.objects.filter(key=key).filter(q_values))
print(qs.query)
return qs
出于某种原因,这只返回d1?而且生成的查询并不十分漂亮
你能发现错误吗?有没有更好的方法写这个
SELECT ... FROM "document_document"
INNER JOIN "document_documentattributes" ON ("document_document"."id" = "document_documentattributes"."document_id")
INNER JOIN "document_documentattributes" T3 ON ("document_document"."id" = T3."document_id")
WHERE
("document_documentattributes"."id" = ( SELECT U0."id"
FROM "document_documentattributes" U0
WHERE (U0."key" = 'a' AND (U0."value" = '1' OR U0."value" = '3')))
AND T3."id" = ( SELECT U0."id"
FROM "document_documentattributes" U0
WHERE (U0."key" = 'b' AND (U0."value" = '1' OR U0."value" = '4'))))
如果我自己使用原始查询,则一切正常:
def whitelist_keyvalue_in(json_obj, doc_qs):
names = {key: 'da{}'.format(k_index) for k_index, key in enumerate(json_obj)}
raw_sql = "SELECT da0.document_id as id FROM document_documentattributes as da0 "
for key in json_obj:
if names[key] == 'da0':
continue
raw_sql += ("JOIN document_documentattributes as {0} ON {0}.document_id = da0.document_id "
"".format(names[key]))
for key in json_obj:
where_and = 'WHERE' if names[key] == 'da0' else ' AND'
values = [json_obj[key]] if isinstance(json_obj[key], basestring) else json_obj[key]
values_opts = ' OR '.join("{}.value = '{}'".format(names[key], value) for value in values)
raw_sql += "{} {}.key = '{}' AND ({})".format(where_and, names[key], key, values_opts)
return doc_qs.filter(id__in=(d.id for d in doc_qs.raw(raw_sql)))
其中:
SELECT da0.document_id as id
FROM document_documentattributes as da0
JOIN document_documentattributes as da1 ON da1.document_id = da0.document_id
WHERE da0.key = 'a' AND (da0.value = '1' OR da0.value = '3')
AND da1.key = 'b' AND (da1.value = '1' OR da1.value = '4')
SELECT ... FROM "document_document" WHERE "document_document"."id" IN (1, 4, 6)
我更希望避免中的id__,但无法确定如何从原始查询集获取到常规查询集
如果必须使用原始sql进行此操作,是否有办法避免这两个选择以返回正常查询集?
qs=qs.filter(attributes=DocumentAttributes.objects.filter(key=key).filter(q_值))
位于for循环内,每次应用筛选器时,其行为类似于和条件。因此,最终的查询将是获取a为1或3,b为1或4的文档。这里d1匹配条件并返回,因为它具有a=1
和b=1
在每个键周围使用Q()
和|
可以解决此问题
这一行qs=qs.filter(attributes=DocumentAttributes.objects.filter(key=key.filter(q_值))
将触发两个查询,因为DocumentAttributes.objects.filter(key=key.filter)(q_值)
将被评估以用于qs
查询确定最终找到了正确的方法。生成的原始查询甚至很好看
def whitelist_keyvalue_in(attributes, doc_qs):
qs = doc_qs
for key, values in attributes.iteritems():
values = [values] if isinstance(values, basestring) else values
qs = qs.filter(attributes__key=key, attributes__value__in=values)
return qs
为什么要在“筛选器”中避免使用“id_uu?”@MevinBabu-我已经有了我关心的文档列表。中的id__执行第二个不必要的查询,这也可能是一个缓慢的查询,具体取决于有多少文档。您的意思是说您已经有一个查询返回文档列表,然后要再次筛选此列表以返回与属性匹配的文档?所以你不想做两个不同的查询?对吗?但是你的
函数中的
白名单\u keyvalue\u无论如何都在向db启动一个新的查询?@MevinBabu-不完全是这样。QuerySets存储执行SQL查询所需的信息,直到必须时才会访问数据库。当我使用原始sql查询时,它会立即命中数据库,给出一个PK:s列表。但是,我想返回一个QuerySet(便于后续操作)。在给定PK列表的情况下,返回QuerySet的唯一方法是在中执行id__。现在,我将总是击中数据库两次。我希望有某种方法可以获取一个提供PKs的原始sql,并将其转换为QuerySet,而不必点击db获取PKs(即Django将其用作sql子查询)。希望澄清。你的分析是不正确的。注意:qs.filter返回一个新的过滤器,它以迭代方式构建查询集。您的建议也不正确-每当查询多个属性时,它将返回空集。(请注意,“if key in filter_dic:”永远不会是这种情况。)这部分DocumentAttributes.objects.filter(key=key).filter(q_值)
将得到评估,以用于qs.filter
查询您的右侧。我把事情搞混了。我已经编辑了答案。仍然不正确。“它的行为就像一个条件”这正是我想要的。“a是1或3,b是1或4”也正是我想要的,但请注意,d1不是唯一符合此标准的。它真的应该返回:d1、d4、d6。”将引发两个查询,因为“不,不会”。查询集的好处(如我前面所述)是,您可以处理抽象查询,而无需向数据库触发sql语句,直到您需要它们为止。例如:qs=Documents.objects.all()。不会执行任何查询。如果我接着执行qs2=qs.filter(something=something)——它仍然不会执行任何查询。只有当我实际获取数据时,才会发送查询。例如doc=qs2.all()[0]。