如何关联MongoDB三个集合生成指定结构的考试成绩CSV表格
现有MongoDB集合结构
users集合:
{ "_id": { "$oid": String }, "username": String, }
marks集合:
{ "_id": { "$oid": String }, "user_id": String, "exam_id": String, "question_id": String, "mark": String }
questions集合:
{ "_id": { "$oid": String }, "exam_id": String, "text": String, }
需求说明
已知exam_id的取值,需要导出如下结构的CSV文件:
| question1.text | question2.text | ... | |
|---|---|---|---|
| user1.username | mark | mark | ... |
| user2.username | mark | mark | ... |
| ... | ... | ... | ... |
现有实现代码
const ws = fs.createWriteStream("result.csv"); const fastcsv = require("fast-csv"); const db = MongoClient.connect('mongodb://localhost:27017', { useUnifiedTopology: true }).db('exam'); db.collection('users') .aggregate([ { $lookup: { from: 'marks', localField: '_id', foreignField: 'user_id', as: 'marks_doc' } } ]) //.find({'exam_id': exam_id}) .toArray(function(err, doc) { if (err) throw err; console.log(doc); fastcsv.write(doc, {headers: true}).on("finish", function() { console.log('done!'); }).pipe(ws); db.close(); });
调整方案
核心调整点
- 先查询目标
exam_id对应的所有题目,建立question_id到题目标题text的映射,同时确定CSV表头顺序 - 关联
marks集合时增加过滤条件,只保留当前exam_id对应的分数记录 - 将查询到的用户+分数数据转换为CSV要求的行格式,每行第一列为用户名,剩余列按题目顺序对应分数,未作答题目默认填空值避免列错位
完整调整后代码
const fs = require('fs'); const ws = fs.createWriteStream("result.csv"); const fastcsv = require("fast-csv"); const { MongoClient } = require('mongodb'); // 替换为实际的exam_id取值 const TARGET_EXAM_ID = '你的目标exam_id'; MongoClient.connect('mongodb://localhost:27017', { useUnifiedTopology: true }, async (err, client) => { if (err) throw err; const db = client.db('exam'); // 1. 获取当前考试所有题目,生成表头和id到题目的映射 const questions = await db.collection('questions').find({ exam_id: TARGET_EXAM_ID }).toArray(); const qIdToText = {}; const headers = ['用户名']; questions.forEach(q => { const qId = q._id.toString(); qIdToText[qId] = q.text; headers.push(q.text); }); // 2. 关联查询用户和对应考试的分数 const userMarks = await db.collection('users').aggregate([ { $lookup: { from: 'marks', let: { userId: '$_id' }, pipeline: [ { $match: { $expr: { $eq: ['$user_id', '$$userId'] }, exam_id: TARGET_EXAM_ID } } ], as: 'marks_doc' } } ]).toArray(); // 3. 转换为CSV要求的行格式 const csvRows = userMarks.map(user => { const row = { '用户名': user.username }; user.marks_doc.forEach(mark => { const qText = qIdToText[mark.question_id.toString()]; if (qText) row[qText] = mark.mark; }); // 补全未作答题目为空值 headers.slice(1).forEach(qText => { if (!row[qText]) row[qText] = ''; }); return row; }); // 4. 写入CSV文件 fastcsv.write(csvRows, { headers: headers }).on("finish", function() { console.log('done!'); client.close(); }).pipe(ws); });
内容的提问来源于stack exchange,提问作者eggsloveoats
相关产品推荐
相关产品推荐

