You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用PHP XMLReader解析XML并去重插入MySQL表?

问题:XMLReader解析嵌套XML插入MySQL时重复数据过多

我有如下结构的XML文件:

<?xml version="1.0" encoding="UTF-8"?>
<products currency="EUR">
<product id="457654754" vat="13.0">
    <price gross="0.09" net="0.07"/>
    <sizes>
        <size id="0"  name="gfdgfdgfdg" panel_name="dfgfdgfdgfd" code_producer="fgdfgfdg"  
         iaiext:code_external="457547547547"  code="354643643643"  weight="4"  iaiext:weight_net="10" >
        <stock id="1" quantity="333"/>
        </size>
    </sizes>
</product>
</products>

用PHP的XMLReader解析时能正确输出属性值,但插入MySQL时出现大量重复数据(预期2500行,实际48000行)。解析代码如下:

<?php
$xml = new XMLReader;
$xml->open('XML_URL');
while ($xml->read()) {
if ($xml->nodeType === XMLReader::ELEMENT && $xml->name == 'product')
    echo $xml->getAttribute('id').'<br />';
if ($xml->nodeType === XMLReader::ELEMENT && $xml->name == 'price')
    echo $xml->getAttribute('net').'<br />';
if ($xml->nodeType === XMLReader::ELEMENT && $xml->name == 'size')
    echo $xml->getAttribute('iaiext:code_external').'<br />';
    if ($xml->nodeType === XMLReader::ELEMENT && $xml->name == 'stock')
    echo $xml->getAttribute('quantity').'<br />';
 }
?>

MySQL表结构:

CREATE TABLE `table` (
`auto_increm` int(11) NOT NULL,
`un_id` int(11) NOT NULL,
`price` decimal(15,2) NOT NULL,
`quantity` int(11) NOT NULL,
`ean` varchar(255) NOT NULL
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

 ALTER TABLE `table`
 ADD PRIMARY KEY (`auto_increm`);

 ALTER TABLE `table`
 MODIFY `auto_increm` int(11) NOT NULL AUTO_INCREMENT;
 COMMIT;

属性映射规则:

  • XML中product的id → MySQL的un_id
  • XML中price的net → MySQL的price
  • XML中size的iaiext:code_external → MySQL的ean
  • XML中stock的quantity → MySQL的quantity

之前处理扁平XML时插入正常,但嵌套XML就出问题了,怎么解决重复插入的问题?


问题根源

之前的代码在XMLReader循环中,每遇到一个匹配元素(如product、price、size、stock)就单独处理,没有把属于同一组的数据关联起来。同时XMLReader会遍历所有节点(包括文本节点、结束标签等),导致多次触发插入逻辑,产生大量重复数据。

解决方法

需要把同一product下的price、size、stock数据关联起来,仅在完整收集到一组数据后执行一次插入。同时精准定位节点,跳过无效遍历。

优化后的代码

<?php
// 数据库连接(替换为你的实际参数)
$conn = mysqli_connect('localhost', 'username', 'password', 'database_name');
if (!$conn) {
    die('数据库连接失败: ' . mysqli_connect_error());
}

$xml = new XMLReader();
$xml->open('XML_URL');

// 存储当前产品的临时数据
$currentProduct = [];

while ($xml->read()) {
    // 只处理元素节点,跳过文本、结束标签等
    if ($xml->nodeType !== XMLReader::ELEMENT) {
        continue;
    }

    // 读取product节点,开始收集数据
    if ($xml->name === 'product') {
        $currentProduct['un_id'] = $xml->getAttribute('id');
        
        // 定位price节点,获取net价格
        $xml->read();
        while ($xml->nodeType !== XMLReader::ELEMENT || $xml->name !== 'price') {
            if (!$xml->read()) break;
        }
        if ($xml->name === 'price') {
            $currentProduct['price'] = $xml->getAttribute('net');
        }

        // 定位sizes节点,遍历内部的size
        while ($xml->nodeType !== XMLReader::ELEMENT || $xml->name !== 'sizes') {
            if (!$xml->read()) break;
        }
        if ($xml->name === 'sizes') {
            $xml->read();
            // 遍历sizes下的所有size节点
            while ($xml->nodeType !== XMLReader::END_ELEMENT || $xml->name !== 'sizes') {
                if ($xml->nodeType === XMLReader::ELEMENT && $xml->name === 'size') {
                    $currentProduct['ean'] = $xml->getAttribute('iaiext:code_external');
                    
                    // 定位当前size下的stock节点
                    $xml->read();
                    while ($xml->nodeType !== XMLReader::ELEMENT || $xml->name !== 'stock') {
                        if (!$xml->read()) break;
                    }
                    if ($xml->name === 'stock') {
                        $currentProduct['quantity'] = $xml->getAttribute('quantity');
                        
                        // 收集完完整数据,执行插入(用预处理语句防注入)
                        $stmt = mysqli_prepare($conn, "INSERT INTO `table` (un_id, price, ean, quantity) VALUES (?, ?, ?, ?)");
                        mysqli_stmt_bind_param($stmt, 'dssi', $currentProduct['un_id'], $currentProduct['price'], $currentProduct['ean'], $currentProduct['quantity']);
                        mysqli_stmt_execute($stmt);
                        mysqli_stmt_close($stmt);
                    }
                }
                // 移动到下一个节点
                if (!$xml->read()) break;
            }
        }
    }
}

$xml->close();
mysqli_close($conn);
?>

关键改进点

  1. 数据关联收集:将同一product下的所有关联数据存入临时数组,仅在集齐完整数据后执行一次插入,避免重复提交。
  2. 精准节点遍历:通过循环条件定位目标节点,跳过无关的文本、结束标签等节点,减少无效遍历次数。
  3. 预处理语句:使用mysqli_prepare绑定参数,既防止SQL注入,又保证数据类型与表结构匹配。
  4. 避免重复触发:不再单独处理每个元素节点,而是在product节点内部一次性完成子节点读取与数据整合。

额外去重保障

如果XML本身存在重复数据,或需要避免插入表中已有的数据,可给表添加唯一约束:

-- 假设un_id+ean是唯一标识组合
ALTER TABLE `table` ADD UNIQUE KEY `unique_product_size` (`un_id`, `ean`);

然后修改插入语句,选择忽略重复或更新现有数据:

  • 忽略重复:INSERT IGNORE INTO ...
  • 更新现有数据:
$stmt = mysqli_prepare($conn, "INSERT INTO `table` (un_id, price, ean, quantity) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE price=VALUES(price), quantity=VALUES(quantity)");

内容的提问来源于stack exchange,提问作者qwertyg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 22:25:25