2010-03-17

sql loader загрузка master-detail

Задался интересным вопросом, вставки через loader таблиц master-detail.
Взял примерчик с forum.oracle.com, немного его попилил и наткнулся на проблему, что генерируемый в master table ключ получить из записи detail совсем не просто.

Итак задача: есть текстовый файл, содержащий данные из родительской и дочерней таблицы в перемешку. Нет доступа к серверу :) (что бы не было желания делать внешние таблицы) и неохота делать пост-процедуры обработки (как советует делать дядюшка Кайт). Но есть желание поизголяться с sql loader.

Пример входных данных:
M,master1
D,master1-detail1
D,master1-detail2
D,master1-detail3
M,master2
D,master2-detail1
M,master3
D,master3-detail1
Скрипт для создания объектов:
CREATE TABLE master_table(ID NUMBER,code VARCHAR2(50),creation DATE);
CREATE TABLE detail_table(pid NUMBER, NAME VARCHAR2(100));
CREATE SEQUENCE testseq INCREMENT BY 1;


Решение 1 (используя sequence)
OPTIONS (ROWS = 1)
LOAD DATA
INFILE *
TRUNCATE
INTO TABLE master_table
WHEN (1) ='M'
FIELDS TERMINATED BY ',' optionally enclosed by '"' TRAILING NULLCOLS
(id  expression "testseq.nextval",
mcol1 filler,
code,
creation "sysdate"
)

INTO TABLE detail_table
WHEN (1) ='D'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' TRAILING NULLCOLS
(col1 filler position(1:2),
pid  expression "testseq.currval" ,
name  "UPPER(:name)")

BEGINDATA
M,master1
D,master1-detail1
D,master1-detail2
D,master1-detail3
M,master2
D,master2-detail1
M,master3
D,master3-detail1


Без OPTIONS (ROWS = 1) ничего работать не будет -- все дочерние записи привяжутся к последней записи

Решение 2 (натолкнувшее на написание этой задачи)
Допустим у нас есть какой-то признак, по которому мы можем найти родительскую запись (в примере добавил в конце строки еще поле). Будем получать по этому признаку идентификатор родителькой записи:
OPTIONS (ROWS = 1)
LOAD DATA
INFILE *
TRUNCATE
INTO TABLE master_table
WHEN (1) ='M'
FIELDS TERMINATED BY ',' optionally enclosed by '"' TRAILING NULLCOLS
(mcol1 filler,
code,
id,
creation "sysdate"
)

INTO TABLE detail_table
WHEN (1) ='D'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' TRAILING NULLCOLS
(col1 filler position(1:2),
Name  "UPPER(:name)",
pid  "(select id from master_table WHERE id = :pid)")

BEGINDATA
M,master1,1
D,master1-detail1,1
D,master1-detail2,1
D,master1-detail3,1
M,master2,2
D,master2-detail1,2
M,master3,3
D,master3-detail1,3
Пример так же работает только с OPTIONS (ROWS = 1). Без этого запрос к получению родительского идентификатора выполняется столько раз, сколько COMMIT было сделано и строки привязываются в хаотичном порядке.

Но вынесем запрос для получения для идентификатора в функцию:
CREATE OR REPLACE FUNCTION f(aID VARCHAR2) RETURN NUMBER IS
BEGIN
 FOR rec IN (select id from master_table WHERE id = aID) LOOP
   RETURN rec.id;
 END LOOP;
 RETURN NULL;
END;
и будем ее вызывать:
Load DATA
INFILE *
TRUNCATE
INTO TABLE master_table
WHEN (1) ='M'
FIELDS TERMINATED BY ',' optionally enclosed by '"' TRAILING NULLCOLS
(mcol1 filler,
code,
id,
creation "sysdate"
)

INTO TABLE detail_table
WHEN (1) ='D'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' TRAILING NULLCOLS
(col1 filler position(1:2),
Name  "UPPER(:name)",
pid  "f(:pid)")

BEGINDATA
M,master1,1
D,master1-detail1,1
D,master1-detail2,1
D,master1-detail3,1
M,master2,2
D,master2-detail1,2
M,master3,3
D,master3-detail1,3
Все работает нормально и без OPTIONS (ROWS = 1).
Такое ощущение, что тут что-то аналогичное bind array в dbms_sql -- запросы выполняются 1 раз для первой переменной массива.

Трейс, полученный в результате эксперимента (для немного измененной таблицы) для INSERT:
INSERT INTO BDETAILS (ID,NAME,AMT,FLAG)
VALUES
(f(:"NAME"),(SELECT id FROM master_table WHERE chc = :"NAME") || ' ' ||
UPPER(:"NAME"),:AMT,:FLAG)


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 3 0.01 0.00 3 26 26 5
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.01 0.00 3 26 26 5
5 строк вставляются за 3 execute.