2016-04-05 53 views
0

我有以下型号:Django - 多个连接在同一个表上 - 错误的结果?

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__in但无法弄清楚如何从原始查询设置为常规查询集。

如果我必须为此使用原始的sql,有没有办法可以避免这两个选择返回一个正常的查询集?

+0

为什么要避免使用'id__in'过滤器? –

+0

@MevinBabu - 我已经有我关心的文档列表。 id__in执行第二个不必要的查询,这也可能是一个缓慢的查询,具体取决于有多少个文档。 –

+0

你的意思是说你已经有一个查询返回你一个文档列表,然后你想再次过滤这个列表来返回匹配属性的文档?因此你不想做两个不同的查询?对? –

回答

0

OK终于想通了如何得到它的权利。生成的原始查询甚至很好看。

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 
0

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匹配条件并返回,因为它具有a = 1b = 1

围绕每个键使用Q()|应解决此问题。

此行qs = qs.filter(attributes=DocumentAttributes.objects.filter(key=key).filter(q_values))将触发两个查询,因为DocumentAttributes.objects.filter(key=key).filter(q_values)将得到评估在qs查询中使用

+0

你的分析不正确。请注意:qs.filter返回一个新的过滤器,它迭代地构建一个查询集。您的建议也不正确 - 每次查询多个属性时都会返回空集。 (并注意“if key in filter_dic:”永远不会是这种情况。) –

+0

这部分'DocumentAttributes.objects.filter(key = key).filter(q_values)'将被计算用于'qs .filter'查询 –

+0

耶你的权利。我把事情弄混了。我编辑了答案。 –

相关问题