Django Queryset:在优化这组查询方面需要帮助
Posted
技术标签:
【中文标题】Django Queryset:在优化这组查询方面需要帮助【英文标题】:Django Queryset: Need help in optimizing this set of queries 【发布时间】:2013-01-03 14:29:15 【问题描述】:我正在尝试从教育问题记录列表中筛选出一些常见的标签组合。
对于此示例,我只查看 2-tag 示例 (tag-tag),我应该得到如下结果示例: “点”+“曲线”(65 个条目) “加”+“减”(40 个条目) ...
这是 SQL 语句中期望的结果:
SELECT a.tag, b.tag, count(*)
FROM examquestions.dbmanagement_tag as a
INNER JOIN examquestions.dbmanagement_tag as b on a.question_id_id = b.question_id_id
where a.tag != b.tag
group by a.tag, b.tag
基本上,我们将带有常见问题的不同标签识别到一个列表中,并将它们分组到相同的匹配标签组合中。
我曾尝试使用 django 查询集进行类似的查询:
twotaglist = [] #final set of results
alphatags = tag.objects.all().values('tag', 'type').annotate().order_by('tag')
betatags = tag.objects.all().values('tag', 'type').annotate().order_by('tag')
startindex = 0 #startindex reduced by 1 to shorten betatag range each time the atag changes. this is to reduce the double count of comparison of similar matches of tags
for atag in alphatags:
for btag in betatags[startindex:]:
if (atag['tag'] != btag['tag']):
commonQns = [] #to check how many common qns
atagQns = tag.objects.filter(tag=atag['tag'], question_id__in=qnlist).values('question_id').annotate()
btagQns = tag.objects.filter(tag=btag['tag'], question_id__in=qnlist).values('question_id').annotate()
for atagQ in atagQns:
for btagQ in btagQns:
if (atagQ['question_id'] == btagQ['question_id']):
commonQns.append(atagQ['question_id'])
if (len(commonQns) > 0):
twotaglist.append('atag': atag['tag'],
'btag': btag['tag'],
'count': len(commonQns))
startindex=startindex+1
逻辑工作正常,但是由于我对这个平台很陌生,我不确定是否有更短的解决方法来提高效率。
目前,大约 5K X 5K 的标签比较大约需要 45 秒 :(
插件:标签类
class tag(models.Model):
id = models.IntegerField('id',primary_key=True,null=False)
question_id = models.ForeignKey(question,null=False)
tag = models.TextField('tag',null=True)
type = models.CharField('type',max_length=1)
def __str__(self):
return str(self.tag)
【问题讨论】:
【参考方案1】:如果我正确理解了你的问题,我会让事情变得更简单并做这样的事情
relevant_tags = Tag.objects.filter(question_id__in=qnlist)
#Here relevant_tags has both a and b tags
unique_tags = set()
for tag_item in relevant_tags:
unique_tags.add(tag_item.tag)
#unique_tags should have your A and B tags
a_tag = unique_tags.pop()
b_tag = unique_tags.pop()
#Some logic to make sure what is A and what is B
a_tags = filter(lambda t : t.tag == a_tag, relevant_tags)
b_tags = filter(lambda t : t.tag == b_tag, relevant_tags)
#a_tags and b_tags contain A and B tags filtered from relevant_tags
same_question_tags = dict()
for q in qnlist:
a_list = filter(lambda a: a.question_id == q.id, a_tags)
b_list = filter(lambda a: a.question_id == q.id, b_tags)
same_question_tags[q] = a_list+b_list
这样做的好处是,您可以将其扩展到 N 个标签,方法是在循环中迭代返回的标签以获取所有唯一标签,然后进一步迭代以明智地将它们过滤掉。
肯定还有更多方法可以做到这一点。
【讨论】:
t.tag = a_tag
这将永远是True
我尝试实现这一点。请问我怎么会有语法错误:lambda 不能包含赋值?
我可以知道最终的“a_list+b_list”是做什么的吗?我的代码有这个错误 - [unhashable type: 'dict'] 来自该行
好吧,如果 a_list 和 b_list 是过滤后的结果,我只是想创建一个字典,其中 q 为键,a_list、b_list 组合为其值,即所有标签中具有相同问题 id一个【参考方案2】:
不幸的是,除非涉及外键(或一对一),否则 django 不允许加入。您将不得不在代码中执行此操作。我找到了一种方法(完全未经测试),只需一个查询即可显着缩短执行时间。
from collections import Counter
from itertools import combinations
# Assuming Models
class Question(models.Model):
...
class Tag(models.Model):
tag = models.CharField(..)
question = models.ForeignKey(Question, related_name='tags')
c = Counter()
questions = Question.objects.all().prefetch_related('tags') # prefetch M2M
for q in questions:
# sort them so 'point' + 'curve' == 'curve' + 'point'
tags = sorted([tag.name for tag in q.tags.all()])
c.update(combinations(tags,2)) # get all 2-pair combinations and update counter
c.most_common(5) # show the top 5
上面的代码使用了Counters、itertools.combinations和django prefetch_related,它们应该涵盖了上面可能未知的大部分位。如果上面的代码不能正常工作,请查看这些资源,并进行相应的修改。
如果您没有在 Question
模型上使用 M2M 字段,您仍然可以使用 reverse relations 访问标签,就像它是 M2M 字段一样。请参阅我将反向关系从 tag_set
更改为 tags
的编辑。我进行了一些其他的修改,这些修改应该适用于您定义模型的方式。
如果您不指定related_name='tags'
,则只需在过滤器中更改tags
并将prefetch_related 与tag_set
相关联即可。
【讨论】:
您的模型与我的有些不同,因此我无法实施您的解决方案。我刚刚编辑了问题并添加了标签类。您认为您可以提供基于此的解决方案吗?非常感谢你的建议,但是这个模型会对我所有的其他代码产生太大的影响。我会记住在将来使用它:) @jdtoh 你能用你的question
模型编辑你的问题吗?从我所看到的,我的解决方案应该仍然适合你。外键有一个reverse relation
,这意味着您可以使用question.objects.tag_set.all()
访问tags
作为question
上的集合。此外,按照惯例,模型名称通常以大写字母开头。
@jdtoh 查看我的编辑,它应该可以解决您定义的模型的问题。
非常感谢乔希!有用!顺便说一句,您可能希望将解决方案“组合”编辑为“组合”以供将来参考。你为我省去了很多麻烦,真是太感谢你了!谢谢!
@jdtoh 只是好奇,现在运行需要多长时间?我想最多 5-10 秒?以上是关于Django Queryset:在优化这组查询方面需要帮助的主要内容,如果未能解决你的问题,请参考以下文章
实例具体解释Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化
使用 prefetch_related 优化 Django Queryset 多对多 for 循环
详解Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化
转 实例具体解释DJANGO的 SELECT_RELATED 和 PREFETCH_RELATED 函数对 QUERYSET 查询的优化
转 实例详解Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化