为啥在加载到 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 数据;然而,在testingXMLTABLEXMLQUERYEXTRACTVALUE 上都遇到相同的问题,因此似乎没有可用的纯 SQL 解决方案。您可以在数据库中嵌入一个 Java 函数来处理解析 XML,但这会降低性能并且似乎对这个问题反应过度;或 构建一个自定义解决方案来测试该问题的第 39,999 个字符并解决它(这可能不是一个可行的选项,但为了完整性而包含在内)。

【讨论】:

以上是关于为啥在加载到 XMLTABLE 时会跳过 XML 文档的第 40,000 个字符?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Python for 循环在遍历列表副本并进行删除时会跳过元素? [复制]

为啥我的时间 UILabel 从暂停状态恢复播放时会跳过 2 秒?

为啥SqlClient 在传递SqlXml 时会使用不必要的XML 转换?

为啥有时会跳过 maven 依赖项中的版本号?

如果没有空格分隔符,为啥 XmlReader 会跳过所有其他元素?

Seaborn.relplot() 中的 `hue` 参数在给定数值数据时会跳过一个整数?