perf(题单): 学生端题目列表少两次 join 一次查询,顺带把并列的 order 排稳
## 排序 学生端 /problem-sets/:id/problems 只按 order 排,没有 tiebreaker,而卡片是按数组 下标编号的(#1 #2 #3)。order 并列时 Postgres 不保证次序,题单 8(3 道题 order 都是 10)、题单 11(2 道并列 4)、题单 14(3 道并列 0)实际就有并列,于是「第 3 题」指 哪道题每次刷新都可能变。后台那条列表一直是 order + id 排的,两边本来就不一致。 补上 asc(id)。教师进度视图里那份题目清单(同一路由文件 :398)有同样的问题,一并补。 实跑:四道题、三道 order 并列,连打 5 次次序完全一致。 ## 载荷 这个接口原来复用 problemListItemSchema,为此要多 join user + user_profile 凑 createdBy、再多查一次标签表凑 tags —— 而题单卡片只渲染题号、标题、难度、分数和 完成标记。tags / submissionNumber / acceptedNumber / createdBy / contestId / allowFlowchart / showFlowchart / hasAstRules 一个都不用,myStatus 甚至写死是 null。 而且 select 的是 schema.problem 整行,题面、样例、答案、ast_rules、flowchart_data、 sql_display 全都白拉回来一遍。 改成 problemSetProblemItemSchema(id / _id / title / difficulty 四个字段),查询相应 收窄:两次 join 去掉,标签那次查询整条去掉,行查询只取四列。每次请求从 4 条查询降到 3 条,其中最大的那条不再拖整行题面。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QqqZwxtXLo2GTqMi51C94D
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
problemListItemSchema,
|
||||
problemSetBadgeSchema,
|
||||
problemSetListSchema,
|
||||
problemSetProblemSchema,
|
||||
@@ -168,44 +167,37 @@ problemsetRoutes.get("/problem-sets/:id/problems", optionalAuth, async (c) => {
|
||||
const [problemSet] = await db.select({ id: schema.problemset.id }).from(schema.problemset)
|
||||
.where(and(eq(schema.problemset.id, id), eq(schema.problemset.visible, true), ne(schema.problemset.status, "draft"))).limit(1)
|
||||
if (!problemSet) return failure(c, 404, "problem-set-not-found", "题单不存在")
|
||||
const rows = await db.select({ link: schema.problemsetProblem, problem: schema.problem, user: schema.user, realName: schema.userProfile.realName })
|
||||
.from(schema.problemsetProblem).innerJoin(schema.problem, eq(schema.problemsetProblem.problemId, schema.problem.id))
|
||||
.innerJoin(schema.user, eq(schema.problem.createdById, schema.user.id))
|
||||
.leftJoin(schema.userProfile, eq(schema.userProfile.userId, schema.user.id))
|
||||
.where(eq(schema.problemsetProblem.problemsetId, id)).orderBy(asc(schema.problemsetProblem.order))
|
||||
const problemIds = rows.map((row) => row.problem.id)
|
||||
const [tagRows, progressRows] = await Promise.all([
|
||||
problemIds.length ? db.select({ problemId: schema.problemTags.problemId, name: schema.problemTag.name }).from(schema.problemTags)
|
||||
.innerJoin(schema.problemTag, eq(schema.problemTags.problemtagId, schema.problemTag.id)).where(inArray(schema.problemTags.problemId, problemIds)) : Promise.resolve([]),
|
||||
c.get("user") ? db.select({ detail: schema.problemsetProgress.progressDetail }).from(schema.problemsetProgress)
|
||||
.where(and(eq(schema.problemsetProgress.problemsetId, id), eq(schema.problemsetProgress.userId, c.get("user")!.id))).limit(1) : Promise.resolve([]),
|
||||
])
|
||||
const tags = new Map<number, string[]>()
|
||||
for (const tag of tagRows) tags.set(tag.problemId, [...(tags.get(tag.problemId) ?? []), tag.name])
|
||||
// 只取卡片要渲染的四列。取 schema.problem 整行会把题面、样例、答案、ast_rules、
|
||||
// flowchart_data、sql_display 一起拉回来,题单页一个都不用。
|
||||
//
|
||||
// order 后面必须再跟一个 tiebreaker:并列时 Postgres 不保证次序,而卡片是按数组
|
||||
// 下标编号的(#1 #2 #3),题单 8 / 11 / 14 实际就存在 order 重复,不定死的话
|
||||
// 「第 3 题」指哪道题每次刷新都可能不一样。后台那条列表一直是这么排的。
|
||||
const rows = await db.select({
|
||||
link: schema.problemsetProblem,
|
||||
problemId: schema.problem.id,
|
||||
displayId: schema.problem.displayId,
|
||||
title: schema.problem.title,
|
||||
difficulty: schema.problem.difficulty,
|
||||
})
|
||||
.from(schema.problemsetProblem)
|
||||
.innerJoin(schema.problem, eq(schema.problemsetProblem.problemId, schema.problem.id))
|
||||
.where(eq(schema.problemsetProblem.problemsetId, id))
|
||||
.orderBy(asc(schema.problemsetProblem.order), asc(schema.problemsetProblem.id))
|
||||
const progressRows = c.get("user")
|
||||
? await db.select({ detail: schema.problemsetProgress.progressDetail }).from(schema.problemsetProgress)
|
||||
.where(and(eq(schema.problemsetProgress.problemsetId, id), eq(schema.problemsetProgress.userId, c.get("user")!.id))).limit(1)
|
||||
: []
|
||||
const completed = objectValue(progressRows[0]?.detail)
|
||||
return success(c, rows.map(({ link, problem, user, realName }) => problemSetProblemSchema.parse({
|
||||
return success(c, rows.map(({ link, problemId, displayId, title, difficulty }) => problemSetProblemSchema.parse({
|
||||
id: link.id,
|
||||
problemsetId: link.problemsetId,
|
||||
problem: problemListItemSchema.parse({
|
||||
id: problem.id,
|
||||
_id: problem.displayId,
|
||||
title: problem.title,
|
||||
submissionNumber: problem.submissionNumber,
|
||||
acceptedNumber: problem.acceptedNumber,
|
||||
difficulty: problem.difficulty,
|
||||
createdBy: sampleUser(user, realName),
|
||||
tags: tags.get(problem.id) ?? [],
|
||||
contestId: problem.contestId,
|
||||
allowFlowchart: problem.allowFlowchart,
|
||||
showFlowchart: problem.showFlowchart,
|
||||
hasAstRules: problem.astRules !== null,
|
||||
myStatus: null,
|
||||
}),
|
||||
problem: { id: problemId, _id: displayId, title, difficulty },
|
||||
order: link.order,
|
||||
isRequired: link.isRequired,
|
||||
score: link.score,
|
||||
hint: link.hint,
|
||||
isCompleted: String(problem.id) in completed,
|
||||
isCompleted: String(problemId) in completed,
|
||||
})))
|
||||
})
|
||||
|
||||
@@ -395,7 +387,8 @@ problemsetRoutes.get("/problem-sets/:id/user-progress", requireTeacher, async (c
|
||||
.orderBy(desc(schema.problemsetProgress.isCompleted), desc(schema.problemsetProgress.progressPercentage), asc(schema.problemsetProgress.joinTime)).limit(limit).offset(offset),
|
||||
db.select({ id: schema.problem.id, _id: schema.problem.displayId, title: schema.problem.title }).from(schema.problemsetProblem)
|
||||
.innerJoin(schema.problem, eq(schema.problemsetProblem.problemId, schema.problem.id))
|
||||
.where(eq(schema.problemsetProblem.problemsetId, id)).orderBy(asc(schema.problemsetProblem.order)),
|
||||
.where(eq(schema.problemsetProblem.problemsetId, id))
|
||||
.orderBy(asc(schema.problemsetProblem.order), asc(schema.problemsetProblem.id)),
|
||||
])
|
||||
const problemMap = new Map(problemRows.map((problem) => [String(problem.id), problem]))
|
||||
const results = rows.map(({ progress, user: progressUser, realName }) => problemSetProgressSchema.parse({
|
||||
|
||||
Reference in New Issue
Block a user