生成XML PLSQL迭代

时间:2013-10-15 19:04:01

标签: xml plsql oracle11g

我正在尝试通过Oracle 11g中的PL / SQL构建一些非常大型XML文件。我试图迭代地构建文件 - 获取一行,写入文件,获取下一行等。下面是我的代码。我在定义CLOB的位置上遇到了问题。

我得到两个错误,具体取决于我初始化和释放CLOB的位置: 1 - 超出内存 - 在循环之前初始化CLOB和循环之后的freetemporary 2-找不到clob(指定了无效的LOB定位符) - 在循环中初始化并在循环中自由,或初始化并在循环中释放

请告知我的方法中存在的问题,或者迭代构建大型XML文件的最佳方法是什么。

PROCEDURE sql_to_xml(p_sql IN VARCHAR2,
                    p_fileName       IN VARCHAR2,
                    p_dir            IN VARCHAR2,
                    p_xml_created OUT VARCHAR2) IS

xml_result CLOB;
doc        dbms_xmldom.DOMDocument;
ctx DBMS_XMLGEN.ctxHandle;
vv_exit_code varchar2(5);
vv_ctx_open varchar2(1) := 'N';
max_rows NUMBER := 5;

BEGIN

vv_exit_code := 'XML1';
ctx := dbms_xmlgen.newcontext(p_sql);
vv_ctx_open := 'Y';
DBMS_OUTPUT.put_line(vv_exit_code);

vv_exit_code := 'XML2';
DBMS_XMLGEN.SETCONVERTSPECIALCHARS (ctx,TRUE);
DBMS_OUTPUT.put_line(vv_exit_code);

DBMS_LOB.CREATETEMPORARY(xml_result,true); 
while DBMS_XMLGEN.GETNUMROWSPROCESSED(ctx) < max_rows
LOOP
    vv_exit_code := 'XML3';
    xml_result := dbms_xmlgen.getXML(ctx);
    DBMS_OUTPUT.put_line(vv_exit_code);
    DBMS_output.put_line('Xml result is: ' ||dbms_lob.substr( xml_result, 4000, 1 ));

    IF xml_result is not null THEN
        vv_exit_code := 'XML4';    
        doc := dbms_xmldom.newDOMDocument(xml_result);
        DBMS_OUTPUT.put_line(vv_exit_code);

        vv_exit_code := 'XML5';
        dbms_xmldom.writeToFile(doc,p_dir||'/'||p_fileName, 'ISO-8859-1');
        DBMS_OUTPUT.put_line(vv_exit_code);

        vv_exit_code := 'XML6';
        dbms_xmldom.freeDocument(doc);
        p_xml_created := 'TRUE';
        DBMS_OUTPUT.put_line(vv_exit_code);

    ELSE
        p_xml_created := 'FALSE';
    END IF;

    DBMS_OUTPUT.PUT_LINE('XML Result: '||xml_result);
        dbms_lob.FREETEMPORARY(xml_result);

end loop;


DBMS_XMLGEN.CLOSECONTEXT (ctx);
vv_ctx_open := 'N';

EXCEPTION
WHEN out_of_process_memory THEN
    IF vv_ctx_open = 'Y' THEN
        DBMS_XMLGEN.CLOSECONTEXT (ctx);
    END IF;

    gv_err_msg := substr(sqlerrm,1,2000);
    DBMS_OUTPUT.put_line(gv_process_name||' failed '||gv_err_msg);
    RAISE_APPLICATION_ERROR(-20906,gv_process_name||' failed'||gv_err_msg);
    dbms_output.put_line('XML_EXPORT failed (out_of_process_memory exception) executing '||p_sql);
    raise_application_error(-20906,'XML_EXPORT (out_of_process_memory exception) failed executing '||p_sql);


WHEN OTHERS THEN
    IF vv_ctx_open = 'Y' THEN
        DBMS_XMLGEN.CLOSECONTEXT (ctx);
    END IF;
    if xml_result is NULL then
        gv_err_msg := substr(sqlerrm,1,2000);
        DBMS_OUTPUT.put_line(gv_process_name||' failed '||gv_err_msg);
        --   RAISE_APPLICATION_ERROR(-20906,gv_process_name||' failed'||gv_err_msg);
        dbms_output.put_line('XML_EXPORT failed (xml results are NULL) executing '||p_sql);
        raise_application_error(-20906,'XML_EXPORT (xml results are NULL) failed executing '||p_sql);
    else
        gv_err_msg := substr(sqlerrm,1,2000);
        DBMS_OUTPUT.put_line(gv_process_name||' failed '||gv_err_msg);
          dbms_output.put_line('XML_EXPORT failed (others exception) executing '||p_sql);
        DBMS_OUTPUT.put_line('Export Directory is: '||p_dir||'/'||p_fileName);
        raise_application_error(-20906,'XML_EXPORT (others exception) failed executing '||p_sql);
    end if;
END sql_to_xml;

1 个答案:

答案 0 :(得分:1)

尝试在PL / SQL中生成非常大的XML是没有意义的。问题不在于PL / SQL,而是PL / SQL只支持XML DOM,而DOM根本不能处理大型XML。你没有说明你有多大的XML文档,但是我发现PL / SQL用来构建文档的内存大约是生成文档大小的10到30倍,我也不会感到惊讶。

是否有使用PL / SQL之外的其他东西生成XML的选项?如果没有,我真的不得不在Oracle数据库中生成大型XML文件,我会考虑使用Java存储过程。 This question对如何在Java中执行此类操作有一些答案。

编辑以回应您的评论:您的代码绝对不是一次只写一行。它正在编写这个批次,我通过在Oracle 11g XE数据库上使用查询SELECT * FROM all_objects运行它来验证这一事实。循环运行一次并写入7341个对象,创建一个大小超过3MB的XML文件。

然后我尝试修改您的代码以更好地支持您描述的“增量”方法。这包括:

  • 添加一行dbms_xmlgen.setmaxrows(ctx, max_rows);告诉DBMS_XMLGEN一次只生成5行。否则它会一次尝试生成该批次。

  • WHILE循环顶部的代码修改为

    xml_result := dbms_xmlgen.getXML(ctx);
    num_rows_processed := DBMS_XMLGEN.GETNUMROWSPROCESSED(ctx);
    dbms_output.put_line('Got ' || num_rows_processed || ' rows processed');
    
    while num_rows_processed > 0
      -- rest of loop omitted
    
  • WHILE循环底部之前添加这三行中的第一行。

然后我重新运行了你的代码,我可以看到它每次都将每批五行写入文件。但是,这种方法存在一个小问题,因为文件每次都被覆盖。最后,我在输出XML文件中只有一条记录。我无法想象这会是你想要的。

DBMS_XMLDOM中的WRITETOCLOBWRITETOBUFFERWRITETOFILE方法并未提示附加到现有文件的能力,说实话我不是他们没有惊讶。如果可以的话,最终会得到无效的XML,因为文件中会有多个<?xml ... ?>声明。

我支持我之前的建议。无论何时需要处理大型XML,在Oracle数据库或其他地方,都要使用SAX或StAX。 PL / SQL也不支持,所以你需要在Java存储过程中做任何事情,或者从数据库中做。