Oracle — файл .CSV для нескольких таблиц одновременно

Мне нужно загрузить файл .CSV в свои таблицы Oracle. но дело в том, что в одном файле CSV будут данные для нескольких таблиц. Хитрость в том, что нам нужно определить данные первого столбца для вставки в конкретную таблицу. т. е. если первый столбец имеет значение «16», то вся строка должна быть вставлена ​​в TABLE_16, которая будет иметь 16 столбцов, если значение равно 21, TABLE_21 будет иметь 21 столбец и так далее. Еще одна вещь, о которой нужно позаботиться, это то, что в моем CSV-файле будут миллионы записей, поэтому мне также нужно учитывать производительность процесса, поэтому я думаю, что BULK COLLECT и FORALL будут лучшим подходом для быстрой вставки данных.

Когда я пытаюсь запустить следующий блок, я получаю эту ошибку:

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;

person 124    schedule 27.08.2018    source источник


Ответы (3)


На мой взгляд, вы на ложном пути. 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>
person Littlefoot    schedule 27.08.2018

Я думаю, что самым простым решением было бы сначала сбросить все данные в таблицу PRE-Staging с помощью загрузчика Shell Script/SQL. После того, как данные будут сброшены в предварительную подготовку, вы можете написать блок/пакет PLSQL для вставки определенных строк в необходимые таблицы.

В блоке PLSQL вы можете использовать массовый сбор, чтобы максимизировать производительность.

Спасибо, Идрис.

person ihm017    schedule 27.08.2018
comment
файл будет загружен пользователем с помощью другого приложения (в столбце BLOB), и мне нужно запустить планировщик для обработки файла. Я не смогу использовать *Loader, я могу использовать для этого простой блок PL/SQL. - person 124; 27.08.2018

Я согласен с @Littlefoot SQL-Loader - это естественный и быстрый способ.
Альтернативой является использование "ВНЕШНИХ ТАБЛИЦ" Oracle.

При этом вы можете использовать оператор SELECT для файлов, находящихся в файловой системе вашей операционной системы (ОС).
Например, текстовые файлы в формате CSV.

Книга: Утилиты баз данных Глава: Внешние таблицы docs.oracle.com/cd/E11882_01/server.112/e22490/part_et.htm#i436567
Функция внешних таблиц является дополнением к существующим функциям SQLLoader. Это позволяет вам получать доступ к данным во внешних источниках, как если бы они находились в таблице базы данных.
Обратите внимание, что SQL
Loader может быть лучшим выбором в ситуациях загрузки данных, требующих дополнительной индексации промежуточной таблицы.

Основные шаги:

1-Создайте подкаталог в файловой системе операционной системы (ОС), в которой работает ваш экземпляр Oracle.
Например: если ОС — Windows, создайте подкаталог "IN_FILES" внутри подкаталога " ДАННЫЕ» на устройстве «C:»

Если ОС является разновидностью Linux/Unix, создайте подкаталог «in_files» внутри подкаталога «data» в корневом каталоге.

2 — В ОС предоставьте права на чтение и запись для шага подкаталога (1) пользователю ОС, работающему с экземпляром Oracle.

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 предоставляет доступ на чтение и запись для объекта каталога из шага 3 для PUBLIC.

разрешить чтение, запись в каталоге EXTERNAL_INFO для общего доступа;

5 – Создайте внешнюю таблицу для доступа к CSV-файлу:

В этом примере файл имеет следующий формат:
a.CSV
b.Конец записи — CARRIAGE_RETURN, за которым следует LINE_FEED (records delimited by '\r\n')
c.Первая запись – имена столбцов заголовков, (skip 1)
d.Поля разделяются запятой (fields terminated by ',')
e.Данные могут быть заключены в кавычки (символ 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|

до свидания

person alvalongo    schedule 28.08.2018