使用XML数据

使用XML数据

Greenplum数据库支持存储XML数据的xml数据类型。xml数据类型会检查输入值的结构良好性,提供了在文本域中简单地存储XML数据的便利。此外,支持函数允许用户在这种数据上执行类型安全的操作,请参考下文的XML函数参考

使用这种数据类型要求编译时指定configure --with-libxml

xml类型可以存储结构良好(按照XML标准的定义)的“文档”,以及“内容”片段(按照XML标准中生产XMLDecl?内容定义)。大致上,这意味着内容片段可以有多于一个顶层元素或者字符节点。表达式xmlvalue IS DOCUMENT可以被用来评估一个特定的xml值是一个完整文档或者只是一个内容片段。

创建XML值

要从字符数据产生一个xml类型的值,使用函数xmlparse
xmlparse ( { DOCUMENT | CONTENT } value)
例如:
XMLPARSE (DOCUMENT '<?xml version="1.0"?><book><title>Manual</title><chapter>...</chapter></book>')
XMLPARSE (CONTENT 'abc<foo>bar</foo><bar>foo</bar>')
上面的方法把字符串根据SQL标准转换成XML值,但是也可以使用如下的Greenplum数据库语法:
xml '<foo>bar</foo>'
'<foo>bar</foo>'::xml

即便输入值指定了一个文档类型声明(DTD),xml类型也不会按DTD验证输入值。当前也没有内建的支持来根据其他XML模式语言(例如XML模式)来进行验证。

xml产生字符串值的逆操作,可以使用函数xmlserialize
xmlserialize ( { DOCUMENT | CONTENT } value AS type )

type可以是charactercharacter varying或者text(或者这些类型的一种别名)。同样,根据SQL标准,这是唯一一种在xml类型和字符类型见转换的方法,但是Greenplum数据库也允许用户简单地造型该值。

当一个字符串值与类型xml之间的转换没有使用XMLPARSE或者XMLSERIALIZE时,选择DOCUMENT还是CONTENTXML OPTION会话配置参数决定,该参数可以使用标准命令设置:
SET XML OPTION { DOCUMENT | CONTENT };
或者用Greenplum数据库的风格:
SET XML OPTION TO { DOCUMENT | CONTENT };

默认值是CONTENT,这样所有形式的XML数据都被允许。

注意:

在设置了默认的XML选项后,如果字符串中含有一个文档类型声明,则用户不能直接将字符串转换成类型xml,因为XML内容片段的定义不接受文档类型声明。如果真的需要做这样的转换,可以使用XMLPARSE或者更改XML选项。

编码处理

在处理客户端、服务器以及流经它们的XML数据上的多种字符编码时要多加小心。在使用文本模式向服务器传递查询以及向客户端传递查询结果(这是通常的模式)时,Greenplum数据库会把转换所有在客户端和服务器之间传递的字符数据为相应端点的字符编码,请见字符集支持。这包括XML值的字符串表示,如上面的例子所示。通常,这意味着XML数据中包含的编码声明可能会变得无效,由于字符数据会在客户端和服务器之间进行传输时被转换成其他编码,而内嵌的编码声明却不会改变。为了应对这种行为,提供给xml类型输入的字符串中包含的编码声明会被忽略,而内容会被假定为当前的服务器编码。随后,为了正确的处理,XML数据的字符串必须被从客户端以当前的客户端编码发出。客户端应该负责在把文档发送到服务器之前将它们转换成当前的客户端编码,或者适当地调整客户端编码。在输出上,类型xml的值将不会有编码声明,并且客户端应该假定所有数据都处于当前的客户端编码。

在使用二进制模式向服务器传递查询参数并且向客户端传回查询结果时,不会执行字符集转换,因此情况有所不同。在这种情况下,XML数据中的编码声明会被注意到,并且在缺少编码声明时假定数据是UTF-8(按XML标准的要求,注意Greenplum数据库不支持UTF-16)。在输出上,数据将有编码声明来指定客户端编码,除非客户端编码为UTF-8,那种情况下编码声明将被省略。

注意:

如果XML数据编码、客户端编码和服务器编码都相同,用Greenplum数据库处理XML数据会很少碰到错误并且更加高效。由于XML数据在内部是以UTF-8的形式处理,所以在服务器编码也是UTF-8时计算效率最高。

访问XML值

xml数据类型是与众不同的,在其中没有提供任何比较操作符。这是因为对于XML数据没有良好定义的并且通用的比较算法。其后果之一就是无法通过比较一个xml列和一个搜索值来检索行。因此XML值应该通常伴随着一个独立的键域,例如ID。比较XML值的一种可选方案是将它们先转换成字符串,但是要注意字符串比较并不是一种有用的XML比较方法。

正因为没有用于xml数据类型的比较操作符,所以不能直接在这种类型的列上创建索引。如果想要在XML数据中的快速搜索,可能的解决方案包括将表达式转换成字符串类型并且建立索引,或者索引一个XPath表达式。当然,要用被索引的表达式进行搜索,实际的查询也必须被调整。

处理XML

为了处理数据类型xml的值,Greenplum数据库提供了函数xpathxpath_exists,它们可以计算XPath 1.0表达式。
xpath(xpath, xml [, nsarray])

函数xpath对XML值xml计算XPath表达式xpath(一个文本值)。它返回一个XML值的数组,它对应于XPath表达式产生的节点集合。

第二个参数必须是一个结构良好的XML文档。特别是它必须有一个单一的根节点元素。

该函数可选的第三个参数是一个名字空间映射的数组。这个数组应该是一个二维文本数组,其第二轴线的长度等于2(即,它应该是一个数组的数组,每一个元素数组由刚好两个元素组成)。每个数组项的第一个元素是名字空间名称(别名),第二个元素是名字空间的URI。不要求这个数组中提供的别名与XML文档本身中使用的别名相同(换句话说,在XML文档和xpath函数上下文中,别名都是本地的)。

例子:
SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
             ARRAY[ARRAY['my', 'http://example.com']]);

 xpath  
--------
 {test}
(1 row)
为了处理默认(匿名)名字空间,可以这样做:
SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a>',
             ARRAY[ARRAY['mydefns', 'http://example.com']]);

 xpath
--------
 {test}
(1 row)
xpath_exists(xpath, xml [, nsarray])

函数xpath_existsxpath函数的一种特殊形式。与返回满足该XPath的个体XML值不同,这个函数返回一个布尔值来表示该查询是否被满足。这个函数等效于标准的XMLEXISTS谓词,不过它还提供了对名字空间映射参数的支持。

例子:
SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
                     ARRAY[ARRAY['my', 'http://example.com']]);

 xpath_exists  
--------------
 t
(1 row)

将表映射到XML

下列函数把关系表的内容映射到XML值。它们可以被认为是XML导出功能:
table_to_xml(tbl regclass, nulls boolean, tableforest boolean, targetns text)
query_to_xml(query text, nulls boolean, tableforest boolean, targetns text)
cursor_to_xml(cursor refcursor, count int, nulls boolean,
              tableforest boolean, targetns text)

每个函数的返回类型都是xml

table_to_xml映射由参数tbl指定的表的内容。regclass类型接受使用通常记法标识表的字符串,包括可选的方案限定和双引号。query_to_xml执行文本作为参数query传入的查询并且映射结果集。cursor_to_xml从参数cursor指定的游标中取出指定数目的行。如果不得不映射大型表,推荐使用这种变体,因为每个函数都会在内存中构建结果值。

如果tableforest为假,那么得到的结果XML文档看起来像这样:
<tablename>
  <row>
    <columnname1>data</columnname1>
    <columnname2>data</columnname2>
  </row>

  <row>
    ...
  </row>

  ...
</tablename>
如果tableforest为真,结果会是一个这样的XML内容片段:
<tablename>
  <columnname1>data</columnname1>
  <columnname2>data</columnname2>
</tablename>

<tablename>
  ...
</tablename>

...

如果没有表名可用,也就是说映射一个查询或者游标时,会以第一种格式使用table,而以第二种格式使用row

对这些格式的选择取决于用户。第一种格式是一种正确的XML文档,它在很多应用中都很重要。如果结果值后面会被重新组装到一个文档中,第二种格式在cursor_to_xml函数中更有用。上面讨论的产生XML内容的函数(尤其是xmlelement)可以被用来按照要求修改结果。

数据值会按照函数xmlelement所描述的相同方式来映射。

参数nulls决定空值是否应该被包括在输出中。如果为真,列中的空值会被表示为:
<columnname xsi:nil="true"/>

其中xsi是XML模式实例的XML名字空间前缀。一个合适的名字空间声明将被加入到结果值中。如果为假,包含空值的列会被从输出中省略。

参数targetns指定想要的结果XML名字空间。如果没有特定的名字空间想要,应该传递一个空字符串。

下列函数返回XML模式文档,它描述由上述对应函数执行的映射:
able_to_xmlschema(tbl regclass, nulls boolean, tableforest boolean, targetns text)
query_to_xmlschema(query text, nulls boolean, tableforest boolean, targetns text)
cursor_to_xmlschema(cursor refcursor, nulls boolean, tableforest boolean, targetns text)

关键的一点是,应该传递相同的参数来获得相匹配的XML数据映射以及XML模式文档。

下面的函数在一个文档(或者森林)中产生XML数据映射和相应的XML模式,它们会被链接在一起。如果想要自包含和自描述的结果,它们会很有用:
table_to_xml_and_xmlschema(tbl regclass, nulls boolean, tableforest boolean, targetns text)
query_to_xml_and_xmlschema(query text, nulls boolean, tableforest boolean, targetns text)
此外,下面的函数可以用来产生整个方案或者整个当前数据库的类似映射:
schema_to_xml(schema name, nulls boolean, tableforest boolean, targetns text)
schema_to_xmlschema(schema name, nulls boolean, tableforest boolean, targetns text)
schema_to_xml_and_xmlschema(schema name, nulls boolean, tableforest boolean, targetns text)

database_to_xml(nulls boolean, tableforest boolean, targetns text)
database_to_xmlschema(nulls boolean, tableforest boolean, targetns text)
database_to_xml_and_xmlschema(nulls boolean, tableforest boolean, targetns text)

注意这些函数可能会产生大量需要在内存中构建的数据。在请求大型方案或者数据库的内容映射时,应考虑分别映射表,甚至可能通过游标来映射。

一次方案内容映射的结果可能像这样:
<schemaname>

table1-mapping

table2-mapping

...

</schemaname>

其中表映射的格式取决于tableforest参数,如前所释。

一次数据库内容映射的结果可能像这样:
<dbname>

<schema1name>
  ...
</schema1name>

<schema2name>
  ...
</schema2name>

...

</dbname>

其中方案映射和前面一样。

下面的例子展示了对这些函数产生的输出的使用。该例子展示了一个XLST样式表,它把table_to_xml_and_xmlschema的输出转换成一个含有表数据表格化呈现的HTML文档。以类似的方式,来自这些函数的结果可以被转换成其基于XML的格式。
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns="http://www.w3.org/1999/xhtml"
>

  <xsl:output method="xml"
      doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
      doctype-public="-//W3C/DTD XHTML 1.0 Strict//EN"
      indent="yes"/>

  <xsl:template match="/*">
    <xsl:variable name="schema" select="//xsd:schema"/>
    <xsl:variable name="tabletypename"
                  select="$schema/xsd:element[@name=name(current())]/@type"/>
    <xsl:variable name="rowtypename"
                  select="$schema/xsd:complexType[@name=$tabletypename]/xsd:sequence/xsd:element[@name='row']/@type"/>

    <html>
      <head>
        <title><xsl:value-of select="name(current())"/></title>
      </head>
      <body>
        <table>
          <tr>
            <xsl:for-each select="$schema/xsd:complexType[@name=$rowtypename]/xsd:sequence/xsd:element/@name">
              <th><xsl:value-of select="."/></th>
            </xsl:for-each>
          </tr>

          <xsl:for-each select="row">
            <tr>
              <xsl:for-each select="*">
                <td><xsl:value-of select="."/></td>
              </xsl:for-each>
            </tr>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>

</xsl:stylesheet>

XML函数参考

这一节中描述的函数在类型xml的值上操作。小节XML谓词也包含有关xml函数和类函数表达式的信息。

函数:

xmlcomment

概要:

xmlcomment(text)

函数xmlcomment创建一个XML值,它含有一个以指定文本作为内容的XML注释。文本中不能包含"--"或者以一个"-"结尾,这样得到的结果结构才是一个合法的XML注释。如果该参数是空,结果也是空。

例子:
SELECT xmlcomment('hello');

  xmlcomment
--------------
 <!--hello-->

函数:

xmlconcat

概要:

xmlconcat(xml[, …])

函数xmlconcat把一个列表中的个体XML值串接以创建一个包含XML内容片段的单值。空值会被忽略,只有在没有非空参数的情况下结果才为空。

例子:
SELECT xmlconcat('<abc/>', '<bar>foo</bar>');

      xmlconcat
----------------------
 <abc/><bar>foo</bar>

XML声明(如果存在)会按下列方式组合:

  • 如果所有参数值都有相同的XML版本声明,结果中也会用那个版本,不会使用别的版本。
  • 如果所有参数值都有单独的声明值"yes",那么该值会被用在结果中。
  • 如果所有参数值都有单独的声明值且至少有一个为"no",那么那一个会被用在结果中。否则,结果将没有单独的声明。
  • 如果结果被判定为有一个单独声明但没有版本声明,那么将使用版本1.0的版本声明,因为XML要求XML声明包含版本声明。
在所有情况下编码声明都会被忽略并且移除。
例子:
SELECT xmlconcat('<?xml version="1.1"?><foo/>', '<?xml version="1.1" standalone="no"?><bar/>');

             xmlconcat
-----------------------------------
 <?xml version="1.1"?><foo/><bar/>

函数:

xmlelement

概要:

xmlelement(name name [, xmlattributes(value [AS attname] [, ... ])] [, content, ...])

xmlelement表达式产生一个有给定名称、属性和内容的XML元素。

例子:
SELECT xmlelement(name foo);

 xmlelement
------------
 <foo/>

SELECT xmlelement(name foo, xmlattributes('xyz' as bar));

    xmlelement
------------------
 <foo bar="xyz"/>

SELECT xmlelement(name foo, xmlattributes(current_date as bar), 'cont', 'ent');

             xmlelement
-------------------------------------
 <foo bar="2017-01-26">content</foo>
没有合法XML名称的元素和属性名会被转义,转义的方式是将不合法的字符用序列_xHHHH_替换,其中HHHH是该字符以16进制表达的Unicode代码点。例如:
SELECT xmlelement(name "foo$bar", xmlattributes('xyz' as "a&b"));

            xmlelement
----------------------------------
 <foo_x0024_bar a_x0026_b="xyz"/>
如果属性值是一个列引用,则不需要指定显式的属性名,这种情况下默认将把列名用作该属性的名字。在其他情况下,必须给该属性一个显式的命名。因此这个例子是合法的:
CREATE TABLE test (a xml, b xml);
SELECT xmlelement(name test, xmlattributes(a, b)) FROM test;
但这些就不合法:
SELECT xmlelement(name test, xmlattributes('constant'), a, b) FROM test;
SELECT xmlelement(name test, xmlattributes(func(a, b))) FROM test;
元素内容(如果指定)将被根据其数据类型进行格式化。如果内容本身是类型xml,则会构建复杂的XML文档。例如:
SELECT xmlelement(name foo, xmlattributes('xyz' as bar),
                            xmlelement(name abc),
                            xmlcomment('test'),
                            xmlelement(name xyz));

                  xmlelement
----------------------------------------------
 <foo bar="xyz"><abc/><!--test--><xyz/></foo>

其他类型的内容将被格式化为合法的XML字符数据。这意味着特别的字符<>以及&将会被转换成实体。二进制数据(数据类型bytea)将被表示为base64或者十六进制编码,具体取决于配置参数xmlbinary。为了将SQL和Greenplum数据库数据类型与XML模式说明一致,用于个别数据类型的特殊行为也预计会逐步出现,届时会有更精确的描述。

函数:

xmlforest

概要:

xmlforest(content [AS name] [, ...])

xmlforest表达式产生一个使用给定名称和内容的元素的XML森林(序列)。

例子:

SELECT xmlforest('abc' AS foo, 123 AS bar);

          xmlforest
------------------------------
 <foo>abc</foo><bar>123</bar>


SELECT xmlforest(table_name, column_name)
FROM information_schema.columns
WHERE table_schema = 'pg_catalog';

                                         xmlforest
-------------------------------------------------------------------------------------------
 <table_name>pg_authid</table_name><column_name>rolname</column_name>
 <table_name>pg_authid</table_name><column_name>rolsuper</column_name>

如在第二个例子中所见,如果内容值是一个列引用,则列名可以被省略,那种情况下默认会使用列名。否则,必须指定一个列名。

不是合法XML名称的元素名会按照前述对xmlelement的方式转义。类似地,内容数据也会被转义来得到合法的XML内容,除非它已经是类型xml

注意如果XML森林由多于一个元素组成,它们就不是合法的XML文档,因此将xmlforest表达式包裹在xmlelement中可能会有用。

函数:

xmlpi

概要:

xmlpi(name target [, content])

xmlpi表达式创建一个XML处理指令。内容(如果存在)不能包含字符序列?>

例子:
SELECT xmlpi(name php, 'echo "hello world";');

            xmlpi
-----------------------------
 <?php echo "hello world";?>

函数:

xmlroot

概要:

xmlroot(xml, version text | no value [, standalone yes|no|no value])
xmlroot表达式修改一个XML值的根节点属性。如果指定了一个版本,它会替换掉根节点版本声明中的值;如果指定了一个单独的设置,它会替换掉根节点的单独声明的值。
SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
               version '1.0', standalone yes);

                xmlroot
----------------------------------------
 <?xml version="1.0" standalone="yes"?>
 <content>abc</content>

函数:

xmlagg

xmlagg (xml)

和这里描述的其他函数不同,函数xmlagg是一个聚集函数。它把输入值串接到一个聚集函数调用中(很像xmlconcat的做法),不过串接发生在行之间而不是单个行的表达式之间。有关聚集函数的更多信息请见使用函数和操作符

例子:

CREATE TABLE test (y int, x xml);
INSERT INTO test VALUES (1, '<foo>abc</foo>');
INSERT INTO test VALUES (2, '<bar/>');
SELECT xmlagg(x) FROM test;
        xmlagg
----------------------
 <foo>abc</foo><bar/>

为了决定串接的顺序,ORDER BY子句可能会被增加到聚集调用上。例如:

SELECT xmlagg(x ORDER BY y DESC) FROM test;
        xmlagg
----------------------
 <bar/><foo>abc</foo>
下面的非标准方法在之前的版本中常常会被推荐,它们在特定情况下可能仍然能发挥作用:
SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab;
        xmlagg
----------------------
 <bar/><foo>abc</foo>

XML谓词

这一节中描述的表达式会检查xml值的属性。

表达式:

IS DOCUMENT

概要:

xml IS DOCUMENT

如果参数XML值是一个正确的XML文档,表达式IS DOCUMENT会返回真,如果不是正确的XML文档(即是一个内容片段)就会返回假,如果参数为空则返回空。

表达式:

XMLEXISTS

概要:

XMLEXISTS(text PASSING [BY REF] xml [BY REF])

如果第一个参数中的XPath表达式返回任何节点,则函数xmlexists返回真,否则返回假(只要有一个参数为空,结果也为空)。

例子:
SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Toronto</town><town>Ottawa</town></towns>');

 xmlexists
------------
 t
(1 row)

BY REF子句在Greenplum数据库中没有效果,但是为了与其他实现的SQL符合性和兼容性还是允许使用它。每一种SQL标准都要求第一个BY REF,而第二个是可选的。还要注意SQL标准指定xmlexists结构需要一个XQuery表达式作为第一个参数,但Greenplum数据库当前只支持XPath,它只是XQuery的一个子集。

表达式:

xml_is_well_formed

概要:

xml_is_well_formed(text)
xml_is_well_formed_document(text)
xml_is_well_formed_content(text)

这些函数检查一个文本字符串是否为结构良好的XML,返回一个布尔结果。xml_is_well_formed_document检查一个结构良好的文档,而xml_is_well_formed_content检查结构良好的内容。如果xmloption配置参数被设置为DOCUMENTxml_is_well_formed会做前者;如果设置为CONTENT,则xml_is_well_formed会做后者。这意味着xml_is_well_formed对查看一个到类型xml的简单造型是否会成功很有用,而其他两个函数则可用于查看XMLPARSE的相应变体是否会成功。

例子:

SET xmloption TO DOCUMENT;
SELECT xml_is_well_formed('<>');
 xml_is_well_formed 
--------------------
 f
(1 row)

SELECT xml_is_well_formed('<abc/>');
 xml_is_well_formed 
--------------------
 t
(1 row)

SET xmloption TO CONTENT;
SELECT xml_is_well_formed('abc');
 xml_is_well_formed 
--------------------
 t
(1 row)

SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
 xml_is_well_formed_document 
-----------------------------
 t
(1 row)

SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
 xml_is_well_formed_document 
-----------------------------
 f
(1 row)

最后一个例子展示了包括名字空间是否正确匹配在内的检查。