以文本方式查看主题

-  W3CHINA.ORG讨论区 - 语义网·描述逻辑·本体·RDF·OWL  (http://bbs.xml.org.cn/index.asp)
--  『 XML 与 数据库 』  (http://bbs.xml.org.cn/list.asp?boardid=17)
----  映射 DTD 到数据库  (http://bbs.xml.org.cn/dispbbs.asp?boardid=17&rootid=&id=328)


--  作者:mhss
--  发布时间:12/22/2003 12:26:00 PM

--  映射 DTD 到数据库
http://mhss.nease.net/xml/dtd_map.html
映射 DTD 到数据库
作者:Ronald Bourret

May 09, 2001

翻译:寒蝉退士

译者声明:译者对译文不做任何担保,译者对译文不拥有任何权利并且不负担任何责任和义务。

原文:http://www.xml.com/pub/a/2001/05/09/dtdtodbs.html


--------------------------------------------------------------------------------

目录
1. 概述
2. 基于表的映射
3. 对象-关系映射
3.1. 基本映射
3.1.1. 映射 DTD 到对象模式
3.1.2. 映射对象模式到数据库模式
3.1.3. 杂记
3.2. 映射复杂内容模型
3.2.1. 映射序列
3.2.2. 映射选择
3.2.3. 映射重复子元素
3.2.4. 映射可选子元素
3.2.5. 映射子组
3.3. 映射混合内容
3.4. 映射次序
3.4.1. 同级次序, 层次次序, 和文档次序
3.4.2. 映射同级次序
3.4.2.1. 次序属性和列
3.4.2.2. 在映射中存储次序
3.5. 映射属性
3.5.1. 映射单值和多值属性
3.5.2. 映射 ID/IDREF(S) 属性
3.5.3. 映射注记属性
3.5.4. 映射 ENTITY/ENTITIES 属性
3.6. 可供选择的映射
3.6.1. 映射复杂元素类型到标量类型
3.6.2. 映射标量类属性到属性表
3.7. 结论
4. 生成模式
4.1. 从 DTD 生成关系数据库模式
4.2. 从数据库模式生成 DTD
5. 映射 XML 模式到数据库
6. 有关的话题

--------------------------------------------------------------------------------

1. 概述
在 XML 社团中一个常见的问题是如何把 XML 映射到数据库。本文讨论两种映射: 基于表的映射和对象-关系(基于对象)的映射。这两种映射都建模在 XML 文档中的数据、而不是文档自身。这使得这些映射对于以数据为中心的文档是良好的选择,而对于以文档为中心的文档是贫乏的选择。基于表的映射完全不能处理混合内容,而混合内容的对象-关系映射是非常低效的。

这两种映射通常用做在 XML 文档和数据库、特别是关系数据库之间传输数据的软件的基础。它们在这方面的一个重要特征是双向的。就是说,它们可以用来从 XML 文档向数据库、从数据库向 XML 文档传输数据。 结果之一是它们可用做规范映射,在它顶上的 XML 查询语言可以被建造在非 XML 数据库上。规范映射将定义可以用类似 XQuery 的某种东西来查询的虚拟 XML 文档。

除了在 XML 文档和数据库之间传输数据之外,对象关系映射首要角色是用在“数据绑定”之中,这是在 XML 文档和对象之间的数据集结和解散。

2. 基于表的映射
在下面的 XML 文档和表之间有明显的映射:

   <A>                     
      <B>
         <C>ccc</C>             Table A
         <D>ddd</D>             -------
         <E>eee</E>          C     D     E
      </B>                  ---   ---   ---
      <B>             <=>   ...   ...   ...
         <C>fff</C>         ccc   ddd   eee
         <D>ggg</D>         fff   ggg   hhh
         <E>hhh</E>         ...   ...   ...
      </B>
   </A>
   
它叫做基于表的映射。它把文档看成一个单一的表或一组表。文档的结构必须是

   <Table>
      <Row>
         <Column_1>...</Column_1>
         ...
         <Column_n>...</Column_n>
      </Row>
      ...
      <Row>
         <Column_1>...</Column_1>
         ...
         <Column_n>...</Column_n>
      </Row>
   </Table>
或者是

   <Tables>
      <Table_1>
         <Row>
            <Column_1>...</Column_1>
            ...
            <Column_n>...</Column_n>
         </Row>
         ...
      </Table_1>
      ...
      <Table_n>
         <Row>
            <Column_1>...</Column_1>
            ...
            <Column_m>...</Column_m>
         </Row>
         ...
      </Table_n>
   </Tables>
同这个限制在一起的还有,列数据可以被表示为唯 PCDATA 元素(可显示的)或属性。

这种映射的明显的优点是它的简单性。因为它匹配在关系数据库中的表和结果集的结构,基于这种映射写代码很容易、快速、缩放自如,并对特定应用非常有用,比如在数据库之间一次一个表的传输数据。

这种映射有许多缺点;首先,它只能处理 XML 文档的非常小的子集。此外,它不保存物理结构(比如字符和实体引用,CDATA 段,字符编码,和孤立的声明)或文档信息(比如文档类型或 DTD),注释,或处理指令。

基于表的映射通常被中间件用来在 XML 文档和关系数据库之间传输数据。它还在某些 Web 应用服务器中用来把结果集数据返回为 XML。

3. 对象-关系映射
由于基于表的映射只能处理 XML 文档的一个有限的子集,一些中间件工具、多数启用 XML 的关系数据库、和多数启用 XML 的对象服务器使用一个更加完善的映射,它叫做对象-关系映射。它把 XML 文档建模为特定于在文档中数据的对象的一个树,接着把这些对象映射到数据库。

(名字“对象-关系”实际上是用词不当的 -- 更好的名字是基于对象的映射。这是因为对象可以被映射到非关系数据库,比如面向对象数据库或层次数据库, 或者简单的不去管它,在数据绑定系统中就是这么做的。但是,由于对象-关系是一个熟知的术语而且这种映射通常与关系数据库一起使用,所以这里用了这个术语。此外,所有例子都使用关系表。)

要理解对象-关系映射,最好先看一下一些简单的例子。作为开始,注意在下面 XML 文档、对象、和在表中的行之间有明显的映射:

        XML                    对象                     表
   =============           ============           ===============

                                                     Table A
   <A>                     object A {                 -------
      <B>bbb</B>              B = "bbb"            B     C     D
      <C>ccc</C>    <=>       C = "ccc"    <=>    ---   ---   ---
      <D>ddd</D>              D = "ddd"           ...   ...   ...
   </A>                    }                      bbb   ccc   ddd
                                                  ...   ...   ...
类似的,在下列元素类型定义、类和表模式之间也有明显的映射:

            DTD                           类                      表模式
   ======================           ============           ==========================

                                    class A {              CREATE TABLE A
                                       String B;              B VARCHAR(10) NOT NULL,
   <!ELEMENT A (B, C, D)>    <=>       String C;    <=>       C VARCHAR(10) NOT NULL,
                                       String D;              D VARCHAR(10) NOT NULL
                                    }                      )
作为一个更复杂的例子,考虑下列 XML 文档:

   <SalesOrder>
      <Number>1234</Number>
      <Customer>Gallagher Industries</Customer>
      <Date>29.10.00</Date>
      <Item Number="1">
         <Part>A-10</Part>
         <Quantity>12</Quantity>
         <Price>10.95</Price>
      </Item>
      <Item Number="2">
         <Part>B-43</Part>
         <Quantity>600</Quantity>
         <Price>3.99</Price>
      </Item>
   </SalesOrder>
它映射成下面的对象:

      object SalesOrder {
         number = 1234;
         customer = "Gallagher Industries";
         date = 29.10.00;
         items = {ptrs to Item objects};
      }           /   \
                 /     \
                /       \
   object Item {       object Item {
      number = 1;         number = 2;
      part = "A-10";      part = "B-43";
      quantity = 12;      quantity = 600;
      price = 10.95;      price = 3.95;
   }                   }
并接着映射成下面的表:

   SaleOrders
   ----------
   Number   Customer                Date
   ------   --------------------    --------
   1234     Gallagher Industries    29.10.00
   ...      ...                     ...
   ...      ...                     ...
   
   
   Items
   -----
   SONumber   Item   Part   Quantity   Price
   --------   ----   ----   --------   -----
   1234       1      A-10   12         10.95
   1234       2      B-43   600        3.99
   ...        ...    ...    ...        ...
所有这些都是从 DTD 到关系数据库表的对象-关系映射。

3.1. 基本映射
对象-关系映射用两个步骤完成。首先,把 XML 模式(这里是 DTD)映射成一个对象模式,接着把对象模式映射成数据库模式。这两步映射可以随意的被结合在一起为直接的 DTD-到-数据库映射,如同今天多数软件作的那样。

在考虑这种映射的时候,理解涉及到的对象是特定于每个 DTD 的,而不是来自 DOM 的对象是很重要的。特别是,这些对象建模在 XML 文档中的数据,而 DOM 建模 XML 文档的结构。例如,下面展示了为上面的例子中的 XML 文档建立的,特定于数据的对象和 DOM 对象的对象树:

    SalesOrder                  Document
     /      \                       |
   Item    Item                  Element_______
                          ______/ /  \ \______ \_________
                         /       /    \       \          \
                    Element  Element  Element  Element   Element
                       |        |        |    /  |  \ \    etc.
                     Text     Text     Text  /   |   \ \_______
                                            /    |    \        \
                                      Element Element Element  Attr
                                         |       |       |       |
                                       Text    Text    Text    Text
这个区别在你考虑数据是如何存储在数据库中的时候是非常重要的。要存储特定于数据的对象,你将需要 SalesOrders 和 Items 表;要存储 DOM 对象,你将需要 Document、Element、Text 和 Attr 表。最重要的不同点是非 XML 应用可以使用特定于数据的表,而不能使用特定于 DOM 的表。

3.1.1. 映射 DTD 到对象模式
这种映射开始于认识到元素类型是数据类型。有唯 PCDATA 内容的元素类型叫做简单元素类型;术语取自 W3C XML Schema。它们持有一个单一的数据值,并等价于在面向对象编程语言中的标量数据类型。(注意这里用的单词“标量”意思是“由一个单一的数据值组成”。在一些语言中,“标量”数据类型 -- 在这个单词的意义上的 -- 使用对象来表示。最显著的例子是 Java 中的 String 数据类型。) 属性类型也是简单类型。

有元素或混合内容、或有属性的元素类型叫做复杂数据类型;术语还是取自 XML Schema。它们持有有结构的值,并等价于在面向对象编程语言中的类或 C 中的结构。注意有空内容和有属性的元素类型仍是“复杂的”。如此做的原由是属性也提供了结构并粗略的等价于子唯 PCDATA 元素。

对象-关系映射首先把简单类型映射成标量数据类型。例如,元素类型 Title 可以映射成 String,而元素类型 Price 可以映射成 float。它接着映射复杂类型到类,同时把这个复杂类型的内容模型中的每个元素类型映射成这个类的类属性。每个类属性的数据类型都是引用的元素类型所映射成的数据类型。例如,到 Title 元素的一个引用将被映射成一个 String 类属性,而到 Price 元素的一个引用将被映射成一个 float 类属性。到复杂元素类型的引用被映射成到复杂元素类型所映射成的类的一个对象的指针/引用。

映射的最后一部分是把属性映射成类属性,同时由属性的数据类型决定类属性的数据类型。注意这些属性等价于在内容模型中到元素类型的引用。这是因为,就象在内容模型中的引用,它们局限在一个给定的元素类型中。它们只是概念上不同,属性类型是局部定义的,而不是在一个全局(DTD 范围)级别,对于元素类型是这种情况。

例如,在下面的简单元素类型 B、D、E 和属性 F 都被映射成 String 而复杂元素类型 A 和 C 被映射成类 A 和 C。 A 和 C 的内容模型和属性被映射成类 A 和 C 的类属性。在 A 和 C 的内容模型中到 B、D 和 E 的引用被映射成 String 类属性(因为这些类型被映射成 String),而属性 F 也被映射成一个 String 类属性。在 A 的内容模型中到 C 的引用被映射成带有到类 C 的一个对象的指针/引用的类型的一个类属性,因为元素类型 C 被映射成类 C。

              DTD                           类
   =========================           =============

   <!ELEMENT A (B, C)>                 class A {
   <!ELEMENT B (#PCDATA)>                 String b;
   <!ATTLIST A                  ==>       C      c;
          F CDATA #REQUIRED>              String f;
                                       }


   <!ELEMENT C (D, E)>                 class C {
   <!ELEMENT D (#PCDATA)>       ==>       String d;
   <!ELEMENT E (#PCDATA)>                 String e;
                                       }
这里要重申的一个要点是映射在内容模型中的到元素类型的引用不同于映射元素类型自身。元素类型被映射成数据类型,而到元素类型引用被映射成带有有结构的数据类型(类)的类属性。当你考虑到一个元素类型在两个不同的内容模型中被引用的时候这个不同就清楚了。在这种情况下,每个引用必须被单独的映射,同时结果类属性的数据类型由元素类型自身(不是引用)所映射成的数据类型决定。

例如,考虑下面的 Title 和 Section 元素类型。所有这些元素类型都在 Chapter 和 Appendix 的内容模型中被引用。每个引用都单独为每个父元素类型做映射。把引用到 Title 的类属性的数据类型映射成 String,因为 Title 包含唯 PCDATA 并被映射成一个 String。把引用到 Section 类属性的数据类型映射成到一个 Section 对象的指针/引用,因为 Section 元素类型是复杂的并被映射成一个 Section 类。

                     DTD                                        类
   =========================================               =============

   <!ELEMENT Chapter (Title, Section+)>                    class Chapter {
   <!ELEMENT Appendix (Title, Section+)>          ==>         String    title;
   <!ELEMENT Title (#PCDATA)>                                 Section[] sections;
   <!ELEMENT Section (#PCDATA | p | b | i)*>               }

                                                           class Appendix {
                                                              String    title;
                                                              Section[] sections;
                                                           }
这里要重申的另一个要点是简单元素类型和属性可以映射成除了 String 之外的其他类型。例如,一个叫做 Quantity 的元素类型可以映射成一个整数。在从 DTD 做映射的时候,这需要人为干涉,因为从唯 PCDATA 元素类型无法预知目标数据类型。但是,在从 XML Schema 做映射的时候,因为  XML Schemas 有数据类型所以目标类型是已知的。

3.1.2. 映射对象模式到数据库模式
在对象-关系映射的第二部分中,把类映射成表(叫做类表),把标量类属性映射成列,而把指针/引用类属性映射成主键/外键联系,例如:

        类                       表
   ============             =================
   class A {                Table A:
      String b;                Column b
      C      c;     ==>        Column c_fk
      String f;                Column f
   }

   class C {                Table C:
      String d;     ==>        Column d
      String e;                Column e
   }                           Column c_pk
注意这些表由主键 (C.c_pk) 和外键 (A.c_fk) 连接起来。因为在父元素和子元素之间的联系是一对一的,主键可以在任意一个表中。如果联系是一对多的,主键必须在联系的“一”端,不管是在父元素还是子元素。例如,如果 SalesOrder 元素包含多个 Item 元素,则主键必须在 SalesOrder 表(父元素)中。但是如果每个 Item 元素包含一个 Part 元素,主键必须在 Part 表(子元素)中,因为一个 part 可以出现在多个 item 中。

可以建立一个主键列作为映射的一部分,列 c_pk 的情况就是这样,或者使用一个现存的列或多个列作为主键。例如,如果一个 SalesOrder 元素类型有一个 Number 子元素,可以把它映射成主键列。

如果建立一个主键列作为映射的一部分,它的值必须由数据库或传输软件生成。尽管一般认为这是比使用数据列作为主键更好的数据库设计,在与 XML 一起使用的时候这是个缺点,这个生成的键在源数据库之外是无意义的。所以,在带有生成的键的数据传输到 XML 文档中的时候,它要么包含无意义的主键(如果传输了主键)要么根本没有主键(如果没有传输主键)。在后者情况下,有可能不能重标识数据的来源,如果数据被修改并作为 XML 文档被返回到数据库则这就是一个问题。

3.1.3. 杂记
在我们继续映射的更复杂部分之前,先需要提及两件事情。首先,在映射期间名字可以改变。例如,DTD、对象模式和关系模式可以全部使用不同的名字。例如,下列的 DTD 使用与下列的类不同的名字:

                DTD                                   类
   ===============================             =====================

   <!ELEMENT Part (Number, Price)>             class PartClass {    
   <!ELEMENT Number (#PCDATA)>         ==>        String numberProp;
   <!ELEMENT Price (#PCDATA)>                     float  priceProp;
                                               }
它使用与下列的表不同的名字:

         类                                表
   =====================             ==================

   class PartClass {                 Table PRT
      String numberProp;     ==>        Column PRTNUM
      float  priceProp;                 Column PRTPRICE
   }
其次,在映射中涉及到的对象是概念上的。就是说,当在 XML 文档和关系数据库之间传输数据的时候,   不需要实际上的实例化。(这不是说对象不能被实例化。实例化对象是否有用依赖于实际应用。)

3.2. 映射复杂内容模型
内容模型迄今为止都是相对简单的。对下面的更复杂的内容模型会怎样?

   <!ELEMENT A (B?, (C | ((D | E | F | G)*, (H | I)+, J?)))>
在本节中,我们将考虑内容模型的不同部分。上面例子的映射将留给读者做练习。(我总是希望这么说。)

3.2.1. 映射序列
象已经见到的那样,把在一个序列中的引用的每个元素类型映射成一个类属性,它将被映射成一个列或者一个主键、外键联系。例如:

            DTD                            类                        表
   ======================              ============             ==============

   <!ELEMENT A (B, C)>                 class A {                Table A
   <!ELEMENT B (#PCDATA)>     ==>         String b;     ==>        Column b
                                          C      c;                Column c_fk
                                       }

   <!ELEMENT C (D, E)>                 class C {                Table C
   <!ELEMENT D (#PCDATA)>     ==>         String d;     ==>        Column d
   <!ELEMENT E (#PCDATA)>                 String e;                Column e
                                       }                           Column c_pk
3.2.2. 映射选择
如同序列,把在一个选择中引用的每个元素类型也映射成一个类属性,接着映射成一个列或者一个主键、外键联系。同序列在方式上唯一的不同是这种类属性和列可以是 null。例如,假设我们在把在 A 的内容模型中的序列改成一个选择。从 DTD 到对象模式的映射将是

            DTD                                类
   ======================              ========================

   <!ELEMENT A (B | C)>                class A {
   <!ELEMENT B (#PCDATA)>     ==>         String b; // Nullable
                                          C      c; // Nullable
                                       }
   <!ELEMENT C (D, E)>                 class C {
   <!ELEMENT D (#PCDATA)>     ==>         String d;
   <!ELEMENT E (#PCDATA)>                 String e;
                                       }
而从对象模式到数据库模式的映射将是

           类                                    表
   ========================             ===========================

   class A {                            Table A (
      String b; // Nullable     ==>        Column b     // Nullable
      C      c; // Nullable                Column c_fk  // Nullable
   }
   class C {                            Table C (
      String d;                 ==>        Column d     // Not nullable
      String e;                            Column e     // Not nullable
   }                                       Column c_pk  // Not nullable
要知道为什么是这样,考虑下列 XML 文档,它遵守的上面的 DTD。因为在 A 的内容模型中的选择要求要么 B 要么 C (但不能两个都有)作为子元素而存在,两个相应的类属性(和列)中的一个将总是 null。

       XML                         对象                       表
   =============               ============               ===========

                                                            Table A
                                                           ---------
   <A>                         object a {                  b    c_fk
      <B>bbb</B>      ==>         b = "bbb"      ==>      ---   ----
   </A>                           c = null                bbb   NULL
                               }                          ...   ...
注意如果用来连接表的这个主键在表 A 中,则在表 C 中对应的外键列不能是有空值的。如果 A 元素确实有一个 C 子元素,这个列必须有一个值来把它连接到表 A 中正确的行。如果 A 元素确实没有 C 子元素,则简单的在表 C 中没有行。

3.2.3. 映射重复子元素
子元素在它们的父元素中可以出现多次,就叫做重复子元素,并被映射成多值类属性并接着映射成在一个表中的多个列或到一个单独的表,叫做属性表。

如果一个内容模型包含到一个简单类型的重复的引用,这些引用被映射成一个单一的类属性,它是已知大小的一个数组。它可以被映射成在一个表中的多个列或一个属性表。例如,下面展示如何把一个重复的引用映射成一个表中的多个列:

              DTD                                 类                           表
   =========================                 ==============               ===========

   <!ELEMENT A (B, B, B, C)>                 class A {                    Table A
   <!ELEMENT B (#PCDATA)>         ==>           String[] b;      ==>         Column b1
   <!ELEMENT C (#PCDATA)>                       String   c;                  Column b2
                                             }                               Column b3
                                                                             Column c
如果对一个引用提供了 + 或 * 操作符,则再次把引用映射成一个单一的类属性,它这次是未知大小的一个数组。因为值的数目可以是任意大的,这种类属性必须被映射成一个属性表,它将为每个值包含一行。通过主键、外键联系把属性表连接到类表,这里的主键在类表中。例如:

            DTD                              类                          表
   ======================               ==============               ==============

   <!ELEMENT A (B+, C)>                 class A {                    Table A
   <!ELEMENT B (#PCDATA)>      ==>         String[] b;      ==>         Column a_pk
   <!ELEMENT C (#PCDATA)>                  String   c;                  Column c
                                        }
                                                                     Table B
                                                                        Column a_fk
                                                                        Column b
3.2.4. 映射可选子元素
把可选的子元素映射成有空值的类属性,接着映射成有空值的列。对于出现在选择组中的子元素这已经见到过了,比如下面的从 DTD 到对象模式的映射:

              DTD                               类
   =========================            ========================

   <!ELEMENT A (B | C | D)>             class A {
   <!ELEMENT B (#PCDATA)>       ==>        String b; // Nullable
   <!ELEMENT C (#PCDATA)>                  String c; // Nullable
                                           D      d; // Nullable
                                        }
   <!ELEMENT D (E, F)>                  class D {
   <!ELEMENT E (#PCDATA)>       ==>        String e;
   <!ELEMENT F (#PCDATA)>                  String f;
                                        }
和对象模式到数据库模式的映射:

            类                                   表
   ========================               ========================

   class A {                              Table A
      String b; // Nullable      ==>         Column b     // Nullable
      String c; // Nullable                  Column c     // Nullable
      D      d; // Nullable                  Column d_fk  // Nullable
   }
   class D {                              Table D
      String e;                  ==>         Column e     // Not nullable
      String f;                              Column f     // Not nullable
   }                                         Column d_pk  // Not nullable
在对引用应用了 ? 或 * 操作符的时候它也是适用的,比如下面的从 DTD 到对象模式的映射:

            DTD                                  类
   ======================               ==========================

   <!ELEMENT A (B?, C*)>                class A {
   <!ELEMENT B (#PCDATA)>      ==>         String b;   // Nullable
   <!ELEMENT C (#PCDATA)>                  String c[]; // Nullable
                                        }
和对象模式到数据库模式的映射:

            类                                       表
   ==========================               ===============================

   class A {                                Table A
      String b;   // Nullable      ==>         Column b     // Nullable
      String c[]; // Nullable                  Column a_pk  // Not nullable
   }
                                            Table C
                                               Column a_fk  // Not nullable
                                               Column c     // Not nullable
注意用来存储  C 的列(在属性表 C 中)不能是有空值的。这是因为,如果在 A 元素中不存在 C 子元素 A,则简单的在表 C 中没有行。

3.2.5. 映射子组
把在子组中的引用映射成父类的类属性,接着映射成在类表中的列,比如下面的从 DTD 到对象模式的映射:

            DTD                                     类
   =========================              ============================

   <!ELEMENT A (B, (C | D))>              class A {
   <!ELEMENT B (#PCDATA)>        ==>         String b; // Not nullable
   <!ELEMENT C (#PCDATA)>                    String c; // Nullable
   <!ELEMENT D (E, F)>                       D      d; // Nullable
                                          }
   <!ELEMENT D (E, F)>                    class D {
   <!ELEMENT E (#PCDATA)>        ==>         String e;
   <!ELEMENT F (#PCDATA)>                    String f;
                                          }
和对象模式到数据库模式的映射:

           类                                       表
   ============================               ==========================

   class A {                                  Table A
      String b; // Not nullable      ==>         Column b    // Not nullable
      String c; // Nullable                      Column c    // Nullable
      D      d; // Nullable                      Column d_fk // Nullable
   }
   class D {                                  Table D (
      String e;                       ==>        Column e
      String f;                                  Column f
   }                                             Column d_pk
你可能在想象这是如何可能的。对于子组所强加的结构发生了什么? 实际上,这种结构只出现在内容模型中,而不在实例文档中。例如,下列两个文档都满足上面的内容模型:

   <A>
      <B>bbbbbb</B>
      <C>cccccc</C>
   </A>

   <A>
      <B>bbbbbb</B>
      <D>
         <E>eee</E>
         <F>fff</F>
      </D>
   </A>
从文档中无法确定子组的存在。在结构上,C 和 D 不能区别于 B;它们都只是 A 的子元素。所以,它们可以用象 A 的不在子组中的子元素那样映射。

把在子组中的引用直接映射成在父类中的类属性的一个结果是重复性和可选性可以是间接的。例如,在下列内容模型中 C、D 和 E 是既可选的又可重复的。它们是可重复的、因为 + 操作符间接的应用到它们上。C 是可选择的、因为它间接的在一个选择组中,而 D 和 E 是可选择的、因为它们间接的在一个选择组中。

               DTD                                       类
   ===============================            =============================

   <!ELEMENT A (B, (C | (D, E))+)>            class A {
   <!ELEMENT B (#PCDATA)>                        String   b;
   <!ELEMENT C (#PCDATA)>            ==>         String[] c; // May be null  
   <!ELEMENT D (E, F)>                           D[]      d; // May be null
   <!ELEMENT E (#PCDATA)>                        String[] e; // May be null
                                              }
3.3. 映射混合内容
除了可以包含混合在子元素之间的 PCDATA 之外,混合内容只是 * 操作符间接的应用在其上的一个选择组。所以,可以把混合内容中的元素类型引用首先映射成未知大小的有空值的数组类属性,接着映射成属性表。

要看如何映射混合的内容,考虑下列 XML 文档:

   <A>
   This text <c>cc</c> makes
   <b>bbbb</b> no sense
   <c>cccc</c> except as
   <b>bb</b> an example.
   </A>
然后注意它在本质上同于下列文档,其中的 PCDATA 被包装载 <pcdata> 元素中:

   <A>
   <pcdata>This text </pcdata><c>cc</c><pcdata> makes
   </pcdata><b>bbbb</b><pcdata> no sense
   </pcdata><c>cccc</c><pcdata> except as
   </pcdata><b>bb</b><pcdata> an example.</pcdata>
   </A>
从此,容易看出 PCDATA 可以象其他子元素一样对待。所以,把在混合内容中的 PCDATA 映射成已知道小的有空值的一个数组,接着映射成一个属性表。下面展示如何把混合内容从 DTD 映射到对象模式:

               DTD                                类
   ===============================           ===================

                                             class A {
   <!ELEMENT A (#PCDATA | B | C)*>              String[] pcdata;
   <!ELEMENT B (#PCDATA)>            ==>        String[] b;
   <!ELEMENT C (#PCDATA)>                       String[] c;
                                             }
并从对象模式映射成数据库模式:

         类                                 表
   ===================              ===================================

                                                       Table PCDATA
                                                    ------Column a_fk
   class A {                                       /      Column pcdata
      String[] pcdata;              Table A       /    Table B
      String[] b;           ==>        Column a_pk--------Column a_fk
      String[] c;                                 \       Column b
   }                                               \   Table C
                                                    \-----Column a_fk
                                                          Column c
要看在数据库中实际存储的东西,考虑在本节开始处展示的文档,它被映射成下面的对象,接着映射成灾下列的表中的行。(我们假定为在给 A 的表中的行生成值为 1 的一个主键。用它来把在表 A 中的行连接到其他表上。)

            对象                                        表
   ============================               ===============================

                                                            Table PCDATA
                                                            a_fk  pcdata
                                                            ----  -----------
                                                             1    This text
                                                             1    makes
   object a {                                                1    no sense
      pcdata = {"This text ",                                1    except as
                " makes ",                    Table A        1    an example.
                " no sense ",                   a_pk       
                " except as",        ==>        ----        Table B
                " an example."}                  1          a_fk   b
      b      = {"bbbb", "bb"}                               ----  ----
      c      = {"cc", "cccc"}                                1    bbbb
   }                                                         1    bb

                                                            Table C
                                                            a_fk   c
                                                            ----  ----
                                                             1    cc
                                                             1    cccc
从这个例子中明显容易的看出的一件事情是对象-关系映射在存储混合内容上不是很有效率的。为此,它更通常使用在以数据为中心的应用中,它们倾向于有很少的混合内容。

有两种方式解决这个问题,第一个是使用不同于对象-关系映射的其他映射。例如,如果这个文档使用 DOM 或类似结构来建模,并使用对象-关系映射把它映射到数据库,数据库中只有很少的表 -- Document, Element, Attr, Text 等。-- 尽管需要一个类似的连接编号来检索一个文档。第二种策略是不是把文档分解成最小的可能构件,而是分解成大的片断,比如章或节。这种策略可以与对象-关系映射一起使用;详情请参见章节 3.6.1,“映射复杂元素类型到标量类型”。

3.4. 映射次序
本节讨论对象-关系映射如何处理次序。

3.4.1. 同级次序, 层次次序, 和文档次序
同级(Sibling)意味着“兄妹”。就是说,同级元素或 PCDATA 是有相同父元素的元素或 PCDATA。换句话说,它们出现在同一个内容模型中。例如,如果在前面的章节中文档被表示为一棵树,这很容易的显示出那些元素是同级的: 这些元素在这个层次的第二级上,都有 A 作为它们的父元素。

                                   A
        ___________________________|______________________
       |      |    |    |     |      |      |      |      |
   This text  C  makes  B  no sense  C  except as  B  an example
              |         |            |             |
              cc       bbbb         cccc           bb
注意在第这个层次的第三级的元素不是同级的,因为它们不共享相同的父元素。这还指出了同级次序,它是在它们的父元素中子元素出现的次序,和层次次序,它是子元素在表示文档的树中出现在的级别,二者之间的不同。不同的还有文档次序,它是元素和文本在一个 XML 文档中出现的次序。例如:

同级次序(只有一个同级元素的地方次序不显示):

                                   A
        ___________________________|______________________
       |      |    |    |     |      |      |      |      |
   This text  C  makes  B  no sense  C  except as  B  an example
       1      2    3    4     5      6      7      8      9
              |         |            |             |
              cc       bbbb         cccc           bb
层次次序:

   1                                  A
           ___________________________|______________________
          |      |    |    |     |      |      |      |      |
   2  This text  C  makes  B  no sense  C  except as  B  an example
                 |         |            |             |
   3             cc       bbbb         cccc           bb
文档次序:

                                   A
                                   1
        ___________________________|______________________
       |      |    |    |     |      |      |      |      |
   This text  C  makes  B  no sense  C  except as  B  an example
       2      3    5    6     8      9      11     12     14
              |         |            |             |
              cc       bbbb         cccc           bb
              4         7            10            13
依据 XML 规定,同级次序是重要的。实际上,这依赖于应用。例如,在以数据为中心的应用中,使用 XML 文档来传载一个对象或表,同级次序通常是无关紧要的,因为面向对象语言没有在类属性之间的次序的概念。类似的,关系数据库没有在列之间的次序的概念。所以,同级次序在下列文档中不是重要的:

   <Part>
      <Number>123</Number>
      <Desc>Turkey wrench</Desc>
      <Price>10.95</Price>
   </Part>

   <Part>
      <Price>10.95</Price>
      <Desc>Turkey wrench</Desc>
      <Number>123</Number>
   </Part>
它们都被映射成下列对象和表中的行:

         对象                                         表
   =========================               ===================================
                                                     Table Parts
   object part {                           -------------------------------
      number = 123                ==>      Number  Desc           Price
      desc = "Turkey wrench"               ------  -------------  -----
      price = 10.95                         123    Turkey wrench  10.95
(对此的一个主要的例外是在以数据为中心的文档必须匹配一个特定的 DTD 的时候。这在一个应用必须验证文档的时候发生,比如在它们来自未知或不被信任的来源的时候。尽管在这种情况下 XML Schemas 的“all 组”通过允许一组子元素以任何次序出现能帮上忙,但它们不支持重复子元素。)

在另一方面,在以文档为中心的应用中,通常文档是为了人的消费而设计的,同级次序是非常重要的。例如,我很可能喜欢第一个评述而不是第二个:

   <Review>
      <p>Ronald Bourret 是一个
      <b>优秀的作家</b>。
      只有<b>傻瓜</b>
      才不去读他的作品。</p>
   </Review>

   <Review>
      <p>Ronald Bourret 是一个
      <b>傻瓜</b>。只有
      <b>优秀的作家</b>
      才不去读他的作品。</p>
   </Review>
对象-关系映射可以保留同级次序,下面将会见到,尽管实际上很少有产品支持它。通过把到简单元素类型的引用映射到在表中的列,和把到复杂元素类型的引用映射成主键、外键联系,它本能的保留层次次序。在保留了层次次序和同级次序的时候就保留了文档次序。

3.4.2. 映射同级次序
因为面向对象语言没有类属性之间次序的概念,而关系数据库没有列之间次序的概念,必须与数据值独立的存储同级次序值。这么做的一种方式是介入在其中存储次序值的独立类属性和列。这么做的另一种方式在映射自身中存储次序值。

3.4.2.1. 次序属性和列
使用次序类属性和次序列来存储次序值。它们对立于数据类属性和数据列。对认定次序很重要的每个被引用的元素类型或 PCDATA 都需要一个类属性或列。例如,考虑上面的混合内容的例子。下面把在 DTD 中的同级次序映射到次序类属性:

                DTD                                      类
   ===============================               ========================

                                                 class A {
                                                    String[] pcdata;
                                                    int[]    pcdataOrder;
   <!ELEMENT A (#PCDATA | B | C)*>                  String[] b;
   <!ELEMENT B (#PCDATA)>               ==>         int[]    bOrder;
   <!ELEMENT C (#PCDATA)>                           String[] c;
                                                    int[]    cOrder;
                                                 }
并接着映射到次序列:

           类                                            表
   ========================               ========================================

                                                             Table PCDATA
   class A {                                               -----Column a_fk
      String[] pcdata;                                    /     Column pcdata
      int[]    pcdataOrder;                              /      Column pcdataOrder
      String[] b;                         Table A       /    Table B
      int[]    bOrder;           ==>         Column a_pk--------Column a_fk
      String[] c;                                       \       Column b
      int[]    cOrder;                                   \      Column bOrder
   }                                                      \  Table C
                                                           \----Column a_fk
                                                                Column c
                                                                Column cOrder
注意在标中存储的次序类属性与它们定序的类属性是并列的。

下面的例子展示使用次序属性来保留在"makes-no-sense"例子中的同级次序。这里要注意的重要的事情是所有次序类属性共享相同的次序空间。出现在一个次序类属性中一个次序值不会出现在另一个次序类属性中。

           类                                                表
   =================================               =====================================

                                                            Table PCDATA
                                                            a_fk pcdata      pcdataOrder
                                                            ---- ----------- -----------
                                                             1   This text   1
   object a {                                                1   makes       3
      pcdata      = {"This text ",                           1   no sense    5
                     " makes ",                              1   except as   7
                     " no sense ",                 Table A   1   an example. 9
                     " except as",                 a_pk       
                     " an example."}      ==>      ----     Table B
      pcdataOrder = {1, 3, 5, 7, 9}                 1       a_fk  b   bOrder
      b           = {"bbbb", "bb"}                          ---- ---- ------
      bOrder      = {4, 8}                                   1   bbbb 4
      c           = {"cc", "cccc"}                           1   bb   8
      cOrder      = {2, 6}          
   }                                                        Table C
                                                            a_fk  c   cOrder
                                                            ---- ---- ------
                                                             1   cc   2
                                                             1   cccc 6
尽管次序类属性最常用来维护在混合内容中的次序,它们也可以与元素内容一起使用。例如,考虑下面的元素类型定义。因为在 A 中 B 可以出现任意次,它被存储在独立的属性表中。没有次序类属性,将无法确定如何定序 B 子元素。(注意这里不能使用行次序,因为关系数据库不保证以任何特定的次序返回行。)

   <!ELEMENT A (B*, C)>
3.4.2.2. 在映射中存储次序
在许多情况下,同级次序只在验证的时候是重要的;除了必须能验证一个文档之外,应用自身不关心同级次序。特别是在以数据为中心的文档中元素内容。在这种情况下,在映射自身中存储次序信息就足够了。

例如,给出下列内容模型,映射可以存储 A 的子元素的次序是 B 然后 C 然后 D 的信息:

   <!ELEMENT A (B, C, D)>
特别是,限制为在映射中存储次序信息。例如,考虑下面的内容模型:

   <!ELEMENT A (B?, C, B)>
构造匹配这个内容模型的文档要求软件首先决定能获得多少数据来构造 B 元素。如果只有构造一个 B 元素的足够数据,它不能第一个 B 元素,因为第二个 B 元素是必须的。

让多数软件这么麻烦的这么做是不大可能的。转而,只对组织所有相同元素类型的同级元素在一起的那些内容模型提供合理的限制。对于许多以数据为中心的内容模型这是足够的,并可以通过在映射中存储每个元素在内容模型中的位置来实现。

例如,在下列元素中同级元素的次序可以被如此映射。注意在第三个内容模型中,Author 和 Editor 二者都可以被赋予相同的次序值或不同的值;如果它们被赋予不同的值,一种类型的所有元素都都会出现在其他类型的任何元素的前面。

   <!ELEMENT Part (Number, Description, Price)>
   <!ELEMENT Order (Number, CustNum, Date, Item*)>
   <!ELEMENT Book (Title, (Author | Editor)+, Price, Review*)>
当次序信息只存储在映射中的时候,只要内容模型包含多于一个相同类型的元素,文档的无损流通(round-tripping)就是不可能的。例如,考虑下面的内容模型:

   <!ELEMENT A (B+, C)>
尽管映射可以告诉软件所有 B 元素必须出现在 C 元素起前面,它不能指定 B 元素的次序。所以,如果数据从包含这种内容模型的文档传输到数据库然后再传输回来,就不能保证 B 元素按原始文档中的次序出现。幸运的是,对以数据为中心的文档这通常不是问题。

3.5. 映射属性
如同前面所见到的,把属性映射为标量类属性。本节将讨论这种映射的详情,还有其他一些要点。

3.5.1. 映射单值和多值属性
有两种不同的属性: 单值(CDATA、ID、IDREF、NMTOKEN、ENTITY、NOTATION 和枚举)和多值(IDREFS、 NMTOKENS 和 ENTITIES)。预期的可能是,把它们映射成单值类属性(接者是列)和多值类属性(接着是属性表)。例如:

               DTD                                   类                       表
   ============================                ============               ===========

   <!ELEMENT A (B, C)>                         class A {                  Table A
   <!ATTLIST A                                    String b;                  Column B
             D CDATA #REQUIRED>      ==>          String c;      ==>         Column C
   <!ELEMENT B (#PCDATA)>                         String d;                  Column D
   <!ELEMENT C (#PCDATA)>                      }
和:

               DTD                            类                            表
   ========================               ==============               ==============

   <!ELEMENT A (B, C)>                    class A {                    Table A
   <!ATTLIST A                               String   b;                  Column a_pk
         D IDREFS #IMPLIED>      ==>         String   c;      ==>         Column b
   <!ELEMENT B (#PCDATA)>                    String[] d;                  Column c
   <!ELEMENT C (#PCDATA)>                 }                            Table D
                                                                          Column a_fk
                                                                          Column d
依据 XML 信息集属性出现的次序是不重要的。例如,下列两个 XML 被认为是等同的。故此,不需要次序类属性来维护属性出现的次序,尽管这么做是完全可能的。

   <A B="bbb"
      C="ccc"
      D="ddd"/>

   <A C="ccc"
      B="bbb"
      D="ddd"/>
在另一方面,值出现在多值属性中的次序被认为是重要的。这同于同级元素和 PCDATA 的情况,可以使用次序类属性来维护关于在多值属性中值出现次序的信息。但是,在用于同级元素和 PCDATA 的次序类属性、和用于多值属性的次序类属性之间有一个重要的区别: 用于多值属性的每个次序类属性都有它自己的次序空间。这可以在下面的例子中见到:

        XML                       对象
   ===============             =========================

                               object a {
   <A B="dd ee ff"                b = {"dd", "ee", "ff"}
      C="gg hh"/>      ==>        bOrder = {1, 2, 3}
                                  c = {"gg", "hh"}
                                  cOrder = {1, 2}
                               }
提醒读者注意次序类属性在对象级别不是严格必须的;可以用数组次序替代。但是,在关系数据库中是必须的,因为这里没有行次序的概念。

3.5.2. 映射 ID/IDREF(S) 属性
ID 属性用来唯一的标识在 XML 文档中的元素。使用 IDREF 和 IDREFS 属性,通过提及后面的元素的 ID 来把一个元素与另一个元素关联起来。通常在不能通过把一个元素嵌套在另一个元素之中来形成这种关联的时候这么做。例如,考虑下面的有向图:

          A
         / \
        B   C
         \ /
          D
它可以在 XML 文档中表示为:

   <A>
      <B ref_d="1">
         ...
      </B>
      <C ref_d="1">
         ...
      </C>
      <D id="1">
         ...
      </D>
   </A>
ID/IDREF(S) 属性映射为主键、外键联系。例如,上面的文档可以在数据库中存储为下列表和列:

               Table A
                  Column a_pk
                  ...
                /     \
               /       \
   Table B              Table C
      Column a_fk          Column a_fk
      Column ref_d         Column ref_d
      ...                  ...
              \         /
               \       /
               Table D
                  Column a_fk
                  Column id
                  ...
在数据库中存储 ID/IDREF(S) 属性的时候,数据传输软件需要小心的一件事是 ID 只保证在一个给定的 XML 文档内是唯一的。所以,如果来自多于一个文档的数据存储在相同的表中,则不能保证 ID 是唯一的。这个问题的解决是以某种方式“修饰” ID。可以通过把属性映射成两列,一列包含对于每个文档是唯一的一个值,另一列包含 ID,或者修饰 ID 自身,比如用加以唯一值的前缀来完成。

在数据从数据库传输到 XML 文档的时候存在类似的问题。如果取回的数据起源于多于一个文档,则数据传输软件需要确保 ID 值是唯一的。这可能涉及到改变一个或多个值,连同引用它们的所有 IDREF(S) 属性的值。

目前,多数产品不把 ID/IDREF 作为有别于其他属性的属性来支持。

3.5.3. 映射注记属性
在 XML 文档中使用注记(notation)来提醒应用如何处理一个元素或未分析的实体。例如,下列的 "xhtml" 注记可以告诉应用这个元素包含 XHTML 并应当使用浏览器来显示:

   <Desc type="xhtml">
      <html>
      <head><title>Turkey wrench</title></title></head>
      <body>
      <p>A very <b>big</b> turkey wrench.</p>
      </body>
      </html>
   </Desc>
注记属性和它们的值对于对象-关系映射通常没有什么意义;把它们作为简单的另一种属性来对待。

对此的唯一的例外发生在注记指示包含的文本的数据类型的时候。例如,注记“base64”可以告诉应用包含二进制数据的一个元素被编码为 Base64 (映射二进制数据到 US-ASCII 的一个子集的一种 MIME 编码)。在多数情况下,这种信息只对生成映射的软件有意义。它可以使用这种信息来映射元素类型到二进制值类属性并接着到 BLOB (二进制大对象)。在这些情况下,映射自身不使用这些信息。从元素到到 BLOB 的映射独立于注记包含数据类型信息的事实。

对此的唯一的例外是当数据传输软件复杂到基于注记值来在运行时间切换映射的时候。在这种情况下,每种可能的注记被映射成一种数据类型,接着使用它来转换数据。

3.5.4. 映射 ENTITY/ENTITIES 属性
使用 ENTITY 和 ENTITIES 属性把未分析的、外部数据(比如一个二进制文件)与 XML 文档关联起来。映射它们同任何其他属性一样,不同的是,在传输数据的时候,用实体替换属性值(在从 XML 传输数据到数据库的时候),或者可以建立一个新实体并把它的标示符存储为属性值(在从数据库传输数据到 XML 的时候)。因为未分析实体值可以动态生成,映射指定在数据库中存储值还是实体 URI 是个好主意。

因为未分析实体总是有相关的注记,在决定实体的数据类型(在映射时间或运行时间)的时候可能用到这些注记。

3.6. 可供选择的映射
在前面的章节中,我们已经描述了如何映射 DTD 到数据库。实际上,这些描述是不完整的,因为还有一些其他方式来完成映射。在本节中,我们将讨论两种最重要的替代者。

3.6.1. 映射复杂元素类型到标量类型
尽管复杂元素类型通常映射成类并接着映射成表,也可能把它们映射成标量类型。换句话说,到复杂类型的引用可以被映射成标量类属性。这种类属性的值一般是元素内容,串行化为 XML。在元素的值之作为整体才有意义并且不应该分解为更小的部分的时候这是有用的。

例如,考虑给出关于 part 的信息的一个 XML 文档。如果某个子元素被部分的用 XHTML 描述,进一步分解它可能没有意义。如同我们已经描述的那样,这将导致数据被分散到许多表中;italic 字一个表,bold 字一个表,用在 hyperlink 中的字一个表,等等。所以,最好在一个单一列中存储这些描述:

             DTD                               类                           表
   ===========================              ===============             ==============

   <!ELEMENT Part (Num, Desc)>              class Part {                Table Part
   <!ELEMENT Number (#PCDATA)>      ==>        String num;      ==>        Column num
   <!-- Use Inline entity                      String desc;                Column desc
        from XHTML -->                      }
   <!ELEMENT Desc (%Inline;)>
例如,下面的描述将如此存储:

   <Part>
      <Number>127</Number>      Table Part
      <Desc>                    Num  Desc
         A very <b>big</b>  =>  ---  ----------------
         turkey wrench.         127  A very <b>big</b>
      </Desc>                        turkey wrench.
   </Part>
注意存储数据为 XML 确实导致数据传输软件的问题。特别是,软件不能区别标记和数据。例如,应用如何决定在下列文本中的 <b> 是一个 <b> 元素还是文本?

   An example of the <b> element is <b>this element</b>.
对此的一个解决方法是在数据库中使用标签存储实际元素,和使用实体引用来存储字符:

   An example of the &lt;b&gt; element is <b>this element</b>.
这么做的问题是非 XML 应用不能按它们希望的那样查找数据库。

3.6.2. 映射标量类属性到属性表
尽管单值的、标量值类属性通常映射成列,它们也可以被映射成属性表。这是有用的,例如,在独立于主要表的一个表中存储 BLOB 或不经常使用的类属性。例如:

      类                            表
   ===============             ==================

   class Part {                Table Parts
      String num;      ==>        Column num
      String desc;
                               Table Descriptions
   }                              Column num
                                  Column desc
3.7. 结论
对象-关系映射处理所有 XML 文档,有效的映射到对象,并允许非 XML 应用使用在数据库中的数据。故此,对于以数据为中心的文档是个好主意并且(不奇怪的)在某些中间件、多数启用 XML 的数据库、和多数启用 XML 的对象服务器中用作底层模型。

应当注意所有这些产品实现了对象-关系映射轻微不同的版本,并且没有一个实现了映射中所有可能的东西。在映射的更加公用的部分中,都把复杂元素映射成类并把到元素类型的引用映射成类属性,同样的使用主键、外键对来连接表。但是,一些只对唯 PCDATA 元素映射列,另一些只对属性映射列,还有其他一些允许二者。类似的,多数这些产品不支持同级次序或混合内容,并且许多在映射期间都不允许用户改变名字。

对象-关系映射对于普通文档不是个好的选择。首先,在使用混合内容的时候它是低效的。其次,象基于表的映射一样,它不保留物理结构、注释和处理指令。

4. 生成模式
我们现在考虑如何依据对象-关系映射从 DTD 生成关系数据库模式和反之。因为通过对象-关系映射有很多可能的路径,这里的算法在每次选择时简单的选择最经常使用分支。例如,可以把到唯 PCDATA 元素的单一引用映射成一列或一个单独的属性表。因为最通用的选择是把它们映射一个列,下面的算法从这样的引用生成一列。

对于面向对象数据库,生成过程是类似的。

4.1. 从 DTD 生成关系数据库模式
通过通读 DTD 并处理每个元素类型来生成关系模式:

复杂元素类型生成带有主键列的类表。
除了在处理内容模型的时候之外忽略简单元素类型。
要处理一个类型模型:

到简单元素类型的单一引用生成列;如果这个引用是可选的(? 操作符),这个列是有空值的。
到简单元素类型的重复引用生成带有外键的属性表。
到复杂元素类型的引用生成在远端(remote)类表中的外键。
在混合内容中的 PCDATA 生成带有一个外键的属性表。
对所有被引用的元素类型和 PCDATA 随意的生成有次序的列。
要处理属性:

单值属性生成列;如果属性是可选的,这个列是有空值的。
多值属性生成带有外键的属性表。
如果一个属性有缺省值,则把它用作列缺省值。
下面的例子展示了这个过程是如何工作的。考虑下列 DTD:

                          DTD                                           表
   =================================================               =================

   <!ELEMENT Order (OrderNum, Date, CustNum, Item*)>
   <!ELEMENT OrderNum (#PCDATA)>
   <!ELEMENT Date (#PCDATA)>
   <!ELEMENT CustNum (#PCDATA)>
                                                          
   <!ELEMENT Item (ItemNum, Quantity, Part)>
   <!ELEMENT ItemNum (#PCDATA)>
   <!ELEMENT Quantity (#PCDATA)>
                                                          
   <!ELEMENT Part (PartNum, Price)>
   <!ELEMENT PartNum (#PCDATA)>
   <!ELEMENT Price (#PCDATA)>
在第一步,我们为复杂元素类型生成表和这些表的主键:

                          DTD                                           表
   =================================================               =================

   <!ELEMENT Order (OrderNum, Date, CustNum, Item*)>      ==>      Table Order
   <!ELEMENT OrderNum (#PCDATA)>                                      Column OrderPK
   <!ELEMENT Date (#PCDATA)>
   <!ELEMENT CustNum (#PCDATA)>
                                                         
   <!ELEMENT Item (ItemNum, Quantity, Part)>              ==>      Table Item
   <!ELEMENT ItemNum (#PCDATA)>                                       Column ItemPK
   <!ELEMENT Quantity (#PCDATA)>
                                                          
   <!ELEMENT Part (PartNum, Price)>                       ==>      Table Part
   <!ELEMENT PartNum (#PCDATA)>                                       Column PartPK
   <!ELEMENT Price (#PCDATA)>
在第二步,我们为到简单元素类型的引用生成列:

                          DTD                                           表
   =================================================               =================

   <!ELEMENT Order (OrderNum, Date, CustNum, Item*)>      ==>      Table Order
   <!ELEMENT OrderNum (#PCDATA)>                                      Column OrderPK
   <!ELEMENT Date (#PCDATA)>                                          Column OrderNum
   <!ELEMENT CustNum (#PCDATA)>                                       Column Date
                                                                      Column CustNum
                                                          
   <!ELEMENT Item (ItemNum, Quantity, Part)>              ==>      Table Item
   <!ELEMENT ItemNum (#PCDATA)>                                       Column ItemPK
   <!ELEMENT Quantity (#PCDATA)>                                      Column ItemNum
                                                                      Column Quantity

   <!ELEMENT Part (PartNum, Price)>                       ==>      Table Part
   <!ELEMENT PartNum (#PCDATA)>                                       Column PartPK
   <!ELEMENT Price (#PCDATA)>                                         Column PartNum
                                                                      Column Price
在最后一步,我们为到复杂元素类型的引用生成外键:

                          DTD                                           表
   =================================================               =================

   <!ELEMENT Order (OrderNum, Date, CustNum, Item*)>      ==>      Table Order
   <!ELEMENT OrderNum (#PCDATA)>                                      Column OrderPK
   <!ELEMENT Date (#PCDATA)>                                          Column OrderNum
   <!ELEMENT CustNum (#PCDATA)>                                       Column Date
                                                                      Column CustNum
                                                          
   <!ELEMENT Item (ItemNum, Quantity, Part)>              ==>      Table Item
   <!ELEMENT ItemNum (#PCDATA)>                                       Column ItemPK
   <!ELEMENT Quantity (#PCDATA)>                                      Column ItemNum
                                                                      Column Quantity
                                                                      Column OrderFK
                                                          
   <!ELEMENT Part (PartNum, Price)>                       ==>      Table Part
   <!ELEMENT PartNum (#PCDATA)>                                       Column PartPK
   <!ELEMENT Price (#PCDATA)>                                         Column PartNum
                                                                      Column Price
                                                                      Column PartFK
生成的模式将不会同人工写的模式一样。除了命名问题(例如,一个人可能把表叫做 Orders、Items, and Parts),生成算法不能确定在 Items 和 Parts 直接的联系是多对一的。这个算法还不能识别 OrderNum 和 PartNum 可以用作主键,并且它不能决定数据类型和列长度,而 XML Schemas 解决了后者问题。尽管没有命名冲突发生或非法名字生成,但二者都是可能的。

4.2. 从数据库模式生成 DTD
生成 DTD 从一个单一的“根”表或一组根表并处理每个根表开始:

每个根表生成带有一个单一序列形式的元素内容的一个元素类型。
在表中的每个数据(非键)列生成带有唯 PCDATA 内容的一个元素类型和在序列中的一个引用;有空值的列生成可选的引用。
主键和外键生成如下:

远端表用同根表相同的方式处理。
把到远端表的元素类型的引用增加到序列中。
如果键是主键,则引用是可选的和重复的(* 操作符)。这是因为不能保证在外键表中有一行存在,也不能担保只有一行存在。
如果键是主键,为在这键中的每个列随意的生成唯 PCDATA 的元素类型。如果生成了它们,向序列添加到这些元素类型的引用。这只在主键包含数据时是有用的。
如果键是外键并且是有空值的,引用是可选的(? 操作符)。
在这个处理期间,用来到达表的键(如果有的话)是不处理的。这避免了算法重复建立那些在父表中建立了的元素类型。

下列例子展示这个处理是如何工作的。考虑下列数据库模式:

   Table Orders
      Column OrderNum
      Column Date
      Column CustNum

   Table Items
      Column OrderNum
      Column ItemNum
      Column Quantity
      Column PartNum

   Table Parts
      Column PartNum
      Column Price
在第一步,我们为根表(Orders)生成一个元素类型:

        表                                            DTD
   ==================               ===================================================

   Table Orders            ==>      <!ELEMENT Orders ()>
      Column OrderNum
      Column Date
      Column CustNum

   Table Items
      Column OrderNum
      Column ItemNum
      Column Quantity
      Column PartNum

   Table Parts
      Column PartNum
      Column Price
接着,我们为数据列(Date 和 CustNum)生成唯 PCDATA 元素,并在 Orders 元素的内容模型中添加到这些元素的引用:

        表                                            DTD
   ==================               ===================================================

   Table Orders            ==>      <!ELEMENT Orders (Date, CustNum)>
      Column OrderNum
      Column Date                   <!ELEMENT Date (#PCDATA)>
      Column CustNum                <!ELEMENT CustNum (#PCDATA)>

   Table Items
      Column OrderNum
      Column ItemNum
      Column Quantity
      Column PartNum

   Table Parts
      Column PartNum
      Column Price
现在我们为主键(OrderNum)生成一个唯 PCDATA 元素并向内容模型添加到它的一个引用:

        表                                           DTD
   ==================               ===================================================

   Table Orders            ==>      <!ELEMENT Orders (Date, CustNum, OrderNum)>
      Column OrderNum               <!ELEMENT OrderNum (#PCDATA)>
      Column Date                   <!ELEMENT Date (#PCDATA)>
      Column CustNum                <!ELEMENT CustNum (#PCDATA)>

   Table Items
      Column OrderNum
      Column ItemNum
      Column Quantity
      Column PartNum

   Table Parts
      Column PartNum
      Column Price
接着为主键被导出到其中的表(Items)增加一个元素类型,同样在它的内容模型中增加到它的一个引用:

        表                                            DTD
   ==================               ===================================================

   Table Orders                     <!ELEMENT Orders (Date, CustNum, OrderNum, Items*)>
      Column OrderNum               <!ELEMENT OrderNum (#PCDATA)>
      Column Date                   <!ELEMENT Date (#PCDATA)>
      Column CustNum                <!ELEMENT CustNum (#PCDATA)>

   Table Items             ==>      <!ELEMENT Items()>
      Column OrderNum
      Column ItemNum
      Column Quantity
      Column PartNum

   Table Parts
      Column PartNum
      Column Price
我们以相同的方式处理在远端(Items)表中的数据和主键列:

        表                                            DTD
   ==================               ===================================================

   Table Orders                     <!ELEMENT Orders (Date, CustNum, OrderNum, Items*)>
      Column OrderNum               <!ELEMENT OrderNum (#PCDATA)>
      Column Date                   <!ELEMENT Date (#PCDATA)>
      Column CustNum                <!ELEMENT CustNum (#PCDATA)>

   Table Items             ==>      <!ELEMENT Items(ItemNum, Quantity)>
      Column OrderNum
      Column ItemNum                <!ELEMENT ItemNum (#PCDATA)>
      Column Quantity               <!ELEMENT Quantity (#PCDATA)>
      Column PartNum

   Table Parts
      Column PartNum
      Column Price
接着为外键所对应的表(Parts)增加一个元素:

        表                                            DTD
   ==================               ===================================================

   Table Orders                     <!ELEMENT Orders (Date, CustNum, OrderNum, Items*)>
      Column OrderNum               <!ELEMENT OrderNum (#PCDATA)>
      Column Date                   <!ELEMENT Date (#PCDATA)>
      Column CustNum                <!ELEMENT CustNum (#PCDATA)>

   Table Items                      <!ELEMENT Items(ItemNum, Quantity, Parts)>
      Column OrderNum
      Column ItemNum                <!ELEMENT ItemNum (#PCDATA)>
      Column Quantity               <!ELEMENT Quantity (#PCDATA)>
      Column PartNum

   Table Parts             ==>      <!ELEMENT Parts()>
      Column PartNum
      Column Price
最后,我们处理外键表(Parts):

        表                                            DTD
   ==================               ===================================================

   Table Orders                     <!ELEMENT Orders (Date, CustNum, OrderNum, Items*)>
      Column OrderNum               <!ELEMENT OrderNum (#PCDATA)>
      Column Date                   <!ELEMENT Date (#PCDATA)>
      Column CustNum                <!ELEMENT CustNum (#PCDATA)>

   Table Items                      <!ELEMENT Items (ItemNum, Quantity, Parts)>
      Column OrderNum
      Column ItemNum                <!ELEMENT ItemNum (#PCDATA)>
      Column Quantity               <!ELEMENT Quantity (#PCDATA)>
      Column PartNum

   Table Parts             ==>      <!ELEMENT Parts(PartNum, Price)>
      Column PartNum                <!ELEMENT PartNum (#PCDATA)>
      Column Price                  <!ELEMENT Price (#PCDATA)>
同于前面章节的情况,生成的 DTD 不同于人工建立的。尽管这里唯一的问题是有关名字的,这个算法不能识别次序列或属性表。

5. 映射 XML 模式到数据库
多数 XML 模式语言可以用对象-关系映射来映射到数据库。准确的映射依赖于语言。DDML、DCD 和 XML Data Reduced schemas 可以用几乎与 DTD 一致的方式来映射。对 W3C Schemas、Relax、TREX 和 SOX 的映射表现的更加复杂。我还不清楚 Schematron 能否映射。

在 W3C Schemas 的情况下,能获得到对象模式并接着到数据库模式的一个完整映射。简单的说,这种把复杂类型映射到类(带有复杂类型扩展映射成继承),并把简单类型映射成标量类型(尽管许多细节丢失了)。“all”组都象无序的序列那样来对待,而替换(substitution)组象选择那样对待。最后,多数同一性约束都被映射成键。详情请参见 http://www.rpbourret.com/xml/SchemaMap.htm。

6. 有关的话题
关于 XML 和数据库的更加广泛的讨论请参见:

  XML and Databases (http://www.rpbourret.com/xml/XMLAndDatabases.htm)

关于 XML 数据库产品的一个适当更新的列表请参见:

  XML Database Products (http://www.rpbourret.com/xml/XMLDatabaseProds.htm)


[此贴子已经被admin于2004-2-24 0:38:42编辑过]

--  作者:mhss
--  发布时间:12/26/2003 9:48:00 AM

--  存储 XML 到关系数据库中:
http://mhss.nease.net/xml/dbxml.html
存储 XML 在关系数据库中
by Igor Dayen

June 20, 2001

翻译:寒蝉退士

译者声明:译者对译文不做任何担保,译者对译文不拥有任何权利并且不负担任何责任和义务。

原文:http://www.xml.com/pub/a/2001/06/20/databases.html


--------------------------------------------------------------------------------

介绍
解决把 XML 有效的、自动的转换出入关系数据库的问题有各种方式。数据库厂商比如 IBM、Microsoft、Oracle 和 Sybase 已经开发了转换 XML 到数据库表中的辅助工具。各种解决方案如下。

Oracle XML SQL Utility 把 XML 文档元素建模为一组嵌套的表。通过使用 Oracle 对象数据类型建模套入的元素。"SQL-to-XML"转换使用被对象数据类型引用的表,和嵌套的元素之间的一到一关联来构造 XML 文档。"XML-to-SQL"可能要求数据模型的改进(从关系转换到对象-关系)或重新构造最初的 XML 文档。

IBM DB2 XML Extender 允许存储 XML 文档为 BLOB 式的对象或分解到一组表中。后者得变换叫做 XML 收集,以 XML 1.0 语法定义。

Microsoft 通过扩展 SQL-92 并介入 OPENXML 行集来解决问题。

Sybase Adaptive Server 介入 ResultSetXml Java 类作为在两个方向上处理 XML 文档的基础。

在这篇文章中,我们将详细讨论这些厂商的解决方案。此后,我们将尝试回答下列问题:

我们可以调整并简化问题吗?
在异构数据库环境中正确的途径是什么?
我将使用下列词汇表作为一个例子。

<!-- 简单类型 -->

<!ELEMENT CURRENCY1   (#PCDATA)>
<!ATTLIST CURRENCY1    e-dtype    NMTOKEN   #FIXED  "string"
                        e-dsize    NMTOKEN   #FIXED  "3">

<!ELEMENT CURRENCY2   (#PCDATA)>
<!ATTLIST CURRENCY2    e-dtype    NMTOKEN   #FIXED  "string"
                        e-dsize    NMTOKEN   #FIXED  "3">

<!ELEMENT AMOUNT      (#PCDATA)>
<!ATTLIST AMOUNT       e-dtype    NMTOKEN   #FIXED  "decimal">

<!ELEMENT SETTLEMENT  (#PCDATA)>
<!ATTLIST SETTLEMENT   e-dtype    NMTOKEN   #FIXED  "date">

<!ELEMENT BANKCODE    (#PCDATA)>
<!ATTLIST BANKCODE     e-dtype    NMTOKEN   #FIXED  "string">

<!ELEMENT BANKACCT    (#PCDATA)>
<!ATTLIST BANKACCT     e-dtype    NMTOKEN   #FIXED  "string">

<!-- 派生类型 -->

<!ELEMENT ACCOUNT  (BANKCODE, BANKACCT)>

<!ELEMENT FXTRADE  (CURRENCY1, CURRENCY2, AMOUNT, SETTLEMENT, ACCOUNT)>


Oracle XML-SQL Utility (XSU)
SQL 到 XML 的映射
Oracle 把对象引用链从数据库转换到 XML 文档的层次结构中。在对象-关系数据库,在表 FXTRADE 中的字段 ACCOUNT 被建模为类型 AccountType 的一个对象引用:

CREATE TABLE FXTRADE
{
     CURRENCY1      CHAR (3),
     CURRENCY2      CHAR (3),
     AMOUNT         NUMERIC (18,2),
     SETTLEMENT     DATE,
     ACCOUNT        AccountType // 对象引用
}

CREATE TYPE AccountType as OBJECT
{  
     BANKCODE       VARCHAR (100),
     BANKACCT       VARCHAR (100)
}

从给定的对象-关系模型生成相应的 XML 文档(使用 "SELECT * FROM FXTRADE")如下

<?xml version="1.0"?>
<ROWSET>
     <ROW num="1">
        <CURRENCY1>GBP</CURRENCY1>
        <CURRENCY2>JPY</CURRENCY2>
        <AMOUNT>10000</AMOUNT>
        <SETTLEMENT>20010325</SETTLEMENT>
        <ACCOUNT>
   <BANKCODE>812</BANKCODE>
   <BANKACCT>00365888</BANKACCT>
        </ACCOUNT>
    </ROW>
    <!-- additional rows ... -->
</ROWSET>

从数据库提取 XML
下面的例子取自 Oracle 的 XSU 文档,带有 SQL 语句的适当替换并使用 Oracle 的纯 Java JDBC 薄驱动程序。

首先,建立了 OracleXMLQuery 的一个实例,其后,执行一个查询,并把结果表示为上面的 XML 文档的形式。类似的,可以提取 XML 文档到 DOM 的形式;在这种情况下,可以调用 qry.getXMLDOM() 取代 getXMLString()。

import oracle.jdbc.driver.*;
import oracle.xml.sql.query.OracleXMLQuery;
import java.lang.*;
import java.sql.*;

// 测试 XML 文档生成为 String 的类
class testXMLSQL {

   public static void main(String[] args)
   {
     try {
      // 建立连接
      Connection conn  = getConnection("scott","tiger");

      // 建立查询类
      OracleXMLQuery qry = new OracleXMLQuery(conn,
         "SELECT  * FROM FXTRADE");

      // 得到 XML 字符串
      String str = qry.getXMLString();

      // 打印 XML 输出
      System.out.println("The XML output is:\n"+str);

      // 总是关闭查询来释放所有资源
      qry.close();
     } catch(SQLException e) {
      System.out.println(e.toString());
     }
   }

   // 得到给定的用户名字和口令的连接
   private static Connection getConnection(String username,
        String password)
        throws SQLException
   {
      // 注册 JDBC 驱动程序
       DriverManager.registerDriver(new
          oracle.jdbc.driver.OracleDriver());

      // 建立 OCI8 驱动程序的连接
       Connection conn =
        DriverManager.getConnection(
           "jdbc:oracle:thin:@dlsun489:1521:ORCL",
           username,password);

      return conn;
   }
}

存储 XML 在数据库中
在例子中使用 OracleXMLSave 来存储我们的 XML 文档到对象关系模型中;insertXML 方法进行实际的数据插入。

import java.sql.*;
import oracle.xml.sql.dml.OracleXMLSave;
public class testXMLInsert
{
   public static void main(String args[])
             throws SQLException
  {
    Connection conn = getConnection("scott","tiger");
    OracleXMLSave sav = new OracleXMLSave(conn, "scott. FXTRADE");
  // Assume that the user passes in this document as 0-arg
      sav.insertXML(args[0]);
      sav.close();
   }

  ...
}

如果 XML 和在数据库中的对象-关系模型是同步的则一切正常,但是如果不同呢? 在这种情况下你有两种选择。

调整对象-关系模型 -- 可以构造一个可修改的对象-关系视图来完成多表修改;或者,
作为替代,使用 XSLT,可以把  XML 文档分解成一组"平坦的"子文档。
XSU 不允许属性值的存储;它建议你把属性转换成元素。

Oracle XSU 的总结
通过如下对象-关系模型构造规则建模 XML 到 SQL 映射: 把每个嵌套的 XML 元素映射在适当的类型的一个对象引用上。映射规则被暗含的嵌入到数据库模型中。

Java API 由类 OracleXMLQuery 和 OracleXMLSave 组成。

IBM DB2 XML Extender
SQL 到 XML 的映射
IBM 的 XML Extender 为使用 DB2 作为 XML 仓库提供两种访问和存储方法:

XML 列: 存储和取回整个 XML 文档为 DB2 列数据
XML 收集: 把 XML 文档分解成一组关系表,或从一组关系表合成 XML 文档。
DTD 存储在 DTD 仓库中,叫做 DTD_REF 的一个 DB2 表中;它的模式名字是"db2xml"。在 DTD_REF 表中每个 DTD 都有一个唯一的 ID。在数据库表和 XML 文档的结构之间的映射是通过数据访问定义(DAD)文件的方式来定义的。DAD 引用一个处理过的文档 DTD,从而提供在 XML 文档、它的 DTD 和在数据库表之上的映射规则之间的桥梁。

下面是一个例子 DAD。

<?xml version="1.0"?>
<!DOCTYPE    DAD    SYSTEM    "dad.dtd">    
<DAD>    
    <dtdid>FXTRADE.DTD</dtdid>    
    <validation>YES</validation>    
    <Xcollection>    
        <prolog>?xml version="1.0"?</prolog>    
        <doctype>!DOCTYPE     FXTRADE       FXTRADE.DTD </doctype>    
        <root_node>    
   <element_node     name="FXTRADE">    
       <RDB_node>    
           <table name="FXTRADE"/>    
           <table name="ACCOUNT" key="ID"/>    
           <condition>    
      FXTRADE.ACCOUNT=ACCOUNT.ID    
           </condition>    
       </RDB_node>    
       <element_node name="CURRENCY1">    
               <text_node>    
           <RDB_node>    
                 <table name="FXTRADE"/>    
                 <column name="CURRENCY1" type="CHAR(3)"/>    
           </RDB_node>    
                </text_node>
       </element_node>
       <element_node name="CURRENCY2">
               <text_node>
           <RDB_node>
                 <table name="FXTRADE"/>
                 <column name="CURRENCY2" type="CHAR(3)"/>
           </RDB_node>
              </text_node>
       </element_node>
       <element_node name="AMOUNT">
               <text_node>
           <RDB_node>
                 <table name="FXTRADE"/>
                 <column name="AMOUNT" type="DECIMAL(18,2)"/>
           </RDB_node>
               </text_node>
       </element_node>
       <element_node name="SETTLEMENT">
             <text_node>
           <RDB_node>
                 <table name="FXTRADE"/>
                 <column name="SETTLEMENT" type="DATE"/>
           </RDB_node>
              </text_node>
       </element_node>
       <element_node name="ACCOUNT">
           <element_node name="BANKCODE">
               <text_node>
                   <RDB_node>
                       <table name="ACCOUNT"/>
                       <column name="BANKCODE"
                           type="VARCHAR(100)"/>
                   </RDB_node>
               </text_node>
           </element_node>
           <element_node name="BANKACCT">
               <text_node>
                   <RDB_node>
                        <table name="ACCOUNT"/>
                        <column name="BANKACCT"
                            type="VARCHAR(100)"/>
                    </RDB_node>
               </text_node>
           </element_node>
       </element_node> <!--end of  Account element-->
   </element_node>    <!-- end of  FxTrade element -->
        </root_node>
    </Xcollection>
</DAD>

DAD 通过使用 element_node 到 RDB_node 关联来定义在 XML 文档和关系数据库列之间的映射。顶层的 element_node FXTRADE 被定义为在表 FXTRADE 和 ACCOUNT 之间的连接,带有在 ACCOUNT 表中的字段 ID 作为主键。子元素 CURRENCY1 被映射到在表 FXTRADE 中的字段 CURRENCY1 上,以此类推。注意 ACCOUNT 元素不包含任何 RDB_node -- 这是不需要的,原因是在前面已经定义了 ACCOUNT 和 FXTRADE 之间的连接。ACCOUNT、BANCCODE 和 BANKACCT 的子元素分别的定义在 ACCOUNT 表中的对应的列中。原子 XML 元素在 DAD 中标记为 text_node。在上面的例子中,除了 FXTRADE 和 ACCOUNT 之外的所有元素都是原子的。

从数据库提取 XML
通过存储过程来处理 XML 文档的合成和分解: 存储过程 dxxGenXML() 从数据库提取 XML 文档;存储过程 dxxShredXML() 存储 XML 文档到数据库中。

dxxGenXML() 的主要输入参数是

DAD: 以 DB2 CLOB 数据类型的形式存储;
结果表名字: 构造的 XML 文档被转发到这个表。
其他输入参数指定返回行的最大数目,和 <RDB_node> <condition> 元素摒弃(override)选项。输出参数包括返回行的实际数目,返回代码,和返回消息。

在 "IBM DB2 Universal Database XML Extender Administration and Programming, Version 7" 中的一个 C 程序内、可以找到的存储过程的一个详尽的例子。

存储 XML 在数据库中
把 XML 文档放入数据库中是通过存储过程 dxxShredXML() 来完成的。

dxxShredXML() 的输入参数是

DAD: 以 DB2 CLOB 数据类型的形式存储;
输入 XML 文档: 以 DB2 XMLCLOB 数据类型的形式存储。
它的输出参数是一个返回代码和返回消息。

总结
XML-SQL 映射规则通过数据访问定义(DAD)文件的方式来指定,它是一个 XML 文档。DB2 XML Extender 管理设施包括为每个持久的 DTD 构造 DAD 文件的一种手段。

进一步增强将包含新的 XML-SQL 转换语法,它将使用 XML 转换语言,是 W3C XSLT 的一个子集。

Microsoft SQL Server 2000
SQL 到 XML 的映射
SQL Server 的 SQL-to-XML 和 XML-to-SQL 映射规则使用不同的语法。在下面的提取和存储段落中讨论映射的详情。

从数据库提取 XML
在数据库列和 XML 元素或属性之间的映射通过在 SELECT 中的 AS 别名的方式来定义:

<数据库列> AS [元素名字! 嵌套级别! 属性名字! 指示]
同下面一样,文档顶层被赋予级别 1。缺省的,映射列数据在属性值上。可以使用指示"element"来改变这个缺省设置。

从数据库生成 XML 的过程有两步。

步骤 1。建立到你希望输出 XML 中的原子元素的 As-别名;别名定义了在元素之间的父/子联系。下面的表展示给我们的例子文档的别名。

FXTRADE       /* LEVEL=1 */    
    CURRENCY1   [FXTRADE!1!CURRENCY1]    
    CURRENCY2   [FXTRADE!1!CURRENCY2]    
    AMOUNT      [FXTRADE!1!AMOUNT]    
    SETTLEMENT  [FXTRADE!1!SETTLEMENT]    
    ACCOUNT   /* LEVEL=2  */    
        BANKCODE     [ACCOUNT!2!BANKCODE]    
        BANKACCT     [ACCOUNT!2!BANKACCT]    

步骤 2。在 SQL 中定义输出树结构。通过 SELECT 语句定义树的每个级别,此后通过 UNION ALL 语句的手段把各个级别组合在一起到树中。级别-1 SELECT 语句介入在所有级别上的原子元素名字。每个 SELECT 语句介入一个树级别标记和它的父标记。在结果集中有一个单一记录对应于树根,如同在下面的第一个 SELECT 语句中定义的那样。

SELECT    
    1          AS    Tag,    
    NULL       AS    Parent,    
    NULL       AS    [FXTRADE!1!CURRENCY1],    
    NULL       AS    [FXTRADE!1!CURRENCY2],    
    NULL       AS    [FXTRADE!1!AMOUNT],    
    NULL       AS    [FXTRADE!1!SETTLEMENT],    
    NULL       AS    [ACCOUNT!2!BANKCODE],    
    NULL       AS    [ACCOUNT!2!BANKACCT]    
FROM            
    FXTRADE    
UNION ALL    
SELECT    
    2,    
    1,    
    FXTRADE.CURRENCY1,    
    FXTRADE.CURRENCY2,    
    FXTRADE.AMOUNT,    
    FXTRADE.SETTLEMENT,    
    ACCOUNT.BANKCODE,    
    ACCOUNT.BANKACCT    
FROM    
    FXTRADE,    ACCOUNT    
WHERE    
    FXTRADE.ACCOUNT = ACCOUNT.ID    
ORDER    BY    [ACCOUNT!2!BANKCODE],    
        [ACCOUNT!2!BANKACCT]    
FOR    XML    EXPLICIT, ELEMENTS    

FOR XML 通过分析在组合的行集中的标记和AS-别名构造 XML 文档。关键字 EXPLICIT 选择构造 XML 文档的最灵活的、用户定义的模式。另一个模式 AUTO 通过应用缺省规则构造 XML 文档。关键字 ELEMENTS 在元素级别建模 SQL 列;否则,缺省的是在属性级别建模 SQL 列。

存储 XML 在数据库中
使用 OPENXML 存储 XML 文档,它是一个新的行集函数,类似于表或视图。可以使用 OPENXML 来插入或更新或 SELECT INTO 目标表。OPENXML 简化的语法展示如下:

OPENXML    (<XML 文档句柄>, <路径 pattern>, <标志>)    
WITH     (模式 | 表)
存储 XML 文档的过程分三步。

使用存储过程 sp_xml_preparedocument,通过把 XML  文档编译成内部 DOM 表示来获取一个 XML 文档句柄。

通过对模式字段关联上原子 XML 元素来构造一个模式。

通过路径 pattern(绝对基础路径)加上相对元素路径来定义 XML 元素。通过标志值 2 指示以元素为中心的映射。可以使用现存的表替代一个模式,带有等价于 XML 名字的字段名字。

使用存储过程 sp_xml_removedocument 从内存中删除编译过的 XML 文档。

在下列例子中展示这些步骤。

DECLARE @idoc int    
DECLARE @doc varchar(1000)    
SET @doc ='    
<FXTRADE>    
    <CURRENCY1>GBP</CURRENCY1>    
    <CURRENCY2>JPY</CURRENCY2>    
    <AMOUNT>10000</AMOUNT>    
    <SETTLEMENT>20010325</SETTLEMENT>    
    <ACCOUNT>    
        <BANKCODE>812</BANKCODE>    
        <BANKACCT>00365888</BANKACCT>    
    </ACCOUNT>    
</FXTRADE>'    
-- 建立 XML 文档的内部 DOM 表示。
EXEC sp_xml_preparedocument @idoc OUTPUT, @doc    
-- 执行使用 OPENXML 行集提供者的一个 SELECT 语句。
SELECT *    
FROM OPENXML (@idoc, '/FXTRADE/ACCOUNT', 2)    
WITH (    
  CURRENCY1     CHAR (3),       '../@CURRENCY1',    
  CURRENCY2     CHAR (3),       '../@CURRENCY2',    
  AMOUNT        NUMERIC (18,2), '../@AMOUNT',    
  SETTLEMENT    DATETIME,       '../@SETTLEMENT',    
  BANKCODE      VARCHAR (100),  '@BANKCODE',    
  BANKACCT      VARCHAR (100),  '@BANKACCT' )
EXEC sp_xml_removedocument @idoc    

总结
对于 Microsoft SQL Server 2000,XML 文档的提取和存档不使用对称的语法。提取通过使用 FOR XML 构造扩展出一个 SELECT-子句。存储介入一个行集函数 OPENXML,类比于一个表或视图。提取映射规则是基于 (a)介入指定树级别用的标记和 (b) 对表的字段关联上在 XML 文档元素之间的父/子联系。存储把 XML 文档重构到一个平坦的模式或表中;使用 XPath 表示法定义 "字段-元素"关联。

Sybase Adaptive Server
SQL 到 XML 的映射
Sybase 使用一个 XML 文档类型 ResultSet 来描述 XML 文档元数据(元素名字、类型、大小等)和实际的行数据二者。下面摘录自假定的 FxTradeSet.xml:

<?xml version="1.0"?>    
<!DOCTYPE ResultSet SYSTEM "ResultSet.dtd">    
<ResultSet>    
    <ResultSetMetaData>    
        <ColumnMetaData    
        ...
        getColumnLabel="CURRENCY1"    
        getColumnName="CURRENCY1"    
        getColumnType="12"    
         ... />    
   ...    
   </ResultSetMetaData>    
   <ResultSetData>    
        <Row>    
           <Column name="CURRENCY1">GBP</Column>    
        ...    
        </Row>    
    </ResultSetData>    
</ResultSet>    

ResultSet DTD 好像不允许嵌套元素的定义。

从数据库提取 XML
Java 类 ResultSetXml 有一个构造器,它接受一个 SQL 查询作为参数,此后 getXmlLText 方法从结果集提取个 XML 文档:

jcs.xml.resultset.ResultSetXml     rsx = new jcs.xml.resultset.ResultSetXml    
           ("Select * from FxTrade", <other parameters>);    
FileUtil.string2File ("FxTradeSet.xml", rsx.getXmlText());    

存储 XML 在数据库中
ResultSetXml 类构造器也接受一个 XML 文档作为参数。此后方法 toSqlScript 生成 SQL 语句的序列来从结果集插入/更新到指定的表。

String    xmlString = FileUtil.file2string ("FxTradeSet.xml");    
jcs.xml.resultset.ResultSetXml     rsx = new jcs.xml.resultset.ResultSetXml    
           (xmlString);    
String     sqlString  = rsx.toSqlScript ("FxTrade", <other parameters>)    

总结
XML 文档的提取和存储是本质上对称的。存储好像不允许修改多于一个表。提取把 SQL 查询的结果转换到有平坦结构的文档中。

厂商比较
厂商
映射规则
单一表/多个表
转化的手段
对称的提取/存储

Oracle
隐含的;通过构造对象-关系模型
多个
指定的 Java 类
对称,如果 XML 文档和对象-关系模型匹配

IBM
数据访问定义文件
多个
指定的存储过程
对称

Microsoft
SQL 扩展;行集函数
多个表用于提取;单一表用于存储
通过使用 SQL 构造FOR XML和行集OPENXML
不对称

Sybase
结果集 DTD
单一表;查询可以包含多个表
通过使用 Java 类
对称

厂商间的公共特征是:

XML 持久性建立在特别的基础上,就是说,没有一般性的设施来存储任意 XML 文档);如果 XML 文档使用一个新的文法则需要专门的映射;
存储经常需要数据处理,比如按照使用的地域来重新格式化数值/日期;可以利用 XSLT 来进行 XML 数据整理。
一种可替代的策略
XML 文档存储在数据库中的任务可以划分到阶段? 首先,存储 XML 在普通的数据库结构中而不应用任何映射规则;其次,为后续的处理提供一个框架? 这种方法的好处是对于事实上的任何 XML 文档有一个通用的采集代理(acquisition   agent)。本文的余下部分只提议这种解决方式。

在这种策略下,XML 文档保存为正常的树结构 -- 原始树数据 -- 用于进一步的应用处理。进一步数据处理的框架是一组数据库对象(存储过程),类似于 DOM API,用来通过传统的 SQL 处理来操纵数据。

使用与全自动方式相反的框架方式的好处有: 数据库群体(population)通常由分布在"纯"应用程序代码、事务处理、数据库层(layer)和存储过程之间的应用逻辑来控制的,不必提及一些表可能驻留在远端数据库中。

此外,在异构的数据库环境中,拥有一个统一的工具来对不同的数据库完成相同的任务有着明显的好处。

作者的解决方案,ObjectCentric Solutions x-Persistence Utility,实现了这里讨论的策略。

原始树结构通过如下表来定义。

1) 树节点定义

CREATE TABLE NodeDef  (
   nodeID       udd_id         NOT NULL, // 唯一性节点 id
   dimID        udd_id         NOT NULL, // 树种类: XML
   name         udd_name       NOT NULL, // 节点名字
   value        udd_paramvalue NULL,     // 节点值
   value_type   udd_type       NULL,     // 值类型
   power        udd_power      NULL,     // 节点树级别
   isRoot       udd_logical    NOT NULL, // 顶层节点标志
   descriptor   udd_descriptor NULL,     // DOM 节点类型
   color        udd_color      NULL      // 非正式数据
)    

2) 在节点间的父-子关系

CREATE TABLE NodeLinks    (    
   parentID     udd_id     NOT NULL,   // 父节点
   childID      udd_id     NOT NULL    // 子节点
)    

用例
存储 XML 文档在数据库中需要调用 XML2SQL 程序:

XML2SQL  <XML 文档文件名>
使用下列存储过程实现从数据库提取 XML 文档为一个树结构:

get_tree_roots <树种类> -- 提取一个给定森林的所有文档根 id
get_tree <根 id> -- 提取一个给定根(文档)的所有节点
get_children <节点, 级别> -- 提取一个给定节点的特定级别的所有子节点
实现细节:

当前的平台包括: Sybase,MS SQL Server。
被评估的平台: Oracle,DB2 和 PostgreSQL。
实用工具建立在 Xerces XML 分析器顶上。
数据库安装涉及到的只是增加一些表,和导航数据库表为树结构的存储过程。
x-Persistence Utility 当前在 Open Finance Laboratory 中用作基于 XML 的数据采集代理的核心,它是如证券管理、负债管理、风险管理等范围内的财务应用的一个可定制的集成平台。Open Finance Laboratory 接受 FpML 作为用于利率导出的一个叙述性语言。x-Persistence Utility 目前用作 FpML/FRA 数据采集代理。

引用
Oracle XML-SQL Utility, http://otn.oracle.com/tech/xml/oracle_xsu
IBM DB2 XML Extender, www.ibm.com/software/data/db2/extenders/xmlext
XML Perspective. In control with FOR XML Explicit. SQL Server Magazine, http://msdn.mcrosoft.com/library/periodic/period01/xmlExplicit.htm
Writing XML Using OPENXML
OPENXML
Technology Preview: x-Persistence Utility, ObjectCentric Solutions, Inc., 2001, www.objcentric.com

[此贴子已经被admin于2004-2-24 0:39:50编辑过]

--  作者:szn
--  发布时间:12/27/2003 2:32:00 PM

--  
thank you !
--  作者:intopython
--  发布时间:1/16/2004 10:29:00 AM

--  
xiexie
--  作者:nizijian
--  发布时间:4/12/2004 9:38:00 AM

--  
3x
--  作者:nolf
--  发布时间:5/13/2004 11:34:00 AM

--  
thx very much
--  作者:凤舞九天
--  发布时间:7/8/2004 2:28:00 PM

--  
3Q
--  作者:kittypig
--  发布时间:7/13/2004 3:38:00 PM

--  
好人啊!正需要这方面的资料,哈哈!
W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
550.781ms