如何将分隔文件(多个分隔符)加载到Oracle中?

时间:2014-08-15 07:48:02

标签: sql unix oracle11g sql-loader

我有一个文件,其中包含主要以管道分隔的信息。但是一些领域在内部进一步划分。我想你可以称它们嵌套分隔?这就是我文件中的行的外观。

FieldA|FieldB|[FieldC111~FieldC112~FieldC113][FieldC121~FieldC122~FieldC123]|[FieldD111~FieldD112~FieldD113][FieldD121~FieldD122~FieldD123]|FieldE|FieldF
FieldA|FieldB|[FieldC111~FieldC112~FieldC113][FieldC121~FieldC122~FieldC123][FieldC131~FieldC132~FieldC133]|[FieldD111~FieldD112~FieldD113][FieldD121~FieldD122~FieldD123]|FieldE|FieldF
FieldA|FieldB|[FieldC111~FieldC112~FieldC113][FieldC121~FieldC122~FieldC123][FieldC131~FieldC132~FieldC133]|[FieldD111~FieldD112~FieldD113][FieldD121~FieldD122~FieldD123][FieldD131~FieldD132~FieldD133]|FieldE|FieldF
FieldA|FieldB|[FieldC111~FieldC112~FieldC113][FieldC121~FieldC122~FieldC123]|[FieldD111~FieldD112~FieldD113][FieldD121~FieldD122~FieldD123][FieldD131~FieldD132~FieldD133]|FieldE|FieldF

基本上字段C和字段D,封装X组字段,其中X的范围为1-10。每个集合由square []括号封装。没有。方括号内的字段保持不变(由波浪号〜分隔)。

我在Oracle中的表应该如下所示

FieldA|FieldB|FieldC111|FieldC112|FieldC113|FieldC121|FieldC122|FieldC123|FieldC131|FieldC132|FieldC133|FieldD111|FieldD112|FieldD113|FieldD121|FieldD122|FieldD123|FieldD131|FieldD132|FieldD133|

我在UNIX上不是很强大(基本上是谷歌每一个命令)但是我已经提出了以下命令来帮助我划分文件(忽略波形符问题,直到我整理出封装的字段问题,因为我可以使用sql函数在波形符号之间读取)

sed 's/\]\[/|/g' final.txt > final1.txt -- replaces all ][ with |
sed 's/\]|\[/|/g' final1.txt > final2.txt -- replaces all ]|[ with |
sed 's/|\[/|/g' final2.txt > final3.txt -- replaces all |[ with |
sed 's/\]|/|/g' final3.txt > final4.txt -- replaces all ]| with |

这为我提供了完全制表符分隔格式的文件。但是,当我加载文件时,问题出现了,分隔符的数量变化(因为字段C和D中的封装字段的数量变化),因此数据将以先来先服务的方式加载。

例如,如果我在字段C中封装了1个字段,并且在字段D中封装了3个字段,那么我的表将导致字段C中的所有3列和字段D中包含数据的一列。我想要发生的是,如果在字段C中只有1个字段被封装(但是为字段C提供了3个字段),则字段C的其他2个字段应该是空白的,并且字段D中封装的字段应该到它们各自的D领域。

我还有其他一些解决方案,比如拆分文件,将其加载到3-4个不同的表中,然后组合所有3个表。但这将是一项额外的工作。想知道是否有更简单的方法来做到这一点

我希望我已经解释清楚了。对我来说,把它写成文字非常复杂:(

有关我正在处理的数据文件的一些其他信息。

  • 任何字段都没有固定的长度。
  • 封装字段中的字段数(由代字号分隔的字段)是固定的
  • 文件大小约为15 GB。
  • 共有120多个字段。

1 个答案:

答案 0 :(得分:2)

如果文件位于数据库服务器上,并且您可以将其放入与Oracle directory对象对应的位置,则可以将初始管道分隔加载为外部表,如:

create table my_external_table (
    A varchar2(10),
    B varchar2(10),
    C varchar2(4000),
    D varchar2(4000),
    E varchar2(10),
    F varchar2(10)
)
organization external (
  type oracle_loader
  default directory my_directory
  access parameters (
    records delimited by newline
    fields terminated by '|'
  )
  location ('my_file.dat')
);

蛮力地分两步提取嵌套分隔的数据,并将其插入真实表中:

insert into my_table (a, b,
  c0101, c0102, c0103, c0201, c0202, c0203, c0301, c0302, c0303,
  d0101, d0102, d0103, d0201, d0202, d0203, d0301, d0302, d0303,
  e, f)
with t as (
  select a, b,
    regexp_replace(regexp_substr(c, '\[.*?\]', 1, 1), '[][]') as c01,
    regexp_replace(regexp_substr(c, '\[.*?\]', 1, 2), '[][]') as c02,
    regexp_replace(regexp_substr(c, '\[.*?\]', 1, 3), '[][]') as c03,
    regexp_replace(regexp_substr(d, '\[.*?\]', 1, 1), '[][]') as d01,
    regexp_replace(regexp_substr(d, '\[.*?\]', 1, 2), '[][]') as d02,
    regexp_replace(regexp_substr(d, '\[.*?\]', 1, 3), '[][]') as d03,
    e, f
  from my_external_table
)
select a, b,
  regexp_substr(c01, '[^~]+', 1, 1) as c0101,
  regexp_substr(c01, '[^~]+', 1, 2) as c0102,
  regexp_substr(c01, '[^~]+', 1, 3) as c0103,
  regexp_substr(c02, '[^~]+', 1, 1) as c0201,
  regexp_substr(c02, '[^~]+', 1, 2) as c0202,
  regexp_substr(c02, '[^~]+', 1, 3) as c0203,
  regexp_substr(c03, '[^~]+', 1, 1) as c0301,
  regexp_substr(c03, '[^~]+', 1, 2) as c0302,
  regexp_substr(c03, '[^~]+', 1, 3) as c0303,
  regexp_substr(d01, '[^~]+', 1, 1) as d0101,
  regexp_substr(d01, '[^~]+', 1, 2) as d0102,
  regexp_substr(d01, '[^~]+', 1, 3) as d0103,
  regexp_substr(d02, '[^~]+', 1, 1) as d0201,
  regexp_substr(d02, '[^~]+', 1, 2) as d0202,
  regexp_substr(d02, '[^~]+', 1, 3) as d0203,
  regexp_substr(d03, '[^~]+', 1, 1) as d0301,
  regexp_substr(d03, '[^~]+', 1, 2) as d0302,
  regexp_substr(d03, '[^~]+', 1, 3) as d0303,
  e, f
from t;

CTE拆分单独的C字段,然后主选择根据波浪号将每个字段拆分出来。我只向相同的空间显示了三个C / D级别,但您必须在CTE和主查询中重复这些行。这将使它成为一个非常长的声明。它涉及大量的剪切和粘贴,并且如果数字不完全正确则会引入错误;但是你可以从shell脚本(或动态SQL)生成语句,以避免这两个问题。

这也假设特定线路上的C / D字段的总长度不超过4k;如果它可能会使事情变得更复杂。

无论如何,对于你展示的数据,决赛桌会有(向那些非常不喜欢滚动的人道歉):

select * from my_table;

A          B          C0101      C0102      C0103      C0201      C0202      C0203      C0301      C0302      C0303      D0101      D0102      D0103      D0201      D0202      D0203      D0301      D0302      D0303      E          F        
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
FieldA     FieldB     FieldC111  FieldC112  FieldC113  FieldC121  FieldC122  FieldC123                                   FieldD111  FieldD112  FieldD113  FieldD121  FieldD122  FieldD123                                   FieldE     FieldF     
FieldA     FieldB     FieldC111  FieldC112  FieldC113  FieldC121  FieldC122  FieldC123  FieldC131  FieldC132  FieldC133  FieldD111  FieldD112  FieldD113  FieldD121  FieldD122  FieldD123                                   FieldE     FieldF     
FieldA     FieldB     FieldC111  FieldC112  FieldC113  FieldC121  FieldC122  FieldC123  FieldC131  FieldC132  FieldC133  FieldD111  FieldD112  FieldD113  FieldD121  FieldD122  FieldD123  FieldD131  FieldD132  FieldD133  FieldE     FieldF     
FieldA     FieldB     FieldC111  FieldC112  FieldC113  FieldC121  FieldC122  FieldC123                                   FieldD111  FieldD112  FieldD113  FieldD121  FieldD122  FieldD123  FieldD131  FieldD132  FieldD133  FieldE     FieldF     

如果你想动态生成插入,你可以这样做,这只是重新创建上面的手册版本 - 但是对于10组C / D字段:

declare
  stmt varchar2(32767);
begin
  stmt := 'insert into my_table (a, b, ';
  for i in 1..10 loop  -- 10 sets of C/D records
    for j in 1..3 loop  -- 3 tilde-delimited values for each
      stmt := stmt || 'c' || lpad(i, 2, '0') || lpad(j, 2, '0') || ', ';
      stmt := stmt || 'd' || lpad(i, 2, '0') || lpad(j, 2, '0') || ', ';
    end loop;
  end loop;
  stmt := stmt || 'e, f) with t as (select a, b, ';
  for i in 1..10 loop  -- 10 sets of C/D records
    stmt := stmt || 'regexp_replace(regexp_substr(c, ''\[.*?\]'', 1, ' || i
      || '), ''[][]'') as c' || lpad(i, 2, '0') || ', ';
    stmt := stmt || 'regexp_replace(regexp_substr(d, ''\[.*?\]'', 1, ' || i
      || '), ''[][]'') as d' || lpad(i, 2, '0') || ', ';
  end loop;
  stmt := stmt || 'e, f from my_external_table) select a, b, ';
  for i in 1..10 loop  -- 10 sets of C/D records
    for j in 1..3 loop  -- 3 tilde-delimited values for each
      stmt := stmt || 'regexp_substr(c' || lpad(i, 2, '0')
        || ', ''[^~]+'', 1, ' || j || '), ';
      stmt := stmt || 'regexp_substr(d' || lpad(i, 2, '0')
        || ', ''[^~]+'', 1, ' || j || '), ';
    end loop;
  end loop;
  stmt := stmt || 'e, f from t';

  -- uncomment to see/debug the actual statement being executed
  -- dbms_output.put_line(stmt);
  execute immediate stmt;
end;
/

运行它会在真实表格中创建相同的记录。