Oracle中XMLType.schemaValidate修改时间戳致正则验证失败
在Oracle中使用带正则表达式的XSD验证XML CLOB字段的datetime值时,遇到验证失败问题。数据格式本身符合要求,但验证过程会自动将datetime值转换为带6个尾随零的格式,比如从2024-03-08T11:56:23转为2024-03-08T11:56:23.000000,导致匹配XSD中的正则失败。
无法修改XSD中的正则表达式,尝试过XMLTYPE(l_xmldoc, 'testDateTimeWithZone.xsd')方式,问题依旧。
复现代码
1. 注册XSD的代码
begin DBMS_XMLSCHEMA.registerSchema(schemaURL => 'testDateTimeWithZone.xsd', schemaDoc => '<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xs:element name="EMPLOYEE" type="CHECK"/> <xs:complexType name="CHECK"> <xs:sequence> <xs:element name="NAME" type="xs:string"/> <xs:element name="HIREDATE" type="dtTimeWithZone"/> </xs:sequence> </xs:complexType> <xs:simpleType name="dtTimeWithZone"> <xs:restriction base="xs:dateTime"> <xs:pattern value="\d*-\d{2}-\d{2}T(2[0-3]|[0-1][0-9]):[0-5][0-9]:[0-5][0-9](\.[0-9]{3})?(([\-+]((0[0-9])|(1[0-4])):[0-5][0-9])|Z)"/> </xs:restriction> </xs:simpleType> </xs:schema>'); end; /
2. 验证失败的测试代码
declare l_xmldoc xmltype; begin dbms_output.put_line('Starting:'); -- 构造合法的测试XML数据 select xmlelement("EMPLOYEE", xmlelement("NAME", 'John'), xmlelement("HIREDATE", '2024-03-08T11:56:23') ) into l_xmldoc from dual; l_xmldoc := l_xmldoc.createSchemaBasedXML('testDateTimeWithZone.xsd'); -- 验证失败 xmltype.schemaValidate(l_xmldoc); dbms_output.put_line('Finished!'); end; /
3. 删除XSD的清理代码(可选)
begin DBMS_XMLSCHEMA.deleteSchema('testDateTimeWithZone.xsd' ,DBMS_XMLSCHEMA.DELETE_CASCADE); end; /
解决方案
由于Oracle在处理xs:dateTime类型时会自动补全毫秒部分为6位,而XSD正则只允许0位或3位毫秒,无法修改XSD的情况下,可以通过以下两种方式规避:
方法1:将datetime值格式化为带3位毫秒的形式
构造XML时,把原datetime值补.000,比如将2024-03-08T11:56:23改为2024-03-08T11:56:23.000,这样Oracle验证时不会再补全为6位,而是保留3位,匹配正则中的(\.[0-9]{3})?规则。
修改后的测试代码:
declare l_xmldoc xmltype; begin dbms_output.put_line('Starting:'); -- 构造带3位毫秒的测试XML数据 select xmlelement("EMPLOYEE", xmlelement("NAME", 'John'), xmlelement("HIREDATE", '2024-03-08T11:56:23.000') ) into l_xmldoc from dual; l_xmldoc := l_xmldoc.createSchemaBasedXML('testDateTimeWithZone.xsd'); -- 验证成功 xmltype.schemaValidate(l_xmldoc); dbms_output.put_line('Finished!'); end; /
方法2:用字符串构造XML,避免自动类型转换
如果无法提前格式化数据,可以直接用字符串构造XML,将datetime值作为纯文本节点处理,避免XMLELEMENT函数自动识别并转换datetime类型。
修改后的测试代码:
declare l_xmldoc xmltype; begin dbms_output.put_line('Starting:'); -- 直接用字符串构造XML,避免自动类型转换 l_xmldoc := XMLTYPE('<EMPLOYEE><NAME>John</NAME><HIREDATE>2024-03-08T11:56:23</HIREDATE></EMPLOYEE>'); l_xmldoc := l_xmldoc.createSchemaBasedXML('testDateTimeWithZone.xsd'); -- 验证成功 xmltype.schemaValidate(l_xmldoc); dbms_output.put_line('Finished!'); end; /
原理说明
Oracle的XMLELEMENT函数在处理字符串时,如果内容符合xs:dateTime格式,会自动将其转换为内部的datetime类型,验证时输出带6位毫秒的格式;而直接用字符串构造XML时,内容会被当作纯文本节点,不会触发自动类型转换,验证时会使用原始字符串匹配XSD正则。
内容的提问来源于stack exchange,提问作者FerFig

