生成 XML PLSQL 迭代

Posted

技术标签:

【中文标题】生成 XML PLSQL 迭代【英文标题】:Generating XML PLSQL Iterative 【发布时间】:2013-10-15 19:04:01 【问题描述】:

我正在尝试通过 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】:

尝试在 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 中做这种事情的答案。

EDIT 回应您的评论:您的代码绝对不是一次写一行。它一起写了很多,我通过在我的 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,因为文件中会有多个 &lt;?xml ... ?&gt; 声明。

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

【讨论】:

我的进程正在中断具有近 22k 行和大约 52 列的文件。这是在一个月的跨度内,受其创建日期的限制。列数不可协商。我知道对于“较小”并且在同一张表上工作的文件,压缩版本 (gzip) 大约是半 GB。我确实认为内存是问题,这就是我希望像上面那样构建它的原因,如果我把我的逻辑更多地列出来可能会有所帮助 - 构建 CLOB 一次加载 CLOB 一行将 CLOB 写入文件 空闲 CLOB 内存我运行的假设是,这将允许我在我去的时候释放内存 抱歉耽搁了这么久。谢谢你。由于 xml 的大小问题,我们最终改变了方法。

以上是关于生成 XML PLSQL 迭代的主要内容,如果未能解决你的问题,请参考以下文章

将每次迭代生成的数据框保存在列表中

迭代返回以调整 for 循环中的计算值

代码详解生成器迭代器

关于可迭代对象迭代器对象生成器对象

五 迭代器生成器

Python记录12:迭代器+生成器+生成式