Oracle 12g导入Cognos XML数据

时间:2015-11-13 13:33:21

标签: xml oracle

我有简单的任务要做。 1)我用XML生成Cognos报告,看起来像这样:

<?xml version="1.0" encoding="utf-8"?>
<dataset  xmlns="http://developer.cognos.com/schemas/xmldata/1/" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance">
<!--
<dataset xmlns="http://developer.cognos.com/schemas/xmldata/1/" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="http://developer.cognos.com/schemas/xmldata/1/ xmldata.xsd">
-->
<metadata>
      <item name="OID" type="xs:string" length="32"/>
      <item name="NAME" type="xs:string" length="522"/>
      <item name="TYPE" type="xs:string" length="32"/>
      <item name="O_STATUS" type="xs:string" length="52"/>
      <item name="O_REGION" type="xs:string" length="52"/>
      <item name="O_DEV_IN_COUNTRY" type="xs:string" length="12"/>
      <item name="O_START" type="xs:date"/>
      <item name="O_REQ_IMPL" type="xs:date"/>
      <item name="O_B_INIT_APPR" type="xs:string" length="50"/>
      <item name="O_B_FIN_APPR" type="xs:date"/>
      <item name="O_REL_Y" type="xs:string" length="12"/>
      <item name="O_REL_M" type="xs:string" length="12"/>
      <item name="O_REL_T" type="xs:string" length="4"/>
      <item name="O_SIZING" type="xs:date"/>
      <item name="O_BPM" type="xs:string" length="592"/>
      <item name="O_BS" type="xs:string" length="22"/>
      <item name="O_MAINT_ASSESSOR" type="xs:string" length="22"/>
      <item name="O_P_ASSIGN_MGR" type="xs:string" length="32"/>
      <item name="O_FUNC_AREA" type="xs:string" length="102"/>
      <item name="O_APS" type="xs:string" length="22"/>
      <item name="O_INV_TYPE" type="xs:string" length="32"/>
      <item name="O_INV_CAT" type="xs:string" length="92"/>
      <item name="O_INV_SUB_CAT" type="xs:string" length="82"/>
      <item name="O_PRIM_AREA_OF_IMPACT" type="xs:string" length="92"/>
      <item name="O_MAND_REQ_TYP" type="xs:string" length="72"/>
      <item name="O_M_COUNTRY" type="xs:string" length="42"/>
      <item name="O_IMP_COUN_1" type="xs:string" length="42"/>
      <item name="O_IMP_COUN_2" type="xs:string" length="42"/>
      <item name="O_IMP_COUN_3" type="xs:string" length="42"/>
      <item name="O_IMP_COUN_4" type="xs:string" length="42"/>
      <item name="O_IMP_COUN_5" type="xs:string" length="42"/>
      <item name="O_IMP_COUN_6" type="xs:string" length="42"/>
      <item name="O_IMP_COUN_7" type="xs:string" length="42"/>
      <item name="O_IMP_COUN_8" type="xs:string" length="32"/>
      <item name="O_IMP_COUN_9" type="xs:string" length="42"/>
      <item name="O_IMP_COUN_10" type="xs:string" length="32"/>
</metadata>
<data>
    <row>
        <value>X-1234567</value>
        <value>SOME TEXT</value>
        <value>Project</value>
        <value>New</value>
        <value>AAAA</value>
        <value>Yes</value>
        <value>2015-01-01</value>
        <value>2015-02-01</value>
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value>AA12345</value>
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value>SOME TEXT</value>
        <value>SOME TEXT</value>
        <value>SOME TEXT</value>
        <value>SOME TEXT</value>
        <value>SOME TEXT</value>
        <value>SOME TEXT</value>
        <value xs:nil="true" />
        <value>SOME TEXT</value>
        <value>SOME TEXT</value>
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value xs:nil="true" />
        <value xs:nil="true" />
    </row>
    <row>
    ...
    </row>
</data>
</dataset>

2)此类文件将发送到映射为名为IMPDIR

的Oracle目录的文件共享

3)我可以从SQL

访问此文件

我想要做的是将此文件中的数据导入到一个表格中,该表格包含元数据中的字段,而记录将由节点中的数据填充。

我通过Stack和Goolgle进行搜索,我发现的大多数情况都与XML有关,这些XML具有可以通过路径区分的字段的清晰路径。

知道怎么分配这样的文件吗? 它自己的文件大约是10MB。

2 个答案:

答案 0 :(得分:1)

您还没有说过如何访问该文件 - 作为外部表,您可以使用utl_file读取,或作为CLOB或类似内容导入到真实表中。如果您可以将整个文件放入CLOB,那么您可以将其转换为XMLType,或者随时转换它。然后,您需要将通用布局转换为关系布局。

此示例假设您有一个名为t42的表,其中有一个名为xml的列,它是XMLType并包含您的文件。您可以将XMLTable与FLWOR XQuery表达式(read more in the documentation)一起使用,将每个通用“项目”节点转换为使用相应元数据条目命名的节点:

select x.*
from t42
cross join xmltable(
  xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as "xs",
    default 'http://developer.cognos.com/schemas/xmldata/1/'),
  'for $row in /dataset/data/row
    return element row {
      for $col_num in (1 to count(/dataset/metadata/item))
        return element {/dataset/metadata/item[$col_num]/@name} {
          $row/value[$col_num]/text()
        }
      }'
  passing t42.xml
  columns oid varchar2(32) path '/row/OID',
    o_dev_in_country varchar2(12) path '/row/O_DEV_IN_COUNTRY',
    o_start date path '/row/O_START'
) x;

OID                              O_DEV_IN_COU O_START 
-------------------------------- ------------ ---------
X-1234567                        Yes          01-JAN-15
X-1234568                        No           14-NOV-15

我在您的示例中添加了第二个row节点,以使结果稍微有用,我刚刚展示了三个示例列。由于您事先知道元数据,因此您只需要为所有列创建更多columns条目以及相应的类型和路径,或者只为您感兴趣的列创建。{/ p>

XMLTable首先定义您使用的名称空间,其中一个名为"xs",另一个名称为默认名称。这样可以识别所有节点;虽然你真的不需要"xs"来提取数据,但它仍然是一个内置的 - 在这种情况下你只需要默认值。然后,它会迭代数据中的每个row,并构造一个新的row元素。然后,对于每一行,它计算元数据项的数量,并为每个元素项创建一个“列”元素,以col_num元元素项的name属性命名,并使用col_numitem值。

SQL Fiddle demo

您可以使用该结果插入真实表格insert into ... select x.oid, x.o_start from ...

如果您正在使用utl_file读取数据,那么您可以在一个PL / SQL块中执行相同的操作,例如:

declare
  l_clob clob;
begin
  -- populate l_clob with utl_file

  insert into your_real_table (oid, o_dev_in_country, o_start) -- add more columns
  select oid, o_dev_in_country, o_start -- add more columns
  from xmltable(
    xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as "xs",
      default 'http://developer.cognos.com/schemas/xmldata/1/'),
    'for $row in /dataset/data/row
      return element row {
        for $col_num in (1 to count(/dataset/metadata/item))
          return element {/dataset/metadata/item[$col_num]/@name} {
            $row/value[$col_num]/text()
          }
        }'
    passing xmltype(l_clob)
    columns oid varchar2(32) path '/row/OID',
      o_dev_in_country varchar2(12) path '/row/O_DEV_IN_COUNTRY',
      o_start date path '/row/O_START'
      -- add more columns...
  ) x;
end;
/

这会将文件放入CLOB(怎么做是偏离主题的,但听起来你已经覆盖了那部分?),然后将CLOB直接传递给XMLTable表达式,并在{{1 }}

你也可以进行XLST转换,但这不是我已经涉足的内容。

您不需要完全动态的解决方案,但只是为了好玩,您可以通过从元数据中提取XMLType()子句成员并使用上面显示的相同基本转换执行它来动态构建查询。你甚至可以进一步动态创建表格 - 当然不是我在运行时推荐的东西,但正如我所说,只是为了好玩......

columns

这将创建declare l_clob clob; l_sql varchar2(32767); begin -- populate l_clob with utl_file -- dynamic create table statement from metadata l_sql := 'create table t42 ('; for col in ( select * from xmltable( xmlnamespaces(default 'http://developer.cognos.com/schemas/xmldata/1/'), '/dataset/metadata/item' passing xmltype(l_clob) columns position for ordinality, column_name varchar2(30) path '@name', data_type varchar2(30) path '@type', data_length varchar2(30) path '@length' ) x) loop l_sql := l_sql || case when col.position > 1 then ', ' end || col.column_name || ' ' || case col.data_type when 'xs:string' then 'varchar2' when 'xs:date' then 'date' -- add any other data types used end || case when col.data_type = 'xs:string' then '(' || col.data_length || ')' end; end loop; l_sql := l_sql || ')'; dbms_output.put_line(l_sql); execute immediate l_sql; -- dynamic insert/select creating the `columns` clause from metadata l_sql := q'!insert into t42 select * from xmltable( xmlnamespaces(default 'http://developer.cognos.com/schemas/xmldata/1/'), 'for $row in /dataset/data/row return element row { for $col_num in (1 to count(/dataset/metadata/item)) return element column { attribute name {/dataset/metadata/item[$col_num]/@name}, $row/value[$col_num]/text() } }' passing xmltype(:l_clob) columns !'; for col in ( select * from xmltable( xmlnamespaces(default 'http://developer.cognos.com/schemas/xmldata/1/'), '/dataset/metadata/item' passing xmltype(l_clob) columns position for ordinality, column_name varchar2(30) path '@name', data_type varchar2(30) path '@type', data_length varchar2(30) path '@length' ) x) loop l_sql := l_sql || case when col.position > 1 then ', ' end || col.column_name || ' ' || case col.data_type when 'xs:string' then 'varchar2' when 'xs:date' then 'date' -- add any other data types used end || case when col.data_type = 'xs:string' then '(' || col.data_length || ')' end || q'! path '//column[@name="!' || col.column_name || q'!"]'!'; end loop; l_sql := l_sql || ')'; dbms_output.put_line(l_sql); execute immediate l_sql using l_clob; end; / 表,然后从数据行填充它。它是一个单独的插入,因此提取每个数据行并逐个插入它们的效率更高。

SQL Fiddle demo

您还可以将列名和数据类型读入本地集合中,这样您就不必将元数据部分循环两次,这样可以使其更具可读性和可维护性,但我认为它不会对绩效有很大影响。

答案 1 :(得分:0)

通用方法是使用动态SQL,因为列名是XML的一部分。下面的查询选择每行的列名和值,并返回存储数据的insert语句。

我认为XML位于TST

中的表xml
WITH colnames AS
  (SELECT  pos,
    column_name
  FROM tst t,
    XMLTable( XMLNAMESPACES (DEFAULT 'http://developer.cognos.com/schemas/xmldata/1/' ), 'for $i in /dataset/metadata/item               
return $i' passing (t.xml) columns 
   pos FOR ORDINALITY, 
   column_name VARCHAR2(30) path '@name' ) x
  ),
  vals AS
  (SELECT vals ,
          rn
  FROM tst t,
    XMLTable( XMLNAMESPACES (DEFAULT 'http://developer.cognos.com/schemas/xmldata/1/' ), 'for $i in /dataset/data/row            
return $i' passing (t.xml) columns 
    rn FOR ORDINALITY,
    vals XMLType path 'value' ) x
  ) ,
  vals2 AS
  (SELECT rn,
    pos,
    val
  FROM vals,
    XMLTable( XMLNAMESPACES (DEFAULT 'http://developer.cognos.com/schemas/xmldata/1/' ), 'for $i in /value             
return $i' passing (vals.vals) columns 
   pos FOR ORDINALITY,
   val VARCHAR2(4000) path 'text()' ) x
  )
SELECT rn,
  'insert into TAB ('
  ||
  (SELECT listagg(column_name,', ') within GROUP (ORDER BY pos) FROM colnames
  )
  ||') values ('
  /* quote and escape the string */
  || listagg(case when val is NULL then 'null' else ''''||replace(val,'''','''''')||'''' end,', ') within GROUP (ORDER BY pos)
  ||');' AS insert_txt
FROM vals2
GROUP BY rn
;

一个示例输出 - 使用execute immediate

执行所有插入
INSERT INTO TAB
  (
    OID,
    NAME,
    TYPE,
    O_STATUS,
    O_REGION,
    O_DEV_IN_COUNTRY,
    O_START,
    O_REQ_IMPL,
    O_B_INIT_APPR,
    O_B_FIN_APPR,
    O_REL_Y,
    O_REL_M,
    O_REL_T,
    O_SIZING,
    O_BPM,
    O_BS,
    O_MAINT_ASSESSOR,
    O_P_ASSIGN_MGR,
    O_FUNC_AREA,
    O_APS,
    O_INV_TYPE,
    O_INV_CAT,
    O_INV_SUB_CAT,
    O_PRIM_AREA_OF_IMPACT,
    O_MAND_REQ_TYP,
    O_M_COUNTRY,
    O_IMP_COUN_1,
    O_IMP_COUN_2,
    O_IMP_COUN_3,
    O_IMP_COUN_4,
    O_IMP_COUN_5,
    O_IMP_COUN_6,
    O_IMP_COUN_7,
    O_IMP_COUN_8,
    O_IMP_COUN_9,
    O_IMP_COUN_10
  )
  VALUES
  (
    'X-1234567',
    'SOME TEXT',
    'Project',
    'New',
    'AAAA',
    'Yes',
    '2015-01-01',
    '2015-02-01',
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    'AA12345',
    NULL,
    NULL,
    'SOME TEXT',
    'SOME TEXT',
    'SOME TEXT',
    'SOME TEXT',
    'SOME TEXT',
    'SOME TEXT',
    NULL,
    'SOME TEXT',
    'SOME TEXT',
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
  );

请注意,对于大量列,动态选择会更好地扩展,但不会处理可能需要的类型转换,这可以在静态中更好地完成选择。