Django - 同一 table 上的多个连接 - 结果不正确?
Django - multiple joins on same table - incorrect result?
我有以下型号:
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
出于某种原因,这只有 returns 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__in 但不知道如何从原始查询集到常规查询集。
如果我必须为此使用原始 sql,有没有办法避免两次选择 return 普通查询集?
你的
qs = qs.filter(attributes=DocumentAttributes.objects.filter(key=key).filter(q_values))
在 for 循环内,每次应用过滤器时,它的行为类似于 and
条件。因此,最终查询将是使用 a is 1 or 3 and b is 1 or 4
获取文档。这里 d1 匹配条件和 return 因为它有 a = 1
和 b = 1
.
在每个键周围使用 Q()
和 |
应该可以解决这个问题。
这一行 qs = qs.filter(attributes=DocumentAttributes.objects.filter(key=key).filter(q_values))
将触发两个查询,因为 DocumentAttributes.objects.filter(key=key).filter(q_values)
将被评估用于 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
我有以下型号:
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
出于某种原因,这只有 returns 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__in 但不知道如何从原始查询集到常规查询集。
如果我必须为此使用原始 sql,有没有办法避免两次选择 return 普通查询集?
你的
qs = qs.filter(attributes=DocumentAttributes.objects.filter(key=key).filter(q_values))
在 for 循环内,每次应用过滤器时,它的行为类似于 and
条件。因此,最终查询将是使用 a is 1 or 3 and b is 1 or 4
获取文档。这里 d1 匹配条件和 return 因为它有 a = 1
和 b = 1
.
在每个键周围使用 Q()
和 |
应该可以解决这个问题。
这一行 qs = qs.filter(attributes=DocumentAttributes.objects.filter(key=key).filter(q_values))
将触发两个查询,因为 DocumentAttributes.objects.filter(key=key).filter(q_values)
将被评估用于 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