将 XML 文件同步到 MySQL 数据库
Posted
技术标签:
【中文标题】将 XML 文件同步到 MySQL 数据库【英文标题】:Synchronizing XML file to MySQL database 【发布时间】:2014-07-27 15:30:39 【问题描述】:我的公司使用内部管理软件来存储产品。他们希望将所有产品转置到 mysql 数据库中,以便他们可以在公司网站上提供他们的产品。
注意:他们将继续使用自己的内部软件。该软件可以以各种文件格式(包括 XML)导出所有产品。
同步不一定是实时的,一天一次(深夜)同步一次MySql数据库就满足了。
另外,他们软件中的每个产品都有一个或多个图像,那么我必须同时提供网站上的图像。
这是一个 XML 导出示例:
<?xml version="1.0" encoding="UTF-8"?>
<export_management userid="78643">
<product id="1234">
<version>100</version>
<insert_date>2013-12-12 00:00:00</insert_date>
<warrenty>true</warrenty>
<price>139,00</price>
<model>
<code>324234345</code>
<model>Notredame</model>
<color>red</color>
<size>XL</size>
</model>
<internal>
<color>green</color>
<size>S</size>
</internal>
<options>
<s_option>aaa</s_option>
<s_option>bbb</s_option>
<s_option>ccc</s_option>
<s_option>ddd</s_option>
<s_option>eee</s_option>
<s_option>fff</s_option>
...
<extra_option>ggg</extra_option>
<extra_option>hhh</extra_option>
<extra_option>jjj</extra_option>
<extra_option>kkk</extra_option>
...
</options>
<images>
<image>
<small>1234_0.jpg</small>
</image>
<image>
<small>1234_1.jpg</small>
</image>
</images>
</product>
<product id="5321">
...
</product>
<product id="2621">
...
</product>
...
</export_management>
关于我该如何做的一些想法?
如果我的问题不清楚,请告诉我。谢谢
编辑: 我对每个表都使用了这样的 SQL 来用 XML 数据填充它们:
LOAD XML LOCAL INFILE '/products.xml' INTO TABLE table_name ROWS IDENTIFIED BY '<tag_name>';
然后,检查表格内容,我可以看到字段“id”(主键)自动为每个表格中的每个相应产品行保持相同。这是正确的,而且非常棒!
现在的问题在于参数<options>
,因为它包含同名的子参数(<s_option>
和<extra_option>
)。这些标签的值总是不同的(也就是说,没有特定的值列表,它们是由员工手动插入的),而且我不知道每种产品有多少。我读到将它们存储为数组不太好,但如果这是我能得到的唯一简单解决方案。
【问题讨论】:
嗯,我的回答对你有帮助吗?你按照我的步骤来了吗? 嘿 Fred,我当然不知道你的个人情况,但特别是如果像 @yair-nevet 这样的人竭尽全力为你提供高质量的答案,那很好礼貌地登录并授予全额奖金。 @flup 感谢您的支持! 【参考方案1】:在您的情况下,我处理问题的方法是:
在数据库中分别创建一组相应的表,通过从给定的 XML 中提取建模,这些表依次代表公司的产品模型。
创建并使用计划的每日同步作业,该作业可能会执行少量 SQL 命令,以便通过解析产品 XML 到创建的表中来刷新数据或引入新命令。
为了更加实用:
至于数据库的表,我可以很容易地根据你的XML识别出三个要创建的表,看黄色标记的元素:
Products
ProductsOptions
ProductsImages
(此图表基于从您的 XML 生成的 XSD 创建的)
所有其余部分都可以视为Products
表中的常规列,因为它们仅构成1-1 关系。
接下来,在您的数据库中创建所需的表(您可以使用 XSD2DB Schema 转换器工具来创建 DDL 脚本,我是手动完成的):
companydb.products
CREATE TABLE companydb.products (
Id INT(11) NOT NULL,
Version INT(11) DEFAULT NULL,
InsertDate DATETIME DEFAULT NULL,
Warrenty TINYINT(1) DEFAULT NULL,
Price DECIMAL(19, 2) DEFAULT NULL,
ModelCode INT(11) DEFAULT NULL,
ModelColor VARCHAR(10) DEFAULT NULL,
Model VARCHAR(255) DEFAULT NULL,
ModelSize VARCHAR(10) DEFAULT NULL,
InternalColor VARCHAR(10) DEFAULT NULL,
InternalSize VARCHAR(10) DEFAULT NULL,
PRIMARY KEY (Id)
)
ENGINE = INNODB
CHARACTER SET utf8
COLLATE utf8_general_ci
COMMENT = 'Company''s Products';
companydb.productsimages
CREATE TABLE companydb.productimages (
Id INT(11) NOT NULL AUTO_INCREMENT,
ProductId INT(11) DEFAULT NULL,
Size VARCHAR(10) DEFAULT NULL,
FileName VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (Id),
CONSTRAINT FK_productsimages_products_Id FOREIGN KEY (ProductId)
REFERENCES companydb.products(Id) ON DELETE RESTRICT ON UPDATE RESTRICT
)
ENGINE = INNODB
AUTO_INCREMENT = 1
CHARACTER SET utf8
COLLATE utf8_general_ci
COMMENT = 'Products'' Images';
companydb.productsoptions
CREATE TABLE companydb.productoptions (
Id INT(11) NOT NULL AUTO_INCREMENT,
ProductId INT(11) DEFAULT NULL,
Type VARCHAR(255) DEFAULT NULL,
`Option` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (Id),
CONSTRAINT FK_producstsoptions_products_Id FOREIGN KEY (ProductId)
REFERENCES companydb.products(Id) ON DELETE RESTRICT ON UPDATE RESTRICT
)
ENGINE = INNODB
AUTO_INCREMENT = 1
CHARACTER SET utf8
COLLATE utf8_general_ci;
至于要发生的同步作业过程,您可以轻松地创建一个MySql event 并使用Event Scheduler 来控制它,我创建了所需的
event
,它正在调用您将在下面找到的存储过程 (SyncProductsDataFromXML
),请看:
CREATE DEFINER = 'root'@'localhost' 事件 companydb.ProductsDataSyncEvent 按计划每“1”天开始 '2014-06-13 01:27:38' COMMENT '同步产品表 产品 XML 的 DO BEGIN SET @productsXml = LOAD_FILE('C:/MySqlXmlSync/products.xml');称呼 SyncProductsDataFromXML(@productsXml);结尾; ALTER EVENT companydb.ProductsDataSyncEvent 启用
现在有趣的部分正在发生,这里是同步存储过程(注意上面的event
是如何调用它的):
CREATE DEFINER = 'root'@'localhost'
PROCEDURE companydb.SyncProductsDataFromXML(IN productsXml MEDIUMTEXT)
BEGIN
DECLARE totalProducts INT;
DECLARE productIndex INT;
SET totalProducts = ExtractValue(productsXml, 'count(//export_management/product)');
SET productIndex = 1;
WHILE productIndex <= totalProducts DO
SET @productId = CAST(ExtractValue(productsXml, 'export_management/product[$productIndex]/@id') AS UNSIGNED);
INSERT INTO products(`Id`, `Version`, InsertDate, Warrenty, Price, ModelCode, Model, ModelColor, ModelSize, InternalColor, InternalSize)
VALUES(
@productId,
ExtractValue(productsXml, 'export_management/product[$productIndex]/version'),
ExtractValue(productsXml, 'export_management/product[$productIndex]/insert_date'),
CASE WHEN (ExtractValue(productsXml, 'export_management/product[$productIndex]/warrenty')) <> 'false' THEN 1 ELSE 0 END,
CAST(ExtractValue(productsXml, 'export_management/product[$productIndex]/price') as DECIMAL),
ExtractValue(productsXml, 'export_management/product[$productIndex]/model/code'),
ExtractValue(productsXml, 'export_management/product[$productIndex]/model/model'),
ExtractValue(productsXml, 'export_management/product[$productIndex]/model/color'),
ExtractValue(productsXml, 'export_management/product[$productIndex]/model/size'),
ExtractValue(productsXml, 'export_management/product[$productIndex]/internal/color'),
ExtractValue(productsXml, 'export_management/product[$productIndex]/internal/size')
);
SET @totalImages = ExtractValue(productsXml, 'count(//export_management/product[$productIndex]/images/image)');
SET @imageIndex = 1;
WHILE (@imageIndex <= @totalImages) DO
INSERT INTO productimages(ProductId, Size, FileName) VALUES(@productId, 'small', EXTRACTVALUE(productsXml, 'export_management/product[$productIndex]/images/image[$@imageIndex]/small'));
SET @imageIndex = @imageIndex + 1;
END WHILE;
SET @totalStandardOptions = ExtractValue(productsXml, 'count(//export_management/product[$productIndex]/options/s_option)');
SET @standardOptionIndex = 1;
WHILE (@standardOptionIndex <= @totalStandardOptions) DO
INSERT INTO productoptions(ProductId, `Type`, `Option`) VALUES(@productId, 'Standard Option', EXTRACTVALUE(productsXml, 'export_management/product[$productIndex]/options/s_option[$@standardOptionIndex]'));
SET @standardOptionIndex = @standardOptionIndex + 1;
END WHILE;
SET @totalExtraOptions = ExtractValue(productsXml, 'count(//export_management/product[$productIndex]/options/extra_option)');
SET @extraOptionIndex = 1;
WHILE (@extraOptionIndex <= @totalExtraOptions) DO
INSERT INTO productoptions(ProductId, `Type`, `Option`) VALUES(@productId, 'Extra Option', EXTRACTVALUE(productsXml, 'export_management/product[$productIndex]/options/extra_option[$@extraOptionIndex]'));
SET @extraOptionIndex = @extraOptionIndex + 1;
END WHILE;
SET productIndex = productIndex + 1;
END WHILE;
END
大功告成,这是此过程的最终预期结果:
注意:我已将整个代码提交到我的 GitHub 存储库之一:XmlSyncToMySql
更新:
因为您的 XML 数据可能大于 TEXT
字段允许的最大值,所以我已将 productsXml
参数更改为 MEDIUMTEXT
。查看此答案,其中概述了各种文本数据类型的最大允许大小:
Maximum length for MYSQL type text
【讨论】:
嗨@Yair Nevet,感谢您的回答。我修复了错误的 XML 标记(您可以删除您关注的部分答案)。好吧,我按照你写的那样创建了表格,但我不清楚:1) LOAD XML 不会填充像model_color
、model_size
、internal_color
这样的“扁平字段” ,internal_size
。 2) 此外,LOAD XML 不会填充 ProductsOptions 和 ProductsImages 表!你能提供更多细节吗?谢谢
@FredK 你在使用一些服务器语言,比如 java、c#、php、python 等吗?
Yair,以上问题都清楚了吗?你明白我的意思了吗?是的,我可能会用 PHP 开发前端。
我宁愿将 XML 文件解析成一系列域事件,并像 CQRS 那样做...gridshore.nl/wp-content/uploads/cqrs_architecture.jpg这样开发和测试要容易得多,而不是直接从解析器调用 SQL...
@YairNevet:这行LOAD XML LOCAL INFILE '/products.xml' INTO TABLE Products(Id, Version, InsertDate, Warrenty, Price) ROWS IDENTIFIED BY '<product>';
出现错误。这里的错误:#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'ROWS IDENTIFIED BY '<product>'' at line 1
。 mysql 5.5.33。它仅在我删除字段(Id,Version,...)时才有效【参考方案2】:
因为这闻起来像集成工作,我建议使用临时格式的多通道、多步骤过程,该过程不仅易于导入 mysql,而且还可以帮助您解决此集成带来的问题使用并以小步骤测试解决方案。
如果您可以将可以或可以在 XML 导出中表示的树结构展平为具有固定命名属性的产品列表,则此过程会很好。
使用 XML 中的 xpath 查询查询所有产品元素,迭代产品的结果 从上一个查询中查询与产品上下文节点相关的所有产品属性。再次为每个属性使用一个 xpath。 将每个产品的所有属性的结果作为一行存储到 CSV 文件中。 也将文件名存储在 CSV 中(基本名称),但将文件存储到它自己的文件夹中 以 .sql 文件的形式创建 mysql 表的 DDL 针对 mysql 命令行运行该 .sql 文件。 通过 mysql 命令行将 CSV 文件导入该表。您应该会在数小时内快速获得结果。如果事实证明由于属性具有多个值(您在问题中称为数组)而无法将产品映射到单行上,请考虑将它们转换为 JSON 字符串,如果您根本无法阻止它们(只是希望你一开始不需要显示复杂的数据)。这样做会违反以正常形式为目标,但是正如您所描述的 Mysql 表在这里也只是中间的,我的目标是数据库中数据结构的简单性,否则查询网站上的简单快速显示会造成下一个负担。
所以我在这里的建议基本上是:将树形结构变成一个(更多)平面列表,以简化过渡和更容易显示模板。
这里有一个中间格式还可以让您在出现问题时重播。
它还允许您更轻松地模拟整个模板。
另外,也可以将每个项目的 XML 存储在数据库中(将块保存在第二个表中,以便您可以将 varchar(可变长度)文件保留在第一个表之外)并将其他一些列保留为(平) 要查询的参考列。如果是为了模板化需求,将 XML 转换为 SimpleXMLElement 通常非常好,因为它是结构化的非原始数据类型作为视图对象,您可以遍历和循环选项。与 JSON 类似,但保持 XML 不会破坏格式边界,而且 XML 还可以表达比 JSON 更多的结构。
【讨论】:
嗨@hakre 如果我可以有一个扁平化的 XML,所有这些工作都会更容易,但我不能有一个扁平化的 XML。有没有办法扁平化 XML?您如何看待 YairNevet 解决方案?这似乎是确定的。 Xslt 是您可以做到这一点的一种方式。【参考方案3】:您对此采取了一种非常以技术为中心的方法。我认为从查看功能规范开始是明智的。
有一个业务类 Product 的简单 UML 类图会很有帮助。在企业看到它们时显示其属性。所以:
模型与产品有何关系?一个产品可以有多个模型还是相反? Internal 元素中存储了哪些类型的数据?特别是:Internal 的颜色和尺寸与 Model 的颜色和尺寸有何不同?特别是关于网络应用程序:
Web 应用程序是唯一对此导出感兴趣的应用程序吗? Web 应用程序应该关心版本控制还是只显示最后可用的版本? Web 应用程序对哪些特定选项感兴趣。比如折扣属性或供应商名称属性或其他? 产品详情页面应该是什么样子,哪些数据需要显示在哪里? 网站上会不会有其他页面展示产品信息,会列出哪些产品信息?然后您将知道哪些属性需要随时可供 Web 应用程序使用(作为 Product 表中的列)以及(也许)哪些属性可以简单地存储在数据库中的一个大 XML blob 中。
【讨论】:
嗨@flup:发布的 XML 代表所有案例,因此:1)每个产品只有一个模型。 2)内部元素包含标准文本数据(varchar),内部的颜色/大小与模型的颜色/大小没有关系。 关于网络应用: 1) 是的。 2) 只需显示最后可用的版本。 3) 所有选项都对 Web 应用程序感兴趣,因为我的公司希望根据任何选项过滤产品(在 Web 前端) 4) 产品详细信息页面将显示所有数据。 5)所有产品信息必须显示在产品特定页面中,其中大部分在排序页面中作为过滤器。 您见过 YairNevet 解决方案吗?你怎么看待这件事?我是这些作品的新手,但看起来不错。让我知道你的意见 我还是不明白一个产品怎么可以有两种不同的颜色和尺寸。另外:过滤/搜索将如何工作?是所有选项都被平等对待还是某些选项字符串具有特殊含义? 是的,我看到了@yair-nevet 的解决方案,它是对您问题的非常详细的具体答案。我认为您可能会跳过一步,因为您没有首先提出一些反映业务对产品的看法的规范模型,而不是产品编辑的内部技术模型。这就是为什么我想我会添加这个答案。以上是关于将 XML 文件同步到 MySQL 数据库的主要内容,如果未能解决你的问题,请参考以下文章