2016-02-13 2 views
0

Я не могу прийти к решению этой задачи: моя цель - передать курсор на процедуру PL/SQL и получить результаты в виде XMLType. Функция dbms_xmlgen.getxmltype() делает эту задачу простойДобавление атрибута ко всем узлам, соответствующим выражению XPATH, с использованием Oracle XML DB

<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?) 

Любой другой подход, чтобы получить работу будет хорошо

ответ

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> 

Возможно, вы захотите, чтобы more data types определен в CASE.

Смежные вопросы