【问题标题】:Adding an attribute to all nodes matching an XPATH expression using Oracle XML DB使用 Oracle XML DB 向匹配 XPATH 表达式的所有节点添加属性
【发布时间】:2016-02-16 02:03:51
【问题描述】:

我无法解决此任务:我的目标是将游标传递给 PL/SQL 过程并以 XMLType 的形式获取结果。 dbms_xmlgen.getxmltype() 函数使这项任务变得简单

<ROWSET>
 <ROW>
  <FIRST_NAME>John</FIRST_NAME>
  <LAST_NAME>Goodman</LAST_NAME>
  <HIRE_DATE>22-JUN-2011</HIRE_DATE>
 </ROW>
</ROWSET>

现在我想将光标列数据类型作为属性添加到每个对应的 XML 元素。

<ROWSET>
 <ROW>
  <FIRST_NAME type="VARCHAR2">John</FIRST_NAME>
  <LAST_NAME type="VARCHAR2">Goodman</LAST_NAME>
  <HIRE_DATE type="DATE">22-JUN-2011</HIRE_DATE>
 </ROW>
</ROWSET>

这可以使用动态 SQL 来完成,因此我可以编写一个 PL/SQL 函数来获得一个关联数组,将每一列映射到相应的数据类型。

假设我同时拥有上述 associativa 数组和 XMLType,我如何使用 XPATH 表达式应用一组转换,例如

-- pseudocode ;)
func(myXMLType, '//FIRST_NAME', ?add attribute to the matching node?)

任何其他完成工作的方法都可以

【问题讨论】:

    标签: oracle xpath oracle-xml-db


    【解决方案1】:

    您可以将元数据信息转换为它自己的 XML 表示,然后使用 XPath 找到匹配的条目:

    select *
    from xmltable('for $i in $x/ROWSET return (element {"ROWSET"} {
      for $j in $i/ROW
        return (element {"ROW"} {
          for $k in $j/*
            return (element {$k/name()} {
              attribute type { $m/metadata/column[@name=$k/name()]/@type },
              $k/text()
          } )
        } )
      } )'
      passing generated_xml as "x", metadata_xml as "m"
      columns result xmltype path '.');
    

    每个 ROWSET(当然只有一个)生成一个新的 ROWSET 元素;然后其下的每个 ROW 生成一个新的 ROW 元素;然后在该模式下的每个模式都会生成一个具有相同名称和值的新节点,但该名称还用于在元数据中查找匹配条目并提取其类型属性并将其用作该节点的属性。

    一个成功的例子:

    create or replace function cursor_to_xml(p_cursor sys_refcursor) return xmltype is
      l_cursor sys_refcursor;
      l_ctx dbms_xmlgen.ctxhandle;
      l_xmltype xmltype;
      l_cursor_num pls_integer;
      l_col_cnt pls_integer;
      l_desc_tab dbms_sql.desc_tab2;
      l_metadata varchar2(32767);
      l_result xmltype;
    begin
      -- get generated XMl as shown in the question
      l_cursor := p_cursor;
      l_ctx := dbms_xmlgen.newcontext(querystring => l_cursor);
      l_xmltype := dbms_xmlgen.getxmltype(ctx => l_ctx);
      dbms_xmlgen.closecontext(ctx => l_ctx);
    
      -- use DBMS_SQL to get the data types
      l_cursor_num := dbms_sql.to_cursor_number(rc => l_cursor);
      dbms_sql.describe_columns2(c => l_cursor_num, col_cnt => l_col_cnt,
        desc_t => l_desc_tab);
      dbms_sql.close_cursor(l_cursor_num);
    
      -- manually create an XML version of the column name/data type mappings
      -- which could be extended easily to include length/scale/precision/etc.
      l_metadata := '<metadata>';
      for i in 1..l_desc_tab.count loop
          l_metadata := l_metadata || '<column name="' || l_desc_tab(i).col_name ||
            '" type="' || case l_desc_tab(i).col_type
              when 1 then 'VARCHAR2'
              when 2 then 'NUMBER'
              when 12 then 'DATE'
              -- ...
              end
            || '"/>';
      end loop;
      l_metadata := l_metadata || '</metadata>';
    
      -- use XMLTable with an XPath that deconstructs and reconstructs the
      -- generated XML to add an attribute with the type; this is passed two
      -- XML objects, referred to internally as $x and $m
      -- xmlserialize() formats the result with indentation; xmltype then just
      -- gets it back to that type - you may not need either really
      select xmltype(xmlserialize(document result as varchar2(4000) indent))
      into l_result
      from xmltable('for $i in $x/ROWSET return (element {"ROWSET"} {
        for $j in $i/ROW
          return (element {"ROW"} {
            for $k in $j/*
              return (element {$k/name()} {
                attribute type { $m/metadata/column[@name=$k/name()]/@type },
                $k/text()
            } )
          } )
        } )'
        passing l_xmltype as "x", xmltype(l_metadata) as "m"
        columns result xmltype path '.');
    
      return l_result;
    end cursor_to_xml;
    /
    

    然后是一个生成游标的块 - 类似于您的示例,但有两行只是为了检查是否有效 - 然后调用该函数以获取修改后的 XML:

    set serveroutput on;
    declare
      l_cursor sys_refcursor;
    begin
      open l_cursor for
        select cast('John' as varchar2(10)) as first_name,
          cast('Goodman' as varchar2(10)) as last_name,
          date '2011-06-22' as hire_date
        from dual
        union all
        select cast('Rhea' as varchar2(10)) as first_name,
          cast('Perlman' as varchar2(10)) as last_name,
          date '2012-07-23' as hire_date
        from dual;
    
      dbms_output.put_line(cursor_to_xml(l_cursor).getstringval);
    end;
    /
    
    PL/SQL procedure successfully completed.
    
    <ROWSET>
      <ROW>
        <FIRST_NAME type="VARCHAR2">John</FIRST_NAME>
        <LAST_NAME type="VARCHAR2">Goodman</LAST_NAME>
        <HIRE_DATE type="DATE">22-JUN-11</HIRE_DATE>
      </ROW>
      <ROW>
        <FIRST_NAME type="VARCHAR2">Rhea</FIRST_NAME>
        <LAST_NAME type="VARCHAR2">Perlman</LAST_NAME>
        <HIRE_DATE type="DATE">23-JUL-12</HIRE_DATE>
      </ROW>
    </ROWSET>
    

    当然,您可能希望在 CASE 中定义 more data types

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多