
本文讲解如何通过 sql 排序配合 php 逻辑,将多对一关联查询结果(如题目与多个选项)按问题归组、合并选项字段,生成结构清晰的嵌套 json 格式,避免重复问题行。
本文讲解如何通过 sql 排序配合 php 逻辑,将多对一关联查询结果(如题目与多个选项)按问题归组、合并选项字段,生成结构清晰的嵌套 json 格式,避免重复问题行。
在实际开发中,当使用 JOIN 多表查询问卷系统(如 questions、answer_options、answer_test)时,原始 SQL 往往返回“一行一选项”的扁平化结果——同一道题因对应多个答案选项而被重复输出多次。但前端或 API 接口通常需要“一道题 + 所有选项数组”的嵌套结构。单纯依赖 SQL 聚合(如 GROUP_CONCAT)虽可拼接字符串,却难以保持结构化数据(如保留选项 ID、顺序、类型等),因此更推荐 SQL 预排序 + PHP 逻辑归并 的组合方案。
✅ 第一步:SQL 层保证数据局部有序
为便于后续 PHP 归并,需确保相同 questions_text(或更稳健的 question_id)的记录连续排列。推荐在原始查询末尾添加 ORDER BY:
SELECT q.id, q.questions_text, a_o.answer_option, a_o.id AS answer_option_id, a_o.question_id, a_t.answer_test_id, a_t.answer FROM questions q JOIN answer_options a_o ON q.id = a_o.question_id JOIN answer_test a_t ON a_o.id = a_t.answer_option_id -- 注意:原文 ON 条件有误,应为 a_o.id = a_t.answer_option_id ORDER BY q.id, a_o.id; -- 按 question_id 升序,再按选项 ID 保序
? 关键修正说明:原问题中 a_o.answer_test_id = a_t.answer_option_id 存在逻辑错误(字段语义错位),正确关联应为 a_o.id = a_t.answer_option_id(即 answer_test 表外键指向 answer_options.id)。务必先校验表结构,避免 JOIN 结果为空或错乱。
✅ 第二步:PHP 层归并构建嵌套结构
利用有序结果,通过单次遍历完成分组聚合。核心思路是维护一个临时变量记录当前处理的问题 ID,并动态构建结果数组:
立即学习“PHP免费学习笔记(深入)”;
<?php // 假设 $pdo 已初始化,执行上述 SQL $stmt = $pdo->query($sql); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $result = []; $currentQuestionId = null; $currentQuestion = null; foreach ($rows as $row) { // 若进入新题目,保存上一题并初始化新题 if ($row['question_id'] !== $currentQuestionId) { if ($currentQuestion !== null) { $result[] = $currentQuestion; } // 初始化新题基础字段(取首行值) $currentQuestion = [ 'id' => $row['id'], 'questions_text' => trim($row['questions_text']), 'question_id' => $row['question_id'], 'answer_test_id' => $row['answer_test_id'], 'answer' => $row['answer'], 'answer_options' => [] // 用于收集所有选项 ]; $currentQuestionId = $row['question_id']; } // 将当前选项追加到该题的 answer_options 数组中(保持结构化) $currentQuestion['answer_options'][] = [ 'text' => $row['answer_option'], 'id' => (int)$row['answer_option_id'] ]; } // 不要遗漏最后一题 if ($currentQuestion !== null) { $result[] = $currentQuestion; } // 输出标准 json(UTF-8 安全) header('Content-Type: application/json; charset=utf-8'); echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); ?>
⚠️ 注意事项与最佳实践
- 唯一标识优先用 question_id:相比 questions_text(可能含空格、换行、大小写差异),question_id 是稳定主键,避免归并逻辑出错;
- 避免 GROUP_CONCAT 的局限性:若强行用 GROUP_CONCAT(answer_option),会丢失选项元数据(ID、顺序、状态),且需额外 JSON_EXTRACT 解析,增加复杂度;
- 内存友好性:本方案仅遍历一次结果集,时间复杂度 O(n),无嵌套循环,适用于千级以内题目;
- 扩展建议:如需支持多语言题目、选项禁用状态等,只需在 $currentQuestion[‘answer_options’][] 中增加对应字段即可,结构天然可扩展。
通过这一组合策略,你既能利用 SQL 的高效排序能力,又能借助 PHP 灵活控制数据形态,最终输出符合现代 API 规范的嵌套 JSON,兼顾性能、可读性与可维护性。