【问题标题】:Django: Filtering via SQL, not PythonDjango:通过 SQL 过滤,而不是 Python
【发布时间】:2020-03-07 22:42:18
【问题描述】:

我创建了以下上下文变量context["genders"]context["ages"]。 目前,Python 在#Filtering 下做了很多工作,而我认为在#Query 下会更好。 然而,这就是我目前挣扎的地方。你知道如何通过 SQL 实现 #Query 部分的预过滤吗?

请不要将int(answer_obj.answer) 视为answerTextField

# Query
responses = Response.objects.filter(
    survey__event=12, survey__template=settings.SURVEY_POST_EVENT
).order_by("-created")

# Filtering
filtered_responses = []
for response in responses:
    for answer_obj in response.answers.all():
        if (
            answer_obj.question.focus == QuestionFocus.RECOMMENDATION_TO_FRIENDS
            and int(answer_obj.answer) >= 8
        ):
            filtered_responses.append(response)


# Context
gender_list = []
age_list = []
for response in filtered_responses:
    for answer_obj in response.answers.all():
        # Here a list of all the genders that gave that answer:
        if answer_obj.question.focus == QuestionFocus.GENDER:
            gender_list.append(answer_obj.answer)

        # Here a list of all the ages that gave that answer:
        if answer_obj.question.focus == QuestionFocus.AGE:
            age_list.append(answer_obj.answer)

context["genders"] = gender_list
context["ages"] = age_list

models.py

class Answer(TimeStampedModel):
    question = models.ForeignKey(
        "surveys.Question", on_delete=models.CASCADE, related_name="answers"
    )
    response = models.ForeignKey(
        "Response", on_delete=models.CASCADE, related_name="answers"
    )
    answer = models.TextField(verbose_name=_("Answer"))
    choices = models.ManyToManyField(
        "surveys.AnswerOption", related_name="answers", blank=True
    )

class Response(TimeStampedModel):
    class Language(Choices):
        CHOICES = settings.LANGUAGES

    survey = models.ForeignKey(
        "surveys.Survey", on_delete=models.CASCADE, related_name="responses"
    )
    order = models.ForeignKey(
        "orders.Order",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="response",
    )
    attendee = models.ForeignKey(
        "attendees.Attendee",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="response",
    )
    total_time = models.PositiveIntegerField(
        null=True, blank=True, verbose_name=_("Total time")
    )
    ip_address = models.GenericIPAddressField(null=True, verbose_name=_("IP Address"))
    language = models.CharField(
        max_length=Language.get_max_length(),
        choices=Language.CHOICES,
        verbose_name=_("Language"),
    )

class Question(TimeStampedModel):
    survey = models.ForeignKey(
        "surveys.Survey", on_delete=models.CASCADE, related_name="questions"
    )
    question_set = models.ForeignKey(
        "QuestionSet", on_delete=models.CASCADE, related_name="questions"
    )
    title = models.CharField(max_length=100, verbose_name=_("Title"))
    help_text = models.TextField(null=True, blank=True, verbose_name=_("Help text"))
    type = models.CharField(
        max_length=QuestionType.get_max_length(),
        choices=QuestionType.CHOICES,
        verbose_name=_("Question type"),
    )
    focus = models.CharField(
        max_length=QuestionFocus.get_max_length(),
        choices=QuestionFocus.CHOICES,
        verbose_name=_("Question focus"),
    )
    required = models.BooleanField(default=False, verbose_name=_("Is required?"))
    position = models.PositiveSmallIntegerField(
        null=True, blank=True, verbose_name=_("Position")
    )

    # Translatable fields
    i18n = TranslationField(fields=("title", "help_text"))

    class Meta:
        ordering = ("position", "pk")

【问题讨论】:

  • 不,它按预期工作,我只是想减少 Python 的工作并将其移至 SQL 查询部分。我基本上是想在这里扩展那部分responses = Response.objects.filter,这样我就可以“删除”Python #Filtering
  • 部分。但是,据我了解,这会改进 Python 过滤,但不会将逻辑带入 SQL 查询。
  • 那你为什么不能这样做呢?您已经知道如何跨关系进行过滤,那么问题出在哪里?

标签: python django


【解决方案1】:

过滤 在我看来,您想要所有回复,其中针对朋友推荐的问题的答案高于 8。是否预计您可能不止一次地将相同的回复附加到过滤后的回复中,还是只有一个此类问题?我认为您可以将其重写如下:

filtered_response = responses.filter(
    answers__question__focus=QuestionFocus.RECOMMENDATION_TO_FRIENDS
).annotate(
    answer_num=Cast("answers__answer", IntegerField()),
).filter(
    answer_num__gt=8,
)

并填充上下文

context["genders"] = Answer.objects.filter(
    response_id__in=filtered_response.values_list("id", flat=True),
    question__focus=QuestionFocus.GENDER,
).values_list("answer", flat=True)
context["ages"] = Answer.objects.filter(
    response_id__in=filtered_response.values_list("id", flat=True),
    question__focus=QuestionFocus.AGE,
).values_list("answer", flat=True)

这应该允许您避免触发查询以迭代 response.answers.all(),并希望获得相同的结果。

【讨论】:

  • 不知道为什么你需要这里的注释。为什么不只是.filter(..., answers__answer__gt=8)
  • 说实话,我现在手头没有 django 可以测试,虽然我觉得 应该 在没有注释的情况下工作,但我有点担心直接比较文本字段,如果数字越大,它可能可能导致例如“9”大于“81”
  • 第一句话总结把它带到了重点@mfrackowiak。 Daniel Roseman 我认为这行不通,因为它首先必须“转换”为整数字段? “是否预计您可能不止一次地将相同的回复附加到过滤后的回复中,还是只有一个此类问题?” > 每个响应只能附加一次。
  • @JoeyCoder 这个查询应该没问题,只是在你的代码中你在嵌套循环中附加了来自外部循环的变量,所以理论上如果有更多的问题满足 if我可以想象 filters_responses 列表中的重复项。
  • 好的明白了。应用上面的代码时不知何故出错:django.db.utils.ProgrammingError: operator does not exist: text > integer LINE 1: ...ndation_to_friends' AND "surveys_answer"."answer" > 8) ORDER... ^ HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
猜你喜欢
  • 1970-01-01
  • 2011-11-30
  • 2019-08-16
  • 2014-01-22
  • 2020-10-24
  • 1970-01-01
  • 1970-01-01
  • 2021-01-11
  • 2021-08-26
相关资源
最近更新 更多