使用 peewee 创建子查询,在子查询结果上使用 `.select`

Posted

技术标签:

【中文标题】使用 peewee 创建子查询,在子查询结果上使用 `.select`【英文标题】:Create subquery using peewee, using `.select` on the subquery results 【发布时间】:2021-12-25 16:25:04 【问题描述】:

我有一个相当复杂的 peewee 查询,看起来像这样:

        SolutionAlias = Solution.alias()
        fields = [
            SolutionAlias.id.alias('solution_id'),
            SolutionAlias.solver.id.alias('solver_id'),
            SolutionAlias.exercise.id.alias('exercise_id'),
        ]

        query = (
            User
            .select(*fields)
            .join(Exercise, JOIN.CROSS)
            .join(Course, JOIN.LEFT_OUTER, on=(Exercise.course == Course.id))
            .join(SolutionAlias, JOIN.LEFT_OUTER, on=(
                (SolutionAlias.exercise == Exercise.id)
                & (SolutionAlias.solver == User.id)
            ))
            .where(
                (Exercise.id << self.get_exercise_ids()),
                (User.id << self.get_user_ids()),
            )
            .group_by(Exercise.id, User.id, SolutionAlias.id)
            .having(
                (SolutionAlias.id == fn.MAX(SolutionAlias.id))
                | (SolutionAlias.id.is_null(True))
            )
            .alias('solutions_subquery')
        )

        full_query_fields = [
            query.c.solver_id,
            query.c.exercise_id,
            Solution.id.alias('solution_id'),
            SolutionAssessment.icon.alias('assessment_icon'),
        ]

        solutions = (
            Solution
            .select(*full_query_fields)
            .join(query, JOIN.RIGHT_OUTER, on=(
                Solution.id == query.c.solution_id
            ))
            .join(SolutionAssessment, JOIN.LEFT_OUTER, on=(
                (Solution.assessment == SolutionAssessment.id)
            ))
        )

这个确实有效,生成以下 SQL 查询:

SELECT 
  "solutions_subquery"."solver_id", 
  "solutions_subquery"."exercise_id", 
  "t1"."id" AS "solution_id",
  "t2"."icon" AS "assessment_icon" 
FROM 
  "solution" AS "t1" 
  RIGHT OUTER JOIN (
    SELECT 
      "t3"."id" AS "solution_id", 
      "t4"."id" AS "solver_id", 
      "t5"."id" AS "exercise_id" 
    FROM 
      "user" AS "t4" CROSS 
      JOIN "exercise" AS "t5" 
      LEFT OUTER JOIN "course" AS "t6" ON ("t5"."course_id" = "t6"."id") 
      LEFT OUTER JOIN "solution" AS "t3" ON (
        ("t3"."exercise_id" = "t5"."id") 
        AND ("t3"."solver_id" = "t4"."id")
      ) 
    WHERE 
      (
        (
          "t5"."id" IN (155, 156, 157)
        ) 
        AND (
          "t4"."id" IN (1, 24, 25, 26, 27, 28)
        )
      ) 
    GROUP BY 
      "t5"."id", 
      "t4"."id", 
      "t3"."id" 
    HAVING 
      (
        (
          "t3"."id" = MAX("t3"."id")
        ) 
        OR ("t3"."id" IS NULL)
      )
  ) AS "solutions_subquery" ON (
    "t1"."id" = "solutions_subquery"."solution_id"
  ) 
  LEFT OUTER JOIN "solutionassessment" AS "t2" ON ("t1"."assessment_id" = "t2"."id")

但我真的不想使用 RIGHT_JOIN,因为 SQLite 不支持它。

当尝试使用子查询 queryJOINSolution 表查询到子查询的结果中时,我收到来自 peewee 的错误。

新查询:

        solutions = (
            query
            .select(*full_query_fields)
            .join(Solution, JOIN.LEFT_OUTER, on=(
                Solution.id == query.c.solution_id
            ))
            .join(SolutionAssessment, JOIN.LEFT_OUTER, on=(
                (Solution.assessment == SolutionAssessment.id)
            ))
        )

生成的查询:

SELECT 
  "solutions_subquery"."solver_id", 
  "solutions_subquery"."exercise_id", 
  "t1"."id" AS "solution_id", 
  "t1"."checker_id", 
  "t1"."state", 
  "t1"."submission_timestamp", 
  "t2"."name" AS "assessment", 
  "t2"."icon" AS "assessment_icon" 
FROM 
  "user" AS "t3" CROSS 
  JOIN "exercise" AS "t4" 
  LEFT OUTER JOIN "course" AS "t5" ON ("t4"."course_id" = "t5"."id") 
  LEFT OUTER JOIN "solution" AS "t6" ON (
    ("t6"."exercise_id" = "t4"."id") 
    AND ("t6"."solver_id" = "t3"."id")
  ) 
  LEFT OUTER JOIN "solution" AS "t1" ON (
    "t1"."id" = "solutions_subquery"."solution_id"
  ) 
  LEFT OUTER JOIN "solutionassessment" AS "t2" ON ("t1"."assessment_id" = "t2"."id") 
WHERE 
  (
    (
      "t4"."id" IN (155, 156, 157)
    ) 
    AND (
      "t3"."id" IN (1, 24, 25, 26, 27, 28)
    )
  ) 
GROUP BY 
  "t4"."id", 
  "t3"."id", 
  "t6"."id" 
HAVING 
  (
    (
      "t6"."id" = MAX("t6"."id")
    ) 
    OR ("t6"."id" IS NULL)
  )

结果:

psycopg2.errors.UndefinedTable: missing FROM-clause entry for table "solutions_subquery"
LINE 1: ...EFT OUTER JOIN "solution" AS "t1" ON ("t1"."id" = "solutions...

During handling of the above exception, another exception occurred:
[Truncated for readability...]

loguru.logger.critical(str(list(solutions.dicts().execute())))
[Truncated for readability...]

peewee.ProgrammingError: missing FROM-clause entry for table "solutions_subquery"
LINE 1: ...EFT OUTER JOIN "solution" AS "t1" ON ("t1"."id" = "solutions...

为什么 peewee 会展平查询?还有其他方法可以使用LEFT_JOIN吗?

【问题讨论】:

【参考方案1】:

最终在文档中找到了Select 函数,这让我可以包装上一个查询:

        solutions = (
            Select(columns=full_query_fields)
            .from_(query)
            .join(Solution, JOIN.LEFT_OUTER, on=(
                Solution.id == query.c.solution_id
            ))
            .join(SolutionAssessment, JOIN.LEFT_OUTER, on=(
                (Solution.assessment == SolutionAssessment.id)
            ))
        )

此解决方案有效。

【讨论】:

以上是关于使用 peewee 创建子查询,在子查询结果上使用 `.select`的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Laravel Eloquent 创建子查询?

如何使用条件 Api 创建子查询

如何将 Flask SQLAlchemy amp;Peewee 的查询结果转换成 json

LEFT OUTER JOIN 在 bigquery 上创建子查询时出错

如何在couchbase服务器中正确创建子查询的索引?

WordPress |帖子查询 |查询帖子类别以创建子类别过滤器并将其应用于我的函数文件中的 Ajax 过滤器