如何有效地从单个Django模型查询一对对象?

时间:2018-07-10 15:28:55

标签: django django-models django-queryset

我有一个Track模型,目前我通过模型ID进行嵌套循环以获取该对,然后将其传递给一个函数以计算这两个非等效轨道对象之间的相似性

track_set = Track.objects.all()

track_ids = [track.id for track in track_set]
pointer_a = 0
pointer_b = 1

for pointer_a in range(len(track_ids) - 1):
    for pointer_b in range(pointer_a + 1, len(track_ids)):
        track_a = Track.objects.get(pk=track_ids[pointer_a])
        track_b = Track.objects.get(pk=track_ids[pointer_b])
        counter += 1
        count_it_sim(track_a, track_b)

我认为我获取对象的方式不是很有效,有什么方法可以优化它吗?

编辑:此count_it_sim将计算track_a和track_b之间的相似度值,我需要针对Track模型中的所有对计算相似度值
models.py

class Tag(models.Model):
    name = models.CharField(max_length=255, unique=True)

class Tagged(models.Model):
    track = models.ForeignKey(Track, on_delete=models.CASCADE)
    tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
    frequency = models.IntegerField(
        default=0,
        validators=[MinValueValidator(0)],
    )
    class Meta:
        unique_together = ('track', 'tag')

class Track(models.Model):
    track_id = models.CharField(max_length=24)
    title = models.CharField(max_length=120)
    link = models.URLField(max_length=120, blank=True)
    tags = models.ManyToManyField(Tag, through='Tagged', blank=True)
    similarity = models.ManyToManyField(
        'self',
        blank=True,
        through='Similar',
        related_name='similar_to',
        symmetrical=False
    )
    users = models.ManyToManyField(User, through='PlayTrack', blank=True)

class Similar(models.Model):
    track1 = models.ForeignKey(Track, on_delete=models.CASCADE, related_name='track1')
    track2 = models.ForeignKey(Track, on_delete=models.CASCADE, related_name='track2')
    similarity = models.FloatField(
        validators=[MinValueValidator(0), MaxValueValidator(1)],
    )

count_it_sim会做的是,它将通过关联实体(即标记模型)获得所有标签的track_a和track_b频率,并进行计算以获取track_a和track_b之间的相似性值

def count_it_sim(track_a: Track, track_b: Track):
    tag_set = Tag.objects.all()
    part1 = 0
    part2 = 0
    part3 = 0
    for tag in tag_set:
        try:
            freq_tag_of_track_a = Tagged.objects.get(track=track_a, tag=tag).frequency
        except Tagged.DoesNotExist:
            continue
        try:
            freq_tag_of_track_b = Tagged.objects.get(track=track_b, tag=tag).frequency
        except Tagged.DoesNotExist:
            continue
        part1 += freq_tag_of_track_a * freq_tag_of_track_b
        part2 += freq_tag_of_track_a ** 2
        part3 += freq_tag_of_track_b ** 2
    try:
        it_sim = part1 / (math.sqrt(part2) * math.sqrt(part3))
    except ZeroDivisionError:
        it_sim = None

编辑2:在count_it_sim上,我不遍历Tag.objects.all()中的所有标签,而是只查询Tagged中存在的那些标签,并且结果更快比以前的代码,这是我当前的代码

def count_it_sim(track_a: Track, track_b: Track):
    filtered_tagged = Tagged.objects.filter(Q(track=track_a) | Q(track=track_b))
    tag_ids = filtered_tagged.values_list('tag', flat=True).distinct()
    part1 = 0
    part2 = 0
    part3 = 0
    for tag_id in tag_ids:
        try:
            freq_tag_of_track_a = filtered_tagged.get(track=track_a, tag__id=tag_id).frequency
        except Tagged.DoesNotExist:
            freq_tag_of_track_a = 0
        try:
            freq_tag_of_track_b = filtered_tagged.get(track=track_b, tag__id=tag_id).frequency
        except Tagged.DoesNotExist:
            freq_tag_of_track_b = 0
        part1 += freq_tag_of_track_a * freq_tag_of_track_b
        part2 += freq_tag_of_track_a ** 2
        part3 += freq_tag_of_track_b ** 2
    try:
        it_sim = part1 / (math.sqrt(part2) * math.sqrt(part3))
    except ZeroDivisionError:
        it_sim = None

编辑3:模型中有一些更改。现在不用计算frequency中每个tag的{​​{1}},而是通过计算有多少track标记了{{1} }和特定的frequency。这是更新

users

和count_it_sim函数变为

track

2 个答案:

答案 0 :(得分:3)

  

我认为我获取对象的方式不是很有效,有什么方法可以优化它吗?

好吧,您已经有了对象(在track_set中),因此您无需再次获取它们;您只需要获取对象对即可。

  

我需要的是(1,2)(1,3)(1,4)(2,3)(2,4)(3,4)

为此,您可以使用itertools.combinations

import itertools

for a,b in itertools.combinations(track_set, 2):
   count_it_sim(a, b)

您将必须确保以正确的顺序从数据库中获取对象;因为不能保证如何退回物品:

  

如果查询未指定顺序,则返回结果   从数据库以未指定的顺序。特定的顺序是   仅在通过一组唯一的字段进行订购时才能保证   识别结果中的每个对象。

在您的情况下,似乎您需要按主键顺序使用它们;所以我将初始查询修改为:

track_set = Track.objects.order_by('pk')

queryset documentation包含有关order_by的详细信息,而model reference包含有关指定默认顺序的详细信息。

答案 1 :(得分:1)

根据我们在评论中的讨论,目的是使用最少数量的数据库查询来获取所需的数据,并且该结构应易于允许进一步的数据处理。

我们将尝试在以下字典结构中获取与每个Tagged.frequency对应的所有Track数据:

{
    track_id1: list(all_related_frequencies), 
    track_id2: list(all_related_frequencies),
}

这个想法是在名为Track的属性中获取所有Tagged实例及其prefetch的相关prefetched_tagged实例。

Track的每个实例都有一个属性tagged_set,该属性允许reverse access到与其相关的所有Tagged实例。这是预取它们的查询:

from django.db.models import Prefetch

the_data = (
    Track.objects.prefetch_related(
        Prefetch(
            'tagged_set',
            to_attr='prefetched_tagged',
        )
    ).all()
)

现在,我们将the_data的所有实例保存在Track变量中,其中每个实例都有一个属性prefetched_tagged,其中包含所有list实例的Tagged与每个Track实例相关。

通过以下字典理解,我们迭代the_data变量以创建一个以所有Track.track_id为键的字典。每个键都有一个list,因为它的值将包含所有与其相关的Tagged.frequency

要创建列表,我们将在字典理解中使用列表理解:

result = {
    each_track.track_id: [
        each_tagged.frequency for each_tagged in each_track.prefetched_tagged
    ] for each_track in the_data
}

现在,变量result在我们需要的结构中包含数据,以便进一步操作它们。为了将所有数据库数据加载到内存中,需要2次数据库命中。这是整个代码,也可以衡量数据库的点击量:

from django import db
from django.db.models import Prefetch

db.reset_queries()    

the_data = (
    Track.objects.prefetch_related(
        Prefetch(
            'tagged_set',
            to_attr='prefetched_tagged',
        )
    ).all()
)

result = {
    each_track.track_id: [
        each_tagged.frequency for each_tagged in each_track.prefetched_tagged
    ] for each_track in the_data
}

print(result)
print ("Queries Used: {0}".format(len(db.connection.queries))