为啥在加载到 XMLTABLE 时会跳过 XML 文档的第 40,000 个字符?
Posted
技术标签:
【中文标题】为啥在加载到 XMLTABLE 时会跳过 XML 文档的第 40,000 个字符?【英文标题】:Why is the 40,000th char of an XML document skipped when loading to an XMLTABLE?为什么在加载到 XMLTABLE 时会跳过 XML 文档的第 40,000 个字符? 【发布时间】:2020-02-12 15:51:42 【问题描述】:将 50661 个字符的 XML 文档加载到 Oracle XMLTABLE 中时,它似乎在位置 40000 处去除了一个空格字符,但继续处理 XML 文档的其余部分,为什么?
我已经包含一个简单的测试脚本来演示 Oracle 数据库 11g 企业版版本 11.2.0.4.0 - 64 位生产上的这个问题
create or replace TYPE SPCBH_V_COLUMNS_OBJECT AS OBJECT
(
COLUMN_ID NUMBER(3),
COLUMN_NAME VARCHAR2(15),
DATA_TYPE VARCHAR2(16),
PRECISION NUMBER(3),
MAX_LENGTH NUMBER(6),
SCALE NUMBER(3),
IS_NULLABLE NUMBER(1),
IS_IDENTITY NUMBER(1),
IDENTITY_NAME VARCHAR2(50),
SEED_VALUE NUMBER(3),
INCREMENT_VALUE NUMBER(3),
LAST_VALUE NUMBER(3),
DEFAULT_CONSTRAINT_NAME VARCHAR2(51),
DEFAULT_CONSTRAINT_TYPE VARCHAR2(3),
DEFAULT_CONSTRAINT_TYPE_DESC VARCHAR2(61),
DEFAULT_CONSTRAINT_DEFINITION VARCHAR2(4000),
COLUMN_USER_NAME VARCHAR2(100),
COLUMN_PARENT_NAME VARCHAR2(9),
COLUMN_PARENT_COLUMN_NAME VARCHAR2(16),
COLUMN_PARENT_SUB_TYPE VARCHAR2(9),
COLUMN_COMMENTS VARCHAR2(2001),
COLUMN_UPDATED_FLAG VARCHAR2(2),
COLUMN_AUDITED_FLAG VARCHAR2(2)
);
create or replace TYPE SPCBH_V_COLUMNS_TABLE AS TABLE OF SPCBH_V_COLUMNS_OBJECT;
这里是测试匿名块
declare
V_XML_PATH VARCHAR2(200);
V_PARSED_COLUMNS SPCBH_V_COLUMNS_TABLE;
DDLXMLSTRING XMLTYPE;
V_TEMP_XML_PATH VARCHAR2(200);
v_length int;
BEGIN
V_XML_PATH := '\\ipcbhcs01\systems\231\PW6\Staging\PW-0231-70\AZLASRL.XML';
V_TEMP_XML_PATH := REPLACE(SUBSTR(V_XML_PATH,INSTR(V_XML_PATH,'\',1,4),200),'\','/');
SELECT XMLTYPE(bfilename('PWSTAGING',V_TEMP_XML_PATH),NLS_CHARSET_ID('WE8ISO8859P15'))
INTO DDLXMLSTRING FROM dual ;
WITH
table_data AS (
SELECT td.*
FROM XMLTABLE('/xml/table'
PASSING (ddlxmlstring)
COLUMNS
table_name VARCHAR2(50) PATH '@table_name',
table_user_name VARCHAR2(100) PATH 'table_user_name',
table_comments CLOB PATH 'table_comments',
table_file_sub_type VARCHAR2(10) PATH 'table_file_sub_type',
table_product VARCHAR2(3) PATH 'table_product',
table_directory VARCHAR2(10) PATH 'table_directory',
table_number NUMBER(5) PATH 'table_number',
table_prim_maint_view VARCHAR2(30) PATH 'table_prim_maint_view',
table_stf_flag VARCHAR2(2) PATH 'table_stf_flag',
table_public VARCHAR2(2) PATH 'table_public',
table_updated_flag VARCHAR2(2) PATH 'table_updated_flag',
table_audited_flag VARCHAR2(2) PATH 'table_audited_flag',
columns XMLTYPE PATH 'columns',
indexes XMLTYPE PATH 'indexes'
) td),
column_data AS (
SELECT cd.*
FROM table_data td,
XMLTABLE('/columns/column'
PASSING td.columns
COLUMNS
column_id NUMBER(3) PATH '@column_id',
column_name VARCHAR2(15) PATH 'column_name',
data_type VARCHAR2(16) PATH 'data_type',
precision NUMBER(3) PATH 'precision',
max_length NUMBER(6) PATH 'max_length',
scale NUMBER(3) PATH 'scale',
is_nullable VARCHAR2(1) PATH 'is_nullable',
is_identity VARCHAR2(1) PATH 'is_identity',
identity_name VARCHAR2(50) PATH 'identity_name',
seed_value NUMBER(3) PATH 'seed_value',
increment_value NUMBER(3) PATH 'increment_value',
last_value NUMBER(3) PATH 'last_value',
default_constraint_name VARCHAR2(51) PATH 'default_constraint_name',
default_constraint_type VARCHAR2(3) PATH 'default_constraint_type',
default_constraint_type_desc VARCHAR2(61) PATH 'default_constraint_type_desc',
default_constraint_definition VARCHAR2(4000) PATH 'default_constraint_definition',
column_user_name VARCHAR2(100) PATH 'column_user_name',
column_parent_name VARCHAR2(9) PATH 'column_parent_name',
column_parent_column_name VARCHAR2(16) PATH 'column_parent_column_name',
column_parent_sub_type VARCHAR2(9) PATH 'column_parent_sub_type',
column_comments VARCHAR2(2001) PATH 'column_comments',
column_updated_flag VARCHAR2(2) PATH 'column_updated_flag',
column_audited_flag VARCHAR2(2) PATH 'column_audited_flag'
) cd )
SELECT SPCBH_V_COLUMNS_OBJECT(
COLUMN_ID,
COLUMN_NAME,
DATA_TYPE,
PRECISION,
MAX_LENGTH,
SCALE,
IS_NULLABLE,
IS_IDENTITY,
IDENTITY_NAME,
SEED_VALUE,
INCREMENT_VALUE,
LAST_VALUE,
DEFAULT_CONSTRAINT_NAME,
DEFAULT_CONSTRAINT_TYPE,
DEFAULT_CONSTRAINT_TYPE_DESC,
DEFAULT_CONSTRAINT_DEFINITION,
COLUMN_USER_NAME,
COLUMN_PARENT_NAME,
COLUMN_PARENT_COLUMN_NAME,
COLUMN_PARENT_SUB_TYPE,
COLUMN_COMMENTS,
COLUMN_UPDATED_FLAG,
COLUMN_AUDITED_FLAG)
BULK COLLECT INTO V_PARSED_COLUMNS
FROM column_data;
select length(DDLXMLSTRING.getClobVal()) into v_length from dual;
dbms_output.put_line('length(DDLXMLSTRING):'||v_length);
DBMS_OUTPUT.PUT_LINE(RPAD('COLUMN_ID',10) || RPAD('COLUMN_NAME',18) || RPAD('COLUMN_PARENT_COLUMN_NAME',30) || RPAD('COLUMN_PARENT_SUB_TYPE',30));
FOR MY_CURSOR IN (SELECT
COLUMN_ID,
COLUMN_NAME,
DATA_TYPE,
PRECISION,
MAX_LENGTH,
SCALE,
IS_NULLABLE,
IS_IDENTITY,
IDENTITY_NAME,
SEED_VALUE,
INCREMENT_VALUE,
LAST_VALUE,
DEFAULT_CONSTRAINT_NAME,
DEFAULT_CONSTRAINT_TYPE,
DEFAULT_CONSTRAINT_TYPE_DESC,
DEFAULT_CONSTRAINT_DEFINITION,
COLUMN_USER_NAME,
COLUMN_PARENT_NAME,
COLUMN_PARENT_COLUMN_NAME,
COLUMN_PARENT_SUB_TYPE,
COLUMN_COMMENTS,
COLUMN_UPDATED_FLAG,
COLUMN_AUDITED_FLAG
FROM TABLE(V_PARSED_COLUMNS) ORDER BY COLUMN_ID)
LOOP
DBMS_OUTPUT.PUT_LINE(RPAD(MY_CURSOR.COLUMN_ID ,10) || RPAD(MY_CURSOR.COLUMN_NAME,18) || RPAD(NVL(MY_CURSOR.COLUMN_PARENT_COLUMN_NAME,'NULL'),30) || RPAD(NVL(MY_CURSOR.COLUMN_PARENT_SUB_TYPE,'NULL'),30));
END LOOP;
end;
在位置 40000,XML 阅读器似乎跳过了空格并加载了带有 NULL 的 XMLTYPE,这在其他任何地方都不会发生。如果我将值更改为单个空格以外的值,例如两个空格,或“ABC”,它工作正常。
XML 文档和屏幕截图显示了该文档的第 40,000 个字符,可在https://drive.google.com/open?id=124zDZYiNJnNzenQScgCbT3RKk7SApYPO找到
您需要指向一个可用的数据库目录。
【问题讨论】:
它似乎不是第 40,000 个字符,而是第 39,999 个字符。 minimal reproducible exampledb<>fiddle。 另外,为什么将数据读入一个集合只是把它放回游标中循环打印出来呢?您可以遍历集合并跳过光标;否则跳过集合并将XMLTABLE
查询作为游标打开。
更新了db<>fiddle 对不同字符串的测试。它似乎不会影响 Oracle 18c db<>fiddle
感谢您提供了一个极简可重现示例的优秀示例。这段代码只是一个更大的 SP 的一部分,它不只是用光标在 XMLTABLE 中旋转,它是为测试添加的以显示无效结果。
我目前无权访问 12c 实例,并已联系我们组织中的一些 DBA 让他们在 12c 上也进行测试。你同意这是一个至少 11g 的错误吗?
【参考方案1】:
一个最小的可重现的例子是:
DECLARE
PROCEDURE testClob(
p_position NUMBER,
p_value VARCHAR2
)
IS
p_clob CLOB := '<root><a>';
p_middle VARCHAR2(7) := '</a><b>';
p_close VARCHAR2(11) := '</b></root>';
p_deduction NUMBER := LENGTH( p_clob ) + LENGTH( p_middle );
p_a CLOB;
p_b VARCHAR2(10);
BEGIN
FOR i IN 1 .. FLOOR( ( p_position - p_deduction) / 4000 ) LOOP
p_clob := p_clob || RPAD( '_', 4000, '_' );
END LOOP;
p_clob := p_clob || RPAD( '_', MOD( p_position - p_deduction, 4000 ), '_' );
p_clob := p_clob || p_middle;
DBMS_OUTPUT.PUT( LENGTH( p_clob ) || ' characters then "' || p_value || '" gives ' );
p_clob := p_clob || p_value;
p_clob := p_clob || p_close;
SELECT a, b
INTO p_a, p_b
FROM XMLTABLE(
'/root'
PASSING XMLTYPE( p_clob )
COLUMNS
a CLOB PATH 'a',
b VARCHAR2(10) PATH 'b'
);
DBMS_OUTPUT.PUT_LINE( '"'||p_b||'"' );
END;
BEGIN
testClob( 39998, '' );
testClob( 39998, ' ' );
testClob( 39998, ' ' );
testClob( 39998, 'ABC' );
testClob( 39998, 'A' );
END;
/
哪些输出:
39998 个字符,然后 "" 给出 "" 39998 个字符然后 " " 给出 "" 39998 个字符然后“”给出“” 39998 个字符,然后“ABC”给出“ABC” 39998 个字符,然后“A”给出“A”
db小提琴here
在Oracle 18c 和the OP testing on Oracle 12 上进行的测试未显示此问题。
这似乎是 Oracle 11g 中的一个错误(可能更早,但未经测试);所以要解决它,你可以:
-
将您的数据库升级到不会出现此问题的更高版本;
联系My Oracle Support,看看这是否是一个已知的错误,他们是否有解决问题的办法或者他们可以做些什么来支持你;
以不同的方式解析 XML 数据;然而,在testing
XMLTABLE
、XMLQUERY
和EXTRACTVALUE
上都遇到相同的问题,因此似乎没有可用的纯 SQL 解决方案。您可以在数据库中嵌入一个 Java 函数来处理解析 XML,但这会降低性能并且似乎对这个问题反应过度;或
构建一个自定义解决方案来测试该问题的第 39,999 个字符并解决它(这可能不是一个可行的选项,但为了完整性而包含在内)。
【讨论】:
以上是关于为啥在加载到 XMLTABLE 时会跳过 XML 文档的第 40,000 个字符?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Python for 循环在遍历列表副本并进行删除时会跳过元素? [复制]
为啥我的时间 UILabel 从暂停状态恢复播放时会跳过 2 秒?
为啥SqlClient 在传递SqlXml 时会使用不必要的XML 转换?