
本文介绍如何将 postgresql 查询结果按某一字段(如 gene_name)分组,并为每个分组动态生成独立的 html 表格,配合可折叠标题,提升数据展示的结构性与可读性。
本文介绍如何将 postgresql 查询结果按某一字段(如 gene_name)分组,并为每个分组动态生成独立的 html 表格,配合可折叠标题,提升数据展示的结构性与可读性。
在实际 Web 开发中,当查询结果存在重复主键(如多个注释对应同一基因),直接线性输出易造成信息混杂。理想的方案是按 gene_name 分组,为每个基因生成一个专属子表格,并添加语义化标题(如 <h4>gene1</h4> 或交互式折叠按钮)。这不仅提升用户体验,也便于前端样式控制与 JavaScript 操作。
实现的关键在于两次数据处理:
- 分组聚合:遍历原始查询结果,以 gene_name 为键,将所有相关描述(description)存入嵌套数组;
- 结构化渲染:遍历分组后的关联数组,为每个基因名生成独立 <table>,并嵌入其全部描述项。
以下是完整、安全、可运行的优化代码(含防 SQL 注入、HTML 转义与错误处理):
<?php
function test_input($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
return $data;
}
$search_input = test_input($_GET["search_keywords"] ?? '');
$searched_by = test_input($_GET["search_by"] ?? '');
// ✅ 严格校验搜索字段,防止列名注入
$allowed_columns = ['gene_name', 'description'];
if (!in_array($searched_by, $allowed_columns)) {
die('Invalid search column');
}
// ✅ 使用参数化查询思想(PostgreSQL 中需结合 pg_query_params)
// 注意:SIMILAR TO 不支持原生参数绑定,故使用 pg_escape_string + 白名单校验双重防护
$escaped_input = pg_escape_string(strtolower($search_input));
$query = "SELECT gene_name, description FROM gene
WHERE lower($searched_by) ~* '" . $escaped_input . "'";
$res = pg_query($query) or die('Query failed: ' . pg_last_error());
if (!$res || pg_num_rows($res) === 0) {
echo "<p>No results found.</p>\n";
} else {
// ? 第一步:按 gene_name 分组聚合数据
$geneAnnotations = [];
while ($line = pg_fetch_assoc($res)) {
$geneName = $line['gene_name'] ?? '';
$desc = $line['description'] ?? '';
// 自动去重可选:$geneAnnotations[$geneName][] = $desc;
$geneAnnotations[$geneName][] = ['desc' => htmlspecialchars($desc)];
}
// ? 第二步:逐组渲染子表格(带折叠功能)
foreach ($geneAnnotations as $geneName => $annotations) {
$tableId = 'tblAnnotations_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $geneName);
echo "<button class=\"collapsible\" data-table-id=\"$tableId\" onclick=\"toggleTable('$tableId')\">";
echo htmlspecialchars($geneName);
echo "</button>\n";
echo "<div class=\"content\">\n";
echo "<table id=\"$tableId\" class=\"table annot_table\">\n";
echo "<thead><tr><th>Term</th></tr></thead>\n<tbody>\n";
foreach ($annotations as $annotation) {
echo "<tr><td>{$annotation['desc']}</td></tr>\n";
}
echo "</tbody>\n</table>\n";
echo "</div>\n";
}
}
pg_free_result($res);
?>? 关键注意事项:
立即学习“PHP免费学习笔记(深入)”;
- 安全性优先:禁用用户输入直接拼接列名(通过白名单 $allowed_columns 校验),对所有输出内容使用 htmlspecialchars() 防 XSS;
- SQL 注入防护:pg_escape_string() 仅用于值部分,列名必须硬编码或白名单控制;
- 空值防御:使用 $line['gene_name'] ?? '' 避免未定义索引警告;
- ID 合法性:HTML ID 不允许特殊字符,用 preg_replace() 清洗 geneName 生成安全 tableId;
- 前端兼容性:toggleTable() 函数需自行实现(示例见下方),用于显示/隐藏对应 <div class="content">。
✅ 可选增强(前端折叠逻辑):
<script>
function toggleTable(id) {
const content = document.querySelector(`[data-table-id="${id}"]`).nextElementSibling;
content.style.display = content.style.display === 'none' ? 'block' : 'none';
}
// 初始化默认收起
document.querySelectorAll('.content').forEach(el => el.style.display = 'none');
</script>通过这种“先分组、再渲染”的模式,你不仅能精准实现按基因拆分子表格的需求,还构建了可扩展的数据展示架构——未来可轻松增加排序、分页、导出等功能,而无需重构核心逻辑。



















