Node.js+Express如何将MySQL两表查询结果合并为指定JSON结构?
实现嵌套JSON结构的可行方案
针对你的需求,这里提供两种简单易实现的方案,基于mysql2的promise API(用async/await避免回调地狱,更适合新手):
方法1:单次JOIN查询后在代码中组装结构
先通过SQL JOIN关联两张表,再用JavaScript的reduce方法将查询结果整理成嵌套结构:
const express = require('express'); const mysql = require('mysql2/promise'); const app = express(); // 数据库连接配置,替换成你的实际信息 const dbConfig = { host: 'localhost', user: 'your_db_user', password: 'your_db_password', database: 'your_db_name' }; app.get('/api/matchdays', async (req, res) => { let connection; try { // 建立数据库连接 connection = await mysql.createConnection(dbConfig); // JOIN查询所有数据,保留matchdays和对应的scores字段 const [rows] = await connection.execute(` SELECT m.matchdayid, m.matchdaydate, s.scoreid, s.player, s.score FROM matchdays m LEFT JOIN matchday_scores s ON m.matchdayid = s.matchdayid ORDER BY m.matchdayid `); // 组装嵌套JSON结构 const nestedResult = rows.reduce((acc, row) => { // 查找当前matchday是否已在结果数组中 const existingMatchday = acc.find(item => item.matchdayid === row.matchdayid); if (existingMatchday) { // 如果当前行有score数据,添加到对应matchday的scores数组 if (row.scoreid) { existingMatchday.scores.push({ scoreid: row.scoreid, player: row.player, score: row.score }); } } else { // 新建matchday对象,初始化scores数组 const newMatchday = { matchdayid: row.matchdayid, matchdaydate: row.matchdaydate, scores: [] }; // 如果当前行有score数据,加入数组 if (row.scoreid) { newMatchday.scores.push({ scoreid: row.scoreid, player: row.player, score: row.score }); } acc.push(newMatchday); } return acc; }, []); res.json(nestedResult); } catch (err) { console.error('数据库查询错误:', err); res.status(500).json({ error: '获取数据失败' }); } finally { // 确保数据库连接关闭 if (connection) await connection.end(); } }); const PORT = 3000; app.listen(PORT, () => console.log(`服务器运行在端口 ${PORT}`));
要点:
- 使用
LEFT JOIN保证即使没有对应scores的matchday也会被返回,此时scores数组为空 - 通过
reduce分组,将同一matchdayid的score数据合并到一个数组中 - 用
async/await简化异步流程,避免回调嵌套
方法2:两次查询后关联数据
先查询所有matchdays,再查询所有scores并按matchdayid分组,最后将两者关联,逻辑更直观:
const express = require('express'); const mysql = require('mysql2/promise'); const app = express(); const dbConfig = { host: 'localhost', user: 'your_db_user', password: 'your_db_password', database: 'your_db_name' }; app.get('/api/matchdays', async (req, res) => { let connection; try { connection = await mysql.createConnection(dbConfig); // 第一步:查询所有matchdays基础数据 const [matchdays] = await connection.execute('SELECT matchdayid, matchdaydate FROM matchdays ORDER BY matchdayid'); // 第二步:查询所有scores数据 const [scores] = await connection.execute('SELECT matchdayid, scoreid, player, score FROM matchday_scores'); // 将scores转成以matchdayid为键的映射对象,方便快速查找 const scoreMap = scores.reduce((map, score) => { if (!map[score.matchdayid]) { map[score.matchdayid] = []; } map[score.matchdayid].push({ scoreid: score.scoreid, player: score.player, score: score.score }); return map; }, {}); // 给每个matchday添加对应的scores数组 const nestedResult = matchdays.map(matchday => ({ ...matchday, scores: scoreMap[matchday.matchdayid] || [] })); res.json(nestedResult); } catch (err) { console.error('数据库查询错误:', err); res.status(500).json({ error: '获取数据失败' }); } finally { if (connection) await connection.end(); } }); const PORT = 3000; app.listen(PORT, () => console.log(`服务器运行在端口 ${PORT}`));
要点:
- 拆分查询逻辑,新手更容易理解和调试
- 用
reduce构建score映射表,避免循环中重复查询数据库(性能更优) - 同样通过
LEFT JOIN的思路,用|| []保证没有scores的matchday返回空数组
注意事项
- 替换代码中的
dbConfig为你的实际数据库连接信息 - 根据
matchday_scores表的实际字段调整代码中的字段名(比如如果你的表没有player字段,改成你实际的字段) - 生产环境中建议使用数据库连接池(
mysql2.createPool)而不是每次请求新建连接,提升性能 - 可以给SQL语句添加索引(比如给
matchday_scores.matchdayid加索引),优化查询速度
内容的提问来源于stack exchange,提问作者Ed H
相关产品推荐
相关产品推荐

