如何在 Django 中使用相关模型和计数构建单个查询

问题描述 投票:0回答:1

我已经有很多年没有使用 Django 了,今天尝试修改一些旧的代码库。

我有模型:

class MetaTemplate(CreatedModifiedBase):
    type = models.CharField(max_length=200)
class Project(CreatedModifiedBase):
    name = models.CharField(max_length=200, unique=True)
    members = models.ManyToManyField(
        User,
        through=ProjectMembership,
        related_name="annotation_projects_as_member",
        blank=True,
    )
    metatemplate = models.ForeignKey(
        MetaTemplate, on_delete=models.SET_NULL, related_name="project", null=True
    )
class Dataset(CreatedModifiedBase):
    name = models.CharField(max_length=200)
    project = models.ForeignKey(
        Project, related_name="datasets", on_delete=models.CASCADE
    )
class Task(CreatedModifiedBase):
    dataset = models.ForeignKey(
        Dataset, related_name="tasks", on_delete=models.CASCADE, null=True
    )

class Completion(CreatedModifiedBase):
    task = models.ForeignKey(Task, related_name="completions", on_delete=models.CASCADE)
    annotator = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        related_name="annotation_completions",
        null=True,
    )

我有一个方法可以返回未完成任务的数量。

    def get_incomplete_tasks(self, user, project_id, target_completion_count) -> int:
        return (
            Task.objects.annotate(counts=Count("completions"))
            .filter(
                counts__lt=target_completion_count,
                dataset__project=project_id,
            )
            .exclude(completions__annotator=user)
            .prefetch_related(
                "completions", "completions__annotator", "dataset__project"
            )
            .count()
        )

现在我想修改这个方法,并在单个查询中返回每个项目的未完成任务数,而不需要用户传递的project_id和target_completion_count。所以逻辑基本相同,但是每个项目都在一个查询中。

我写了这段代码:


            t = Task.objects.filter(
                dataset__project=OuterRef('id')
            )
            completion_count_subquery = t.exclude(completions__annotator=user).prefetch_related(
                "completions", "completions__annotator").order_by().annotate(
                    counts=Func(F('id'), function='Count')).values('counts')
            objects = Project.objects.all().annotate(task_count=Subquery(completion_count_subquery)).prefetch_related("members", "metatemplate")

但是我无法以正确的方式包含

counts__lt=target_completion_count
部分。

也尝试过这个查询

            t = Task.objects.all().annotate(counts=Count("completions")).filter(dataset__project = OuterRef('id'), counts__lt=OuterRef("target_completion_count")).order_by().values("completions")
            completion_count_subquery = t.exclude(completions__annotator=user)
            objects = Project.objects.all().annotate(task_count=Subquery(completion_count_subquery)).prefetch_related("members", "metatemplate")

但它以错误结束

django.db.utils.ProgrammingError: more than one row returned by a subquery used as an expression

django django-queryset django-annotate django-aggregation
1个回答
0
投票

代码的修订版本,以避免由于返回多行而出现

programmingError

因此,为了实现在单个查询中返回每个项目的未完成任务数量的目标,我们需要调整您的方法以有效地使用 Django 的 ORM 功能。这是您的代码的修订版本,我认为应该可以工作。

具体方法如下:

请随意删除我为向您解释我所做的事情而写的评论。

from django.db.models import Count, Q, Subquery, OuterRef

# Assuming 'target_completion_count' is a field in the Project model
t = Project.objects.annotate(
    incomplete_tasks_count=Subquery(
        Task.objects.filter(
            dataset__project=OuterRef('pk'),
            completions__annotator=user,
            completions__count__lt=OuterRef('target_completion_count')
        ).annotate(
            completions_count=Count('completions')
        ).filter(
            completions_count__lt=OuterRef('target_completion_count')
   # We use [:1] to ensure that the subquery returns only one row, which is necessary to avoid the ProgrammingError you encountered     
).values('completions_count')[:1],
        output_field=models.IntegerField()
    )
).prefetch_related("members", "metatemplate")

注意:请注意,

target_completion_count
应该是项目模型中的一个字段才能正常工作。如果不是,您需要相应地调整查询以提供每个项目的目标完成计数。

© www.soinside.com 2019 - 2024. All rights reserved.