如何在 PHP PDO 中正确实现多对多关系查询并合并显示关联数据

如何在 PHP PDO 中正确实现多对多关系查询并合并显示关联数据

本文讲解如何使用 pdo 的 group_concat 与 join 正确处理“人—发票”多对多关系,避免重复列、错位显示问题,实现每人一行、发票 id 合并在单个单元格的效果。

本文讲解如何使用 pdo 的 group_concat 与 join 正确处理“人—发票”多对多关系,避免重复列、错位显示问题,实现每人一行、发票 id 合并在单个单元格的效果。

在构建多对多关系的 Web 表格展示时(如“一人对应多个发票”),常见错误是未对关联数据做聚合处理,或在 PHP 循环中错误嵌套遍历,导致每张发票生成独立列,破坏表格结构。核心解决方案在于:数据库层聚合 + 应用层精准匹配

✅ 正确做法:一步聚合,精准关联

首先,修正 SQL 查询逻辑 —— 不应仅连接 people_inv 和 inv,而需关联 people 表以获取姓名和邮箱,并按人员分组聚合发票 ID:

public function viewdatajoin()
{
    $stmt = $this->db->prepare("
        SELECT 
            p.id AS person_id,
            p.name,
            p.email,
            GROUP_CONCAT(i.id ORDER BY i.id SEPARATOR ', ') AS invoice_ids,
            GROUP_CONCAT(i.inv_no ORDER BY i.id SEPARATOR ', ') AS invoice_numbers
        FROM people p
        LEFT JOIN people_inv pi ON p.id = pi.people_id
        LEFT JOIN inv i ON pi.inv_id = i.id
        GROUP BY p.id, p.name, p.email
        ORDER BY p.id DESC
    ");
    $stmt->execute();
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

? 关键改进说明:

  • 使用 LEFT JOIN 确保无发票的人员也能显示(显示为 NULL 或空);
  • GROUP_CONCAT(… SEPARATOR ‘, ‘) 明确分隔符,避免默认逗号无空格导致可读性差;
  • ORDER BY i.id 保证发票 ID 按序排列;
  • GROUP BY 必须包含所有非聚合字段(p.id, p.name, p.email),符合 SQL 标准且兼容严格模式。

✅ 前端渲染:单次遍历,拒绝嵌套循环

原始代码中对 $invoices 的内层 foreach 是问题根源 —— 它为每个 people 行尝试匹配所有发票记录,导致列数爆炸、错位。正确方式是预关联数据,直接映射渲染

立即学习“PHP免费学习笔记(深入)”;

<?php
$table = 'people';
$people = $data->viewdatamodel($table); // 获取人员基础数据
$personInvoices = $data->viewdatajoin(); // 获取已聚合的 {person_id => [invoice_ids, invoice_numbers]}

// 构建快速查找数组:person_id → invoice info
$invoiceMap = [];
foreach ($personInvoices as $row) {
    $invoiceMap[$row['person_id']] = [
        'ids'  => $row['invoice_ids'] ?: '-',
        'nums' => $row['invoice_numbers'] ?: '-'
    ];
}
?>
<table border="1">
    <thead>
        <tr>
            <th>#</th>
            <th style="display:none;">ID</th>
            <th>姓名</th>
            <th>邮箱</th>
            <th>关联发票(编号)</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
        <?php $no = 1; foreach ($people as $person): ?>
        <tr>
            <td><?= $no++ ?></td>
            <td style="display:none;"><?= $person['id'] ?></td>
            <td><?= htmlspecialchars($person['name']) ?></td>
            <td><?= htmlspecialchars($person['email']) ?></td>
            <td><?= $invoiceMap[$person['id']]['nums'] ?? '-' ?></td>
            <td>
                <a href="edit.php?id=<?= $person['id'] ?>&table=<?= $table ?>">编辑</a> |
                <a href="lib/process.php?action=delete&id=<?= $person['id'] ?>&table=<?= $table ?>">删除</a>
            </td>
        </tr>
        <?php endforeach; ?>
    </tbody>
</table>

⚠️ 注意事项与最佳实践

  • 安全防护:始终对输出内容使用 htmlspecialchars() 防止 XSS,尤其当姓名/邮箱来自用户输入时;
  • 空值处理:LEFT JOIN 后 GROUP_CONCAT 对无关联记录返回 NULL,需用 ?: ‘-‘ 提供默认值;
  • 性能优化:若数据量大,建议为 people_inv(people_id, inv_id) 添加联合索引;
  • 扩展性考虑:如需展示发票状态等更多字段,可在 GROUP_CONCAT 中拼接 JSON 或改用子查询,但优先推荐前端二次请求详情(避免过度膨胀主列表);
  • 调试技巧:打印 $invoiceMap 查看结构,确认键名与 $person[‘id’] 类型一致(整型 vs 字符串)。

通过将聚合逻辑交由数据库完成,并在 PHP 层建立 O(1) 查找映射,即可彻底规避嵌套循环引发的布局错乱问题,实现清晰、稳定、可维护的多对多表格展示。

文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/xinjizixun/124134.html

ThinkPHP如何配置安全的文件下载机制【安全】
上一篇 2026-07-01 17:52
下一篇 2026-07-01 17:52

相关推荐