Django 查询集:需要优化这组查询的帮助

3
我正在尝试从教育问题记录列表中筛选出一些常见的标签组合。
在这个例子中,我只考虑了两个标签的例子(标签-标签),我应该得到一个结果示例: "point" + "curve"(65条记录) "add" + "subtract"(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 queryset做类似的查询:

    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

逻辑本身是没有问题的,但是由于我对这个平台相当陌生,所以不确定是否有更短的解决方法可以使其更加高效。目前,查询需要大约45秒的时间来比较大约5K X 5K的标签 :(
附加:标签类。
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)
2个回答

2
如果我正确理解了您的问题,我会保持简单,做如下操作:
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个标签,并进一步迭代以按标记过滤所有唯一标记。
当然,也有其他更多的方法可以实现。

1
t.tag = a_tag 这将始终为 True - Burhan Khalid
我尝试实现这个功能。请问为什么会出现语法错误:lambda不能包含赋值? - jdtoh
请问最后的'a_list+b_list'是什么意思?我的代码出现了这个错误 - [unhashable type: 'dict'],是从那一行引起的。 - jdtoh
1
如果a_list和b_list是筛选结果,我只是想创建一个字典,其中q是键,a_list和b_list组合起来作为它的值,即具有相同问题ID的所有标签在一个字典中。 - Pratik Mandrekar

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 字段,你仍然可以通过使用 反向关系 来访问标签,就像它是一个 M2M 字段一样。请查看我的编辑,将反向关系从 tag_set 改为 tags。我做了几个其他编辑,应该能够与你定义的模型一起工作。
如果你没有指定 related_name='tags',那么只需将过滤器和 prefetch_related 中的 tags 更改为 tag_set,就可以开始使用了。

你的模型和我的有些不同,所以我无法实现你的解决方案。我刚刚编辑了问题并添加了Tag类。你认为你能基于这个提供一个解决方案吗?非常感谢你的建议,但是这个模型会对我所有其他代码产生太大的影响。我会记住在将来使用这个的 :) - jdtoh
@jdtoh,您能否编辑您的问题并包含您的“question”模型?从我所看到的内容来看,我的解决方案仍然适用于您。外键具有“反向关系”,这意味着您可以通过“question.objects.tag_set.all()”在“question”上作为集合访问“tags”。此外,按照惯例,模型名称通常以大写字母开头。 - Josh Smeaton
@jdtoh,请看我的编辑,它应该解决了你定义的模型的问题。 - Josh Smeaton
非常感谢你,Josh! 它有效了! 顺便说一下,您可能希望将您的解决方案“combination”更改为“combinations”,以备将来参考。 你救了我很多麻烦,真的感激不尽! 谢谢! - jdtoh
@jdtoh只是好奇,现在需要多长时间才能运行?我想最多也就5-10秒钟吧? - Josh Smeaton
@jdtoh 我本以为会近乎瞬间完成。你在原始版本中执行了成千上万次的查询。很高兴它能够成功。 - Josh Smeaton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接