技术开发 频道

MySQL中XML数据的XPath支持

  【IT168 技术文档】今天我要为大家介绍的是XPath,XPath是导航和查询XML文档的语言。我们从一个函数开始。

  UpdateXML()函数

  我们已经花了很多时间介绍ExtractValue()函数,但还没有介绍MySQL的其它XML函数,如UpdateXML(),因为我们先前主要将内容放在将XML文档中的数据导入到MySQL数据库中了,UpdateXML()是一个使用不同的XML标记匹配和替换XML块的函数。

  ExtractValue()有两个字符串参数,一个XML标记,一个XPath表达式。

ExtractValue(xml_frag, xpath_expr)

  它返回第一个匹配XPath表达式的文本节点。假设你想将“”变为“”,并将结果保存到一个变量中,下面是使用UpdateXML()函数实现这个目标的做法:

mysql> SELECT @new_xml_node:=UpdateXML('<state><city/></state>',
    
->    '//city',
    
->    '<county><city/></county>')
    
-> AS xml_node;
+-----------------------------------------+
| xml_node                                |
+-----------------------------------------+
| <state><county><city/></county></state> |
+-----------------------------------------+
1 row in set (0.03 sec)

mysql
> SELECT @new_xml_node;
+-----------------------------------------+
| @new_xml_node                           |
+-----------------------------------------+
| <state><county><city/></county></state> |
+-----------------------------------------+
1 row in set (0.00 sec)

  如果没有发现匹配表达式的文本节点,就返回原始XML字符串。

mysql> SELECT @new_xml_node:=UpdateXML('<state><city/></state>',
    
->    '//dummy',
    
->    '<county><city/></county>')
    
-> AS xml_node;
+---------------------------+
| xml_node                  |
+---------------------------+
| <state><city/></state>    |
+---------------------------+
1 row in set (0.03 sec)

  如果发现有多个都匹配,会按顺序返回每个匹配的子文本节点的内容。

mysql> SELECT @new_xml_node:=UpdateXML('<state><city/></state><state><city/></state><state><city/></state>',
    
->    '//city,
    ->    
'<county><city/></county>')
    -> AS xml_node;

+--------------------------------------------------------------------+
| xml_node                                                           |
+--------------------------------------------------------------------+
| <state><city/></state><state><city/></state><state><city/></state> |
+--------------------------------------------------------------------+
1 row in set (0.00 sec)

  因为UpdateXML()函数在匹配到空元素和没有匹配之间没有区别,要区别它们可以使用XPath count()函数测试ExtractValue()的返回。

mysql> SELECT ExtractValue('<state><city/></state><state><city/></state><state><city/></state>',
    
->    'count(//city)') AS xml_node;
+----------+
| xml_node |
+----------+
| 3        |
+----------+
1 row in set (0.00 sec)

mysql
> SELECT ExtractValue('<state><city/></state><state><city/></state><state><city/></state>',
    
->    'count(//county)') AS xml_node;
+----------+
| xml_node |
+----------+
| 0        |
+----------+
1 row in set (0.00 sec)

  错误处理

  对ExtractValue() 和 UpdateXML(),使用的XPath定位器必须是有效的,被搜索的XML必须是结构良好的,如果定位器无效,则会产生一个错误。

mysql> SELECT @new_xml_node:=UpdateXML('<state><city/></state>',
    
->    '//city/"state', '<county><city/></county>') AS xml_node;
ERROR
1105 (HY000): XPATH syntax error: '"state'

  如果被搜索的XML结构不好,则会返回null,并产生一个警告。 

mysql> SELECT @new_xml_node:=UpdateXML('<state><city></state>', '//city',
    
->    '<county><city/></county>') AS xml_node;
+----------+
| xml_node |
+----------+
| NULL     |
+----------+
1 row in set, 1 warning (0.01 sec)

  可以使用show warnings命令显示警告。

mysql> show warnings;
+---------+------+---------------------------------------------------------------+
| Level   | Code | Message                                                                             |
+---------+------+---------------------------------------------------------------+
| Warning | 1525 | Incorrect XML value: 'parse error at line 1 pos 21: '</state>' unexpected ('</city>' wanted)' |
+---------+------+---------------------------------------------------------------+
1 row in set (0.01 sec)

  作为第三个参数传递给UpdateXML()的替换XML则不会检查结构。

mysql> SELECT @new_xml_node:=UpdateXML('<state><city/></state>', '//city',
    
->    '<<county><city/></county>>') AS xml_node;
+-------------------------------------------+
| xml_node                                  |
+-------------------------------------------+
| <state><<county><city/></county>></state> |
+-------------------------------------------+
1 row in set (0.00 sec)

  在XPath表达式中使用变量

  从MySQL 5.1.20开始,可以在XPath定位器参数中使用变量,这样在传递参数时就更加灵活了,根据使用的语法不同,可以对变量实施弱检查或强检查。

  弱检查变量

  使用$@variable_name语法的变量不会检查类型,也不会检查之前是否给它分配过值,因此如果变量类型错误或未定义,MySQL不会报告任何警告或错误。例如,假设你将变量$@county写成$@conty了,由于拼写有误,这个错误的变量不会被赋予任何值,MySQL便会给它赋予一个“none”或“null”值。

  在下面的例子中,我创建了一个变量@xml_cities,在文本节点中包含三个国家的城市,接着我创建了两个变量存储XPath信息,第一个SELECT查询显示@state_index_first变量和ExtractValue()函数的值,由于@state_index_first变量包含一个值“1”,//state[@state_index_first]表达式检索第一个城市的结果是“Chicago”。

  第二个SELECT查询检索@state_index_second变量的值,以及ExtractValue()函数返回的结果,正如预期那样,它返回“Ocean City”。

  最后一个SELECT语句有点问题,因为没有@state_index_third变量,ExtractValue()函数运行后无值返回,这样可能导致我们认为确实没有更多的城市。

mysql> SET @xml_cities = '<state value="Illinois">Chicago</state>
    ->    <state value="New Jersey">Ocean City</state>
    ->    <state value="Maryland">Baltimore</state>
';
Query OK,
0 rows affected (0.21 sec)

mysql
> SET @state_index_first = 1, @state_index_second = 2;
Query OK,
0 rows affected (0.00 sec)

mysql
> SELECT @state_index_first, ExtractValue(@xml_cities,
    
->    '//state[$@state_index_first]') AS 'First City';
+--------------------+------------+
| @state_index_first | First City |
+--------------------+------------+
|                  1 | Chicago    |
+--------------------+------------+
1 row in set (0.00 sec)

mysql
> SELECT @state_index_second, ExtractValue(@xml_cities,
    
->    '//state[$@state_index_second]') AS 'Second City';
+---------------------+-------------+
| @state_index_second | Second City |
+---------------------+-------------+
|                   2 | Ocean City  |
+---------------------+-------------+
1 row in set (0.00 sec)

mysql
> SELECT @state_index_third, ExtractValue(@xml_cities,
    
->    '//state[$@state_index_third]') AS 'Third City';
+--------------------+------------+
| @state_index_third | Third City |
+--------------------+------------+
| NULL               |            |
+--------------------+------------+
1 row in set (0.00 sec)

  强检查变量

  正是由于上面的原因,强烈建议使用强检查变量,这并没什么奇怪的,因为强检查变量作为计算机编程语言的一个标准已经有很长一段时间了,如果要使用$variable_name语法,需要在存储过程中声明变量,这样的变量属于本地变量,是要进行类型和值的检查的。

  下面的存储过程接受一个xml_data字符串,并将ExtractValue()函数返回的结果添加到一个临时表中,这样我们就可以一次查询所有的城市了。 

DELIMITER |
CREATE PROCEDURE get_cities(IN 'xml_data' varchar(255))
BEGIN  
    
DECLARE i INT DEFAULT 1;  
    
DECLARE city_name VARCHAR(100);      
    
CREATE TEMPORARY TABLE cities (    
        match_number
INT UNSIGNED NOT NULL DEFAULT 0,    
        name
VARCHAR(100) NULL  );  
    
SET city_name = ExtractValue(xml_data, '//state[$i]');    
    
WHILE city_name != "" DO    
        
INSERT INTO cities      
            
SELECT i, city_name;    
        
SET i = i+1;    
        
SET city_name = ExtractValue(xml_data, '//state[$i]');  
    
END WHILE;    
    
SELECT * FROM cities;  
    
DROP TABLE cities;
END |
DELIMITER ;

CALL get_cities(
@xml_cities);

+--------------------+------------+
| match_number       | name       |
+--------------------+------------+
| 1                  | Chicago    |
+--------------------+------------+
| 2                  | Ocean City |
+--------------------+------------+
1 row in set (0.01 sec)

  小结

  虽然在MySQL中使用XML还有些限制,但每个新版本都会带来更多的与XML相关的功能,也有第三方厂家提供了工具来弥补这些缺陷。我希望未来看到支持新的,冗余更少的数据格式标记,如JSON。

0
相关文章