Oracle-.CSV文件一次到多个表

时间:2018-08-27 07:00:14

标签: oracle csv plsql bulk-collect

我必须将.CSV文件加载到我的Oracle表中。但事实是,单个CSV文件将包含多个表的数据。诀窍是我们需要确定要插入到特定表中的第一列数据。即,如果第一列的值为'16',则应将整行插入到具有16列的TABLE_16中;如果值为21,则TABLE_21将具有21列,依此类推。还有一点需要注意的是,我的CSV文件将具有数百万条记录,因此我也需要考虑流程的性能,因此我认为BULK COLLECTFORALL将是插入数据的最佳方法迅速。

当我尝试运行以下代码块时,出现此错误:

01403. 00000 - "no data found" *Cause: No data was found from the objects. *Action: There was no data from the objects which may be due to end of fetch.


.CSV数据示例

16,"Laura","Bissot","LBISSOT","650.124.5234",20-08-05,"ST_CLERK",3300,,121,50,"aaa",234,"asdf","ssedf","wsdrftd"
21,"Mozhe","Atkinson","MATKINSO","650.124.6234",30-10-05,"ST_CLERK",2800,,121,50,"aaa",234,"asdf","ssedf","wsdrftd","aaa",234,"asdf","ssedf","wsdrftd"
11,"James","Marlow","JAMRLOW","650.124.7234",16-02-05,"ST_CLERK",2500,,121,50
16,"TJ","Olson","TJOLSON","650.124.8234",10-04-07,"ST_CLERK",2100,,121,50,"aaa",234,"asdf","ssedf","wsdrftd"
19,"Jason","Mallin","JMALLIN","650.127.1934",14-06-04,"ST_CLERK",3300,,122,50,"aaa",234,"asdf","ssedf","wsdrftd","aaa",234,"asdf",
12,"Michael","Rogers","MROGERS","650.127.1834",26-08-06,"ST_CLERK",2900,,122,50,"aaa"
14,"Ki","Gee","KGEE","650.127.1734",12-12-07,"ST_CLERK",2400,,122,50,"aaa",234,"asdf"
30,"Ki","Gee","KGEE","650.127.1734",12-12-07,"ST_CLERK",2400,,122,50,"aaa",234,"asdf",11,"dd",23,43,789,9086,"1DRFtf","PST","RTF%$",123,"dsda",5656,"dsed",123,4333,112

create or replace type 
T_CSV_DATA as object 
(c001 varchar2(50),c002 varchar2(150),c003 varchar2(150),c004 varchar2(150),c005 varchar2(150),c006 varchar2(150),c007 varchar2(150),c008 varchar2(150),c009 varchar2(150), c010 varchar2(150), 
c011 varchar2(150),c012 varchar2(150),c013 varchar2(150),c014 varchar2(150),c015 varchar2(150),c016 varchar2(150),c017 varchar2(150),c018 varchar2(150),c019 varchar2(150), c020 varchar2(150), 
c021 varchar2(150),c022 varchar2(150),c023 varchar2(150),c024 varchar2(150),c025 varchar2(150),c026 varchar2(150),c027 varchar2(150),c028 varchar2(150),c029 varchar2(150), c030 varchar2(150));

create or replace type T_CSV_VAL as table of T_CSV_DATA;

DECLARE
 --variables to do with the copying the blob into a clob
    v_blob              BLOB;
    v_clob              CLOB;
    v_dest_offset       INTEGER := 1;
    v_src_offset        INTEGER := 1;
    v_lang_context      INTEGER := dbms_lob.default_lang_ctx;
    v_warning           INTEGER;
 --variables to do with iterating over each row of the clob
    v_new_line_pos      NUMBER;
    v_start_pos         NUMBER := 1;
    v_current_line      VARCHAR2(4000);
    v_total_len         NUMBER;
    v_curr_row          apex_application_global.vc_arr2;
    V_DATA_ASSIGN       T_CSV_VAL :=T_CSV_VAL();
    V_BULK_DATA         T_CSV_VAL :=T_CSV_VAL();
BEGIN --t_csv_line
    SELECT FILE_BLOB INTO v_blob FROM  FILE_UPLOAD  WHERE ID=7;

    dbms_lob.createtemporary(v_clob,true);
    dbms_lob.converttoclob(dest_lob => v_clob,src_blob => v_blob,amount => dbms_lob.lobmaxsize,dest_offset => v_dest_offset,src_offset
    => v_src_offset,blob_csid => dbms_lob.default_csid,lang_context => v_lang_context,warning => v_warning);

    v_total_len := dbms_lob.getlength(v_clob);
    WHILE ( v_start_pos <= v_total_len ) LOOP
        v_new_line_pos := instr(v_clob,chr(10),v_start_pos);
        IF v_new_line_pos = 0 THEN
            v_new_line_pos := v_total_len + 1;
        END IF;
        v_current_line := substr(v_clob,v_start_pos,v_new_line_pos - v_start_pos);
        v_curr_row := apex_util.string_to_table(v_current_line,',');

            V_DATA_ASSIGN.EXTEND;
            V_DATA_ASSIGN(V_DATA_ASSIGN.count) := T_CSV_DATA(v_curr_row(1),v_curr_row(2),v_curr_row(3),v_curr_row(4),v_curr_row(5),
                                                             v_curr_row(6),v_curr_row(7),v_curr_row(8),v_curr_row(9),v_curr_row(10),
                                                             v_curr_row(11),v_curr_row(12),v_curr_row(13),v_curr_row(14),v_curr_row(15),
                                                             v_curr_row(16),v_curr_row(17),v_curr_row(18),v_curr_row(19),v_curr_row(20),
                                                             v_curr_row(21),v_curr_row(22),v_curr_row(23),v_curr_row(24),v_curr_row(25),
                                                             v_curr_row(26),v_curr_row(27),v_curr_row(28),v_curr_row(29),v_curr_row(30));
            v_start_pos := v_new_line_pos + 1;
    END LOOP;

    FOR rec IN V_DATA_ASSIGN.first..V_DATA_ASSIGN.last LOOP
        IF V_DATA_ASSIGN(rec).c001 = 16 THEN -- If first value is 16, then insert into TABLE_16(will have 16 columns).
            INSERT INTO TABLE_16.....
        ELSIF  V_DATA_ASSIGN(rec).c001 = 21 THEN -- If first value is 21, then insert into TABLE_21(will have 21 columns).
            INSERT INTO TABLE_21.....
        ELSIF  V_DATA_ASSIGN(rec).c001 = 11 THEN -- If first value is 11, then insert into TABLE_11(will have 11 columns).
            INSERT INTO TABLE_11.....
        ...
            ...
        ELSIF  V_DATA_ASSIGN(rec).c001 = 30 THEN -- If first value is 30, then insert into TABLE_30(will have 30 columns). and so on...
            INSERT INTO TABLE_30.....       
        END IF;
    END LOOP;
END;

3 个答案:

答案 0 :(得分:1)

我认为您走错了路。 PL / SQL您倾向于无法击败SQL * Loader,尤其是在启用直接路径和并行执行时。

这是一个简单的示例,显示了如何做到这一点。

创建表:我懒得创建16列或21列的表,因此我改用4(t1)和5(t1)。

SQL> create table t1 (id number, fname varchar2(20), lname varchar2(20), salary number);

Table created.

SQL> create table t2 (id number, fname varchar2(20), lname varchar2(20), salary number,hiredate date);

Table created.

控制文件:

options (direct=true, parallel=true)
load data 
infile *

into table t1
  append
  when (1) = '4'
  fields terminated by ',' optionally enclosed by '"'
  trailing nullcols  
  (
  id position(1) integer external, 
  fname char,
  lname char,
  salary integer external
  )

into table t2
  append
  when (1) = '5'
  fields terminated by ',' optionally enclosed by '"'
  trailing nullcols
  (
  id position (1) integer external, 
  fname char,
  lname char,
  salary integer external,
  hiredate "to_date(:hiredate, 'dd-mm-rr')"
  )

begindata
4,"Laura","Bissot",6506,14-06-04
5,"Mozhe","Atkinson",1202,20-08-05
5,"James","Marlow",1244,30-10-05
4,"TJ","Olson",4345,16-02-05

加载会话和结果:

SQL> $sqlldr scott/tiger control=test05.ctl log=test05.log

SQL*Loader: Release 11.2.0.2.0 - Production on Pon Kol 27 15:21:17 2018

Copyright (c) 1982, 2009, Oracle and/or its affiliates.  All rights reserved.


Load completed - logical record count 4.

SQL> select * From t1;

        ID FNAME                LNAME                    SALARY
---------- -------------------- -------------------- ----------
         4 Laura                Bissot                     6506
         4 TJ                   Olson                      4345

SQL> select * From t2;

        ID FNAME                LNAME                    SALARY HIREDATE
---------- -------------------- -------------------- ---------- ----------
         5 Mozhe                Atkinson                   1202 20/08/2005
         5 James                Marlow                     1244 30/10/2005

SQL>

答案 1 :(得分:0)

我认为最简单的解决方案是先使用Shell Script / SQL Loader将所有数据转储到PRE-Staging表中。 将数据转储到预暂存之后,您可以编写一个PLSQL块/程序包,以将特定的行插入所需的表中。

在PLSQL块中,您可以使用批量收集来最大化性能。

谢谢, Idrees

答案 2 :(得分:0)

我同意@Littlefoot SQL-Loader是自然而快捷的方法。
一种替代方法是使用Oracle的“ EXTERNAL TABLES”。

这样,您可以对操作系统(OS)的文件系统上的文件使用SELECT语句。
例如CSV格式的文本文件。

  

Book:数据库实用程序Chaepter:外部   桌子   https://docs.oracle.com/cd/E11882_01/server.112/e22490/part_et.htm#i436567
  外部表功能是对现有SQL Loader的补充   功能。它使您可以如同访问外部源中的数据一样   它在数据库的表中。
  请注意,SQL
Loader可能是   在需要额外数据加载情况下的更好选择   登台表的索引。

基本步骤:

1 -在运行Oracle实例的操作系统(OS)的文件系统中创建一个子目录。
  例如:   如果操作系统是Windows,则在单元“ C:”的子目录“ DATA”内创建子目录“ IN_FILES”

如果操作系统是Linux / Unix操作系统,则在根目录的“数据”子目录中创建一个子目录“ in_files”。

2 -在OS上,为运行Oracle实例的OS用户授予子目录步骤(1)的读写特权。

3 -在Oracle上创建目录对象
  您可以根据文件系统的规则使用完整路径。
  如果操作系统是Windows,则完整路径类似于“ C:\ DATA \ IN_FILES”

Create directory external_info as 'C:\DATA\IN_FILES'

如果操作系统为Linux / Unix,则完整路径类似于“ / data / in_files”

Create directory external_info as '/data/in_files'

4 -在Oracle上,对PUBLIC授予对步骤3的目录对象的读写权限。

允许在公共目录EXTERNAL_INFO上进行读取,写入;

5 -创建用于访问CSV文件的外部表:

在此示例中,文件为:
  a.CSV格式
  b。记录结束处是CARRIAGE_RETURN,后跟LINE_FEED(records delimited by '\r\n'
  c。第一条记录是标题列名称(skip 1
  d。字段分隔符是逗号(fields terminated by ','
  e.Data可以用引号引起来(ASCII字符34)(optionally enclosed by '"'
  f。缺少的字段将为空值(missing field values are null
  g。文件名为“ data01.csv”,该名称符合操作系统规则
    RemenberWINDOWS不区分大小写,但LINUX / Unix区分大小写。

create table data01_external
  (id       number,
   fname    varchar2(20),
   lname    varchar2(20),
   salary   number,
   hiredate date
  )
  organization external
  (type oracle_loader
   default directory external_info
   access parameters (records delimited by '\r\n'
                      badfile     'data01_%p.bad'
                      discardfile 'data01_%p.dis'
                      logfile     'data01_%p.log'
                      skip 1
                      fields terminated by ','
                             optionally enclosed by '"'
                             missing field values are null
                      (id        integer external,
                       fname     char,
                       lname     char,
                       salary    decimal external,
                       hiredate  char  date_format date mask 'dd-mm-rr'
                      )
                     )
   location ('data01.csv')
  )
  reject limit UNLIMITED;

6 -现在,您可以编写一个PL / SQL程序包以将数据插入到每个表中
对于此示例,我使用@Littlefoot的表T1和T2:

Create or replace package pk_load_info
  is
    procedure pr_load(p_isbFile_name     varchar2,
                      p_onuErrCode   out number,
                      p_osbErrDesc   out varchar2
                     );
End;
/


Create or replace package body pk_load_info
is
  procedure pr_load(p_isbFile_name     varchar2,
                    p_onuErrCode   out number,
                    p_osbErrDesc    out varchar2
                   )
  is
    sbEvent    varchar2(20);
    sbSentence varchar2(200);
  Begin
    p_onuErrCode:=0;
    p_osbErrDesc:=null;
    --
    if trim(p_isbFile_name) is null then
       p_onuErrCode:=101;
       p_osbErrDesc:='The name of the file is not to be null';
       return;
    End if;
    --
    -- you can use always the same file name of maybe use different file name in every run.
    sbEvent:='alter table';
    sbSentence:='alter table data01_external default directory EXTERNAL_INFO location ('||chr(39)||trim(p_isbFile_name)||chr(39)||')';
    Dbms_Output.Put_Line('sbSentence='||sbSentence);
    execute immediate sbSentence;
    --
    -- hint "append", in direct-path INSERT, data is appended to the end of the table
    sbEvent:='insert table T1';
    insert /*+ append */ into t1
    select a.id,
           a.fname,
           a.lname,
           a.salary
    from data01_external a
    where a.id=4;
    --
    sbEvent:='insert table T2';
    insert /*+ append */ into t2
    select a.id,
           a.fname,
           a.lname,
           a.salary,
           hiredate
    from data01_external a
    where a.id=5;
  Exception
    when others then
         p_onuErrCode:=sqlcode;
         p_osbErrDesc:='Event "'||sbEvent||'" '||sqlerrm;
  End pr_load;
End pk_load_info;
/

7 -将文件“ data02.csv”复制到步骤(1)的子目录中
数据是:

ID,FNAME,LNAME,SALARY,HIREDATE
4,"Laura","Bissot",6506
5,"Mozhe","Atkinson",1202,20-08-05
5,"James","Marlow",1244,30-10-05
4,"TJ","Olson",4345

8 -测试

Declare
  nuErrcode      number;
  sbErrdesc      varchar2(2000);
  Procedure print(p_isbTexto varchar2)
  is
  Begin
    if nvl(length(p_isbTexto),0)<=255 then
       dbms_output.put_line(p_isbTexto);
    Else
       dbms_output.put_line(substr(p_isbTexto,1,254)||'¬');
       dbms_output.put_line(substr(p_isbTexto,255,255));
    End if;
  End print;
Begin
  print(to_char(systimestamp,'yyyy-mm-dd hh24:mi:ss.ff4')||'|Begin');
  dbms_application_info.set_module('SQL','Inicio');
  --
  pk_load_info.pr_load('data02.csv',
                       nuErrcode,
                       sbErrdesc
                      );
  print('nuErrcode='||nuErrcode);
  print('sbErrdesc='||sbErrdesc);
  --
  if nuErrcode=0 then
     commit;
  Else
     rollback;
  end if;
  print(to_char(systimestamp,'yyyy-mm-dd hh24:mi:ss.ff4')||'|End');
  dbms_application_info.set_action('Fin');
End;
/

9 -查看数据

select *
from t1;
ID|FNAME|LNAME |SALARY|
 4|Laura|Bissot|6506  |
 4|TJ   |Olson |4345  |

select *
from t2;
ID|FNAME|LNAME   |SALARY|HIREDATE           |
5 |Mozhe|Atkinson|  1202|2005-08-20 00:00:00|
5 |James|Marlow  |  1244|2005-10-30 00:00:00|

再见