首页 > 解决方案 > 从查询集中获取所有多对多对象的有效方法

问题描述

我有类似于以下的模型:

class Tag(models.Model):
    text = models.CharField(max_length=30)

class Post(models.Model):
    title = models.CharField(max_length=30)
    tags = models.ManyToManyField(Tag)

APost可以有很多Tags并且Tags可以与很多关联Posts

我需要的是获取所有帖子的列表以及与每个帖子关联的所有标签。Pandas DataFrame然后我从该数据创建一个。这是我目前的做法:

qs = Post.objects.all().prefetch_related('tags')

tag_df = pd.DataFrame(columns=["post_id", "tags"])
for q in qs:
    tag_df = tag_df.append(
        {
            "post_id": q.pk,
            "tags": list(q.tags.all().values_list("text", flat=True)),
        },
        ignore_index=True,
    )

post_df = pd.DataFrame(qs.values("id", "title"))
final_df = post_df.merge(tag_df, left_on="id", right_on="post_id")

就我需要的数据而言,结果是正确的。问题是它的效率非常低,即使我使用的是运行的查询数量prefetch_related。对于循环的每次迭代,似乎都有一个查询正在访问数据库。

有没有更好、更有效的方法来做到这一点(可能没有循环)?最后我需要的是一个包含所有帖子的数据框以及一个包含每个帖子标签列表的列。

标签: pythondjangopandas

解决方案


通过使用.values_list(..),您将在每次迭代时进行额外的查询。所以这不是很有效。您可以简单地使用已经预取的Tag对象,并获取.text属性:

qs = Post.objects.prefetch_related('tags')

tag_df = pd.DataFrame(columns=['post_id', 'tags'])
for q in qs:
    tag_df = tag_df.append(
        {
            'post_id': q.pk,
            'tags': [t.text for t in q.tags.all()],
        },
        ignore_index=True,
    )

post_df = pd.DataFrame(qs.values('id', 'title'))
final_df = post_df.merge(tag_df, left_on='id', right_on='post_id')

然而,首先制作一个字典列表,然后将它们加载到数据框中一次可能会更有效:

qs = Post.objects.prefetch_related('tags')

data = [
    {'id': q.pk, 'title': q.title, 'tags': [t.text for t in q.tags.all()]}
    for q in qs
]
final_df= pd.DataFrame(data, columns=['id', 'title', 'tags'])

请注意,使用.values(..)or.values_list(..)不是一个好主意。只有在某些情况下,例如在某个值上创建 GROUP BY,这是一个好主意。通常最好使用模型对象,因为它们增加了额外的逻辑层。


推荐阅读