数据查询优化

This commit is contained in:
2026-07-21 19:01:39 +08:00 Unverified
parent b443015943
commit ca675d3226
6 changed files with 243 additions and 66 deletions
+75 -21
View File
@@ -357,12 +357,46 @@ function gradeForRank(rank, cohortSize) {
return 'D';
}
const resultIndexCache = new WeakMap();
const examSummaryCache = new WeakMap();
function resultIndexes(db) {
let indexes = resultIndexCache.get(db);
if (indexes) return indexes;
const publishedScoresBySubject = new Map();
const publishedByRegistration = new Map();
for (const result of db.results) {
if (!result.published) continue;
if (!publishedScoresBySubject.has(result.subjectId)) publishedScoresBySubject.set(result.subjectId, []);
publishedScoresBySubject.get(result.subjectId).push(Number(result.score));
if (!publishedByRegistration.has(result.registrationId)) publishedByRegistration.set(result.registrationId, []);
publishedByRegistration.get(result.registrationId).push(result);
}
for (const scores of publishedScoresBySubject.values()) scores.sort((left, right) => left - right);
indexes = { publishedScoresBySubject, publishedByRegistration };
resultIndexCache.set(db, indexes);
return indexes;
}
function countScoresGreaterThan(sortedScores, score) {
let low = 0;
let high = sortedScores.length;
while (low < high) {
const middle = Math.floor((low + high) / 2);
if (sortedScores[middle] <= score) low = middle + 1;
else high = middle;
}
return sortedScores.length - low;
}
function resultRankInfo(db, result, scoreOverride = result?.score) {
if (!result?.subjectId || !Number.isFinite(Number(scoreOverride))) return { rank: null, cohortSize: 0, rankPercent: null, grade: '' };
const score = Number(scoreOverride);
const peers = db.results.filter(item => item.id !== result.id && item.subjectId === result.subjectId && item.published);
const cohortSize = peers.length + 1;
const rank = 1 + peers.filter(item => Number(item.score) > score).length;
const scores = resultIndexes(db).publishedScoresBySubject.get(result.subjectId) || [];
const cohortSize = scores.length + (result.published ? 0 : 1);
let greater = countScoresGreaterThan(scores, score);
if (result.published && Number(result.score) > score) greater -= 1;
const rank = 1 + Math.max(0, greater);
const rankPercent = Number((rank / cohortSize * 100).toFixed(2));
return { rank, cohortSize, rankPercent, grade: gradeForRank(rank, cohortSize) };
}
@@ -379,15 +413,43 @@ function subjectPassEvaluation(db, result, subject, scoreOverride = result?.scor
return { ...resultRankInfo(db, result, scoreOverride), qualified: Number(scoreOverride) >= passScore, passScore, cutoffRank: null };
}
function examSummaryIndexes(db) {
let indexes = examSummaryCache.get(db);
if (indexes) return indexes;
const examById = new Map(db.exams.map(exam => [exam.id, exam]));
const publishedByRegistration = resultIndexes(db).publishedByRegistration;
const baseByRegistration = new Map();
const totalsByCohort = new Map();
for (const registration of db.registrations) {
if (registration.status !== 'approved') continue;
const exam = examById.get(registration.examId);
if (!exam) continue;
const subjects = exam.subjects.filter(subject => registration.subjectIds.includes(subject.id));
const published = publishedByRegistration.get(registration.id) || [];
const resultsBySubject = new Map(published.map(result => [result.subjectId, result]));
const complete = subjects.length > 0 && subjects.every(subject => resultsBySubject.has(subject.id));
const total = subjects.reduce((sum, subject) => sum + Number(resultsBySubject.get(subject.id)?.score || 0), 0);
const fullScore = subjects.reduce((sum, subject) => sum + Number(subject.fullScore || 0), 0);
const subjectKey = [...registration.subjectIds].sort().join('|');
const cohortKey = `${exam.id}\u0000${subjectKey}`;
const base = { exam, subjects, published, resultsBySubject, complete, total, fullScore, cohortKey };
baseByRegistration.set(registration.id, base);
if (complete) {
if (!totalsByCohort.has(cohortKey)) totalsByCohort.set(cohortKey, []);
totalsByCohort.get(cohortKey).push(total);
}
}
for (const totals of totalsByCohort.values()) totals.sort((left, right) => left - right);
indexes = { baseByRegistration, totalsByCohort };
examSummaryCache.set(db, indexes);
return indexes;
}
function examResultSummary(db, registration) {
const exam = db.exams.find(item => item.id === registration.examId);
if (!exam) return null;
const subjects = exam.subjects.filter(subject => registration.subjectIds.includes(subject.id));
const published = db.results.filter(result => result.registrationId === registration.id && result.published);
const resultsBySubject = new Map(published.map(result => [result.subjectId, result]));
const complete = subjects.length > 0 && subjects.every(subject => resultsBySubject.has(subject.id));
const total = subjects.reduce((sum, subject) => sum + Number(resultsBySubject.get(subject.id)?.score || 0), 0);
const fullScore = subjects.reduce((sum, subject) => sum + Number(subject.fullScore || 0), 0);
const summaryIndexes = examSummaryIndexes(db);
const base = summaryIndexes.baseByRegistration.get(registration.id);
if (!base) return null;
const { exam, subjects, published, resultsBySubject, complete, total, fullScore, cohortKey } = base;
const scoreRatio = fullScore ? total / fullScore * 100 : 0;
const policy = exam.passPolicy === 'score_ratio' ? 'rank_percent' : (exam.passPolicy || 'rank_percent');
const value = Number(exam.passValue ?? 60);
@@ -402,17 +464,9 @@ function examResultSummary(db, registration) {
});
if (complete && policy === 'none') qualified = null;
if (complete && policy === 'rank_percent') {
const subjectKey = [...registration.subjectIds].sort().join('|');
const totals = db.registrations
.filter(item => item.examId === exam.id && item.status === 'approved' && [...item.subjectIds].sort().join('|') === subjectKey)
.map(item => {
const itemResults = db.results.filter(result => result.registrationId === item.id && result.published);
if (!item.subjectIds.every(id => itemResults.some(result => result.subjectId === id))) return null;
return itemResults.filter(result => item.subjectIds.includes(result.subjectId)).reduce((sum, result) => sum + Number(result.score), 0);
})
.filter(item => item != null);
const totals = summaryIndexes.totalsByCohort.get(cohortKey) || [];
cohortSize = totals.length;
rank = 1 + totals.filter(item => item > total).length;
rank = 1 + countScoresGreaterThan(totals, total);
qualified = rank <= Math.max(1, Math.ceil(cohortSize * value / 100));
}