IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  XML  >

XML 问题 #12: 使用 Python 模块 xml2sql 和 dtd2sql

从 DTD 和 XML 文档生成 SQL 语句

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

David Mertz,Ph.D. (mertz@gnosis.cx), 格式操纵员, Gnosis Software, Inc.

2001 年 6 月 01 日

前面有一篇专栏研究了从 SQL 查询生成 XML 文档。现在,David Mertz 说明将 XML 文档和 DTD 反向转换成 RDBMS 存储格式也同样可能,但它有自己的约束和复杂性集合。Python 公众域利用了这里所讨论的 xml2sqldtd2sql 生成 SQL 语句,以一种一致和可逆的方式创建和填充数据库。这里使用了 7 个代码示例演示了这些技术。

XML 文档和 RDBMS 都将在相当长的一段时间内存在。这是我在研究了各种数据模型并了解 XML 是如何成为其中一部分以前(以及目前)而得出的结论。这里对一些实用程序的讨论继续帮助开发人员更方便地进行数据和格式转换。通过使用 dtd2sqlxml2sql ,程序员可以 自动将 XML 文档的内容移到 SQL 数据库中(以及稍后检索出信息)。

告诫

我希望,在这里以及在早期讨论 sql2dtdsql2xml 时所介绍的实用程序,会让开发人员在寻找 XML 和 SQL 之间快速而实际的转换的工作更轻松。但不要假定这些实用程序可以替代数据库分析师。优化、规范化和反向规范化是一些需要知识和经验的复杂问题。我所介绍的工具提供了一些良好、可用和易于部署的解决方案;但这些解决方案只是有些时候是定制开发的替代。

而且, xml2sqldtd2sql 最适合于包含从面向表信息开始的 XML 文档。对于面向散文和面向线性的 XML 文档的转换,它们则显得苍白无力。在这方面所强加的限制本质上等同于 xml2sql 利用的 xml_objectify 库所强加的限制。不过,这一特殊焦点严格来说算不上是限制:实际上, 任何技术 -- 就是定制开发 -- 都无法在 RDBMS 框架中产生非常自然的散文数据和线性数据的表示。各个模型均不相同, xml2sql 和任何实用程序一样只能做到大致良好的地步。

最后,不要指望 xml2sql 特别快或者特别有效;这种实用程序主要是为可移植性和通用性而设计的。因此,不调用任何数据库的库 -- 无论是特定的 RDBMS 还是通过象 ODBC 这样的机制 -- xml2sqldtd2sql 都创建简单的文本 SQL 语句。模块 dtd2sql 生成 CREATE TABLE 语句的列表,而 xml2sql 生成 INSERT INTO 语句的列表。是否将这些语句放入实际的 RDBMS 由用户或程序员决定。这种安排的好处在于,开发人员可以同样好地将这些语句放入任何供应商的 RDBMS。





回页首


尝试一下

将模块 xml2sqldtd2sql 结合实际示例一起介绍可能效果最好。稍后我会转回到设计中的一些理论问题。

出于测试目的,我创建了一个简单的测试脚本。出于开发目的,我使用流行的开放源码 RDBMS mySQL。虽然与更复杂的 RDBMS 相比, mySQL 有一些限制,它提供了一个非常好的测试平台。样本 DTD 和 XML 文档属于我所编写的 developerWorks 教程(请参阅 参考资料中的集合)。清单 1 中的脚本是 OS/2 命令文件,但在 Windows 下也应该一样有效,在类 Unix 的系统上只需要少许修改。


清单 1. 将 XML 传送到 SQL 所使用的测试脚本
echo drop database test;       > test.sql
echo create database test;    >> test.sql
echo use test;                >> test.sql
python dtd2sql.py dwtut.dtd   >> test.sql
python xml2sql.py haskell.xml >> test.sql
mysql -u root -pPASSWORD test  < test.sql

清单 1 中脚本的前面几行只是清除并恢复 mySQL 中的 test 数据库。在现实生活中,您不太会 drop 现有的数据库,而只是从表中 INSERTDELETE 。我使用 drop 是因为在测试时最好重新开始。

运行 dtd2sql 时,它从 STDIN 或从命令行上给出的文件名中获得 DTD。这个 DTD 可能是 XML 文档的内部子集,如果愿意,它也可以是外部文件。不过,这个工具一次只能从单一源中读取,它当前不处理复杂的多文件 DTD、参数实体或部分覆盖外部定义的内部子集。前面提到过, dtd2sql 只产生一组 CREATE TABLE 语句。

在清单 2 中,可以看到来自 dtd2sql 的一个输出行,然后理解如何将其各个部分拆解(为显示起见添加了一些折行)。


清单 2. 来自 dtd2sql 的样本 CREATE TABLE 语句
CREATE TABLE a (
    primary_key BIGINT UNSIGNED PRIMARY KEY,
    seq INT UNSIGNED,
    href BLOB,
    PCDATA BLOB,
    _XML BLOB,
    foreign_key_p BIGINT UNSIGNED,
    foreign_key_li BIGINT UNSIGNED,
    foreign_key_prompt BIGINT UNSIGNED,
    foreign_key_response BIGINT UNSIGNED
);

某些列名很容易由 a 元素本身的定义解释;对于其它的,则需要加以进一步研究。


清单 3. <a> XML 元素的 DTD 项
<!--A hyperlink to some other resource.-->
<!ELEMENT a (#PCDATA | code)* >
<!ATTLIST a href CDATA #REQUIRED  >

每个 CREATE TABLE 语句都包括一个 primary_keyseq 列。 seq 列有时没什么意义(除了表明缺少属于某些行的顺序性)。用户在命令行上或通过定制应用程序运行这样的 CREATE TABLE 语句将在每个表中创建这些列。 href 列直接来自同样命名 XML 的标记属性。 PCDATA_XML 列存放的是实际的元素内容(有或没有任何嵌入式字符级标记)。

所示的 CREATE TABLE 语句中最有意思的是几个 foreign_key_* 列。我会在下面考虑这些。





回页首


创建关系

按照关系模型,不同的表是通过主键/外键标识相互连接的。SQL 中的 JOIN 只是简单说明一个表中的字段必须对应于另一个表中的另一字段的方法。在关系模型中 主键是特殊的事物:它对于表的每个记录(行)必须是唯一的。大多数时候,数据库分析师查看数据的深层结构并指出 -- 在咨询了应用程序员和最终用户后 -- 哪些是充当主键的最佳候选。这些键可以是多个列的并置,通常象“社会保障号”、雇员标识、ISBN 或部件标识这样的标识用于这些角色中。

很明显, dtd2sql 无法执行数据库分析师所执行的所有背景研究:它有的只是 DTD(也可能是 XML 文档)。在这个基础上,没有一种真正的方法可以确定哪些属性或元素内容是唯一的(如果有的话)。幸好, dtd2sql 可以采取某些商业 RDBMS 的途径,这样就能轻易地避开“天然的”主键。模块能够选择完全人造的主键 -- 在拒绝所有数据表示角色时分解出唯一性需求的那些主键。我不认为这样的模式在数据库设计中能够实际达到比标识适合“现实世界”数据的更常用策略更好的正交性。而且这种方法在为每个表的主键提供完全一致和可预测的名称以及格式方面有着额外的优势。

使用的主键是随机 18 位整数。假设 Python 的 random 模块相当好,冲突的风险也非常小。不过,代码并不能严格保证无冲突(可能在以后的版本中可以)。到目前为止还不错。

下一步是使这些主键可用于 SQL JOIN 目的。要这样做,需要确保当 XML 元素包含子元素时,子元素包含与父代主键相对应的外键。不可否认,达到上述目的最节约的方法是为每个非根的 XML 元素创建一个 foreign_key 列。在那种情况下,查询合成数据库的 SQL 用户需要 知道哪些 JOIN 会产生结果(例如通过读取原始 DTD)。

抛开节约,我选择明确性。我为每个 可能是与表所对应的 XML 元素父代的元素创建了单独的 foreign_key_* 列。所以在上述 CREATE TABLE 示例中, dtd2sql 标识了在清单 4 中显示的 DTD 元素定义。


清单 4. 可能是 <a> 的父代的元素
<!ELEMENT p        (#PCDATA | code | img | br | i | b | a)* >
<!ELEMENT li       (#PCDATA | code | img | br | i | b | a)* >
<!ELEMENT prompt   (#PCDATA | code | img | br | i | b | a)* >
<!ELEMENT response (#PCDATA | code | img | br | i | b | a)* >

dtd2sql 明确方法的一个好处是,所创建的表结构固有地在 DTD 中包含大部分信息(但并非全部,因为量词并不因此而有分别)。





回页首


走些弯路

将数据放入由 dtd2sql 创建的表中是 xml2sql 的任务。当然,从技术上说,这两种工具实际上都没有将任何数据放在任何地方;每个工具都只是指定数据是什么。需要使用 RDBMS 所带的工具来实际装入数据。

事实是 xml2sql 做的非常少。其核心( walkNodes() 函数)代码连 50 行都没有。而且,即使这几行文档都编制得非常详细,无法通过编程技巧来达到简明性。当然, xml2sql 所执行的大部分任务实际上是由 xml_objectify 完成的。 xml2sql 的第一步是使用 xml_objectify 创建一个“Python 化”的对象。然后,要遍历所有嵌套的属性就很简单了,在进行中输出 INSERT INTO SQL 语句。不过,旧版本 xml_objectify 的用户需要获取最新的版本,因为 XML 元素命名方式的细小更改造成了一路“破坏”。

一旦运行了 xml2sql ,您会得到一束 SQL 语句作为回报。这组语句按照正常的 STDOUT 行为可以被重定向和导向管道,使得 xml2sql 与 RDBMS 命令行工具的结合非常直截了当。如果希望使用 xml2sql 作为支持模块,可以获得以 Python 列表获取该组 SQL 语句(可能对于和某些数据库模块一起使用很方便)。产生的典型语句看起来如清单 5 中的示例所示(为显示起见进行了折行)。


清单 5. 产生的典型语句
INSERT INTO p
       (primary_key, seq, foreign_key_text__column, PCDATA)
VALUES (15447926390024014, 0, 527610371062647168,
        "Navigating through the tutorial is easy:");

可以从清单 5 中 INSERT INTO 的形式看出,对应 DTD 中的 <p> 元素创建了一个表。实际上,我们只是知道该元素出现在 XML 文档中;对照 DTD 确认 XML 文档是需要在这些模块外部处理的作业。但假设 XML 文档是有效的, dtd2sql 可以创建正确的表和列。

还可以在 INSERT INTO 中看到这个特殊的 <p> 标记是嵌套在 <text-column> 元素内部的(需要对某些名称做大改动才能获得有效的 SQL 列名),即,具有 527610371062647168 主键的元素。它也证明了这个 <p> 元素具有某些 PCDATA 内容,它的 seq 列值为零。该列表位的含义是, <p> 元素独立位于其容器中;如果在同一个 <text-column> 中出现多个 <p> 元素,它们就按顺序排列,从 1 开始。





回页首


组合起来

在 RDBMS 中有了一束数据后,通常希望以结构化和有用的方式检索它们。幸运的是,随着对使用的主键和外键策略的基本理解,可以找到您所需的全部内容。实际上,通过许多方式,您在这时所具有的灵活性比使用 XPath 查询语法可能具有的灵活性要 好很多。请看清单 6 中的示例。


清单 6. 从 RDBMS 中选择数据
SELECT "Paragraph", p.seq, p._XML
  FROM title,panel,body,text__column TC,p
 WHERE title.foreign_key_panel = panel.primary_key
   AND body.foreign_key_panel = panel.primary_key
   AND TC.foreign_key_body = body.primary_key
   AND p.foreign_key_text__column = TC.primary_key
   AND title.PCDATA="About Haskell"
 ORDER BY p.seq
;

有必要进行一些解释。JOIN 的构成都一样: foreign_key_X 字段与某些表 Xprimary_key JOIN 起来。一旦所有 JOIN 都就位后,可以添加 ORDER、GROUP 等等更实质性的条件。在这种情况下,您希望查看其 <title> 是 "About Haskell" 的 <panel> 的所有 paragraph(段落)( <p> 元素)。结果看上去如清单 7 所示。


清单 7. 针对 XML 教程的 SQL 查询
C:\mysql2\bin>mysql -u root -pgnosis test < haskell.sql
Paragraph  seq  _XML
Paragraph  1    Haskell is just one of a number of functional programming...
Paragraph  2    Among functional languages, Haskell is in many ways the...
Paragraph  3    On a minor note, Haskell is syntactically easier to get...





回页首


结束语

本专栏阐明了 dtd2sqlxml2sql 的命令行用法。对于快速测试和实际的 shell 用法,这可能是您希望使用的方法。不过,和 Python 中的大多数事物一样,在自己的代码中重用这些模块是非常简单的。自测代码(命令行用法)为任一导入模块提供了一个可遵循的简单模板。我期待听到读者准备将这些模块用在一些很棒的用途 -- 就和您为许多其它人所做的一样。



参考资料

通过单击本页顶部或底部的 讨论来询问问题或对本文加以评论。

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.

  • 本文中讨论的模块可从以下站点下载: http://gnosis.cx/download/dtd2sql.pyhttp://gnosis.cx/download/xml2sql.py

  • 本文中使用的支持和数据文件的归档可以在以下站点找到: http://gnosis.cx/download/xml_matters_12.zip

  • 可以在以下站点找到支持模块 xml_objectifyhttp://gnosis.cx/download/xml_objectify.py

  • 通常,Gnosis Software 下载目录包含了我开发的各种软件,大多用于 IBM developerWorks 专栏和文章。通常可以在该目录找到各种版本的特殊软件模块,包括最新最完善的那些以及早期的那些。请查看: http://gnosis.cx/download/

  • 复习 David Mertz 以前的“XML 话题”专栏:
    • XML 问题 #1介绍 Python xml_pickle 对象。
    • XML 问题 #2描述如何使用 Python 的 xml_objectify。
    • XML 问题 #3介绍 DocBook。
    • XML 问题 #4继续介绍如何使用 DocBook 构建旧的文档档案。
    • XML 问题 #5说明如何通过 XSLT 将 XML 文档转换成 HTML。
    • XML 问题 #6比较了几种 XML 编辑器,同时特别着眼于那些适合于文本繁重的文档的工具。
    • XML 问题 #7将 DTD 与 XML Schema 进行了权衡,并建议无论 W3C XML Schema 规范有多么成熟,开发者在什么情况下需要坚持使用 DTD。
    • XML 问题 #8讨论由计算机科学家所概念化出来的数据 模型的抽象理论是如何帮助我们开发特定的多表示数据流的。
    • XML 话题 #9 讨论了公众域 sql2dtdsql2xml 实用程序;这些实用程序可以不依赖 RDBMS 生成可移植 XML 结果集。
    • XML 话题 #10扩展 David 的“可爱的 Python #15”专栏中介绍的常规全文本索引器来包括特定于 XML 的搜索和索引特性,并讨论索引器如何利用 XML 的分层节点结构。
    • XML 话题 #11 重温了在本系列第一个专栏中介绍的模块 xml_picklexml_objectify

  • 请参与包含 17 个问题的 有关开发习惯的调查以帮助 IBM 改进开发软件应用程序的 XML 工具开发和服务。


关于作者

author

David Mertz 相信如果没有数据,生活本身将是不可能的 ... 好吧,至少因特网不会那么繁荣。可以通过 mertz@gnosis.cx与 David 联系;在 http://gnosis.cx/publish/上详细介绍了他的生活。非常欢迎对过去的、这一篇和将来的专栏文章提出建议和意见。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款