플랫 파일 언로드

  • SQLLDR로는 구현할 수 없는 기능
  • 오라클 명령어 툴에서조차 제공하지 않는 기능
  • SQLLDR또는 다른 프로그램에서 인식 간으한 포맷으로 데이터를 언로딩하는 것
  • EXP/IMP or EXPDP/IMPDP를 사용하지 않고 한 시스템에서 다른시스템으로 데이터를 옮기는 데 매우 유용하다.
h4. unloader 패키지 생성
{code:sql}
create or replace package unloader --
AUTHID CURRENT_USER -- SQL Injection 공격을 피하기위해 반드시 명시
as --
/* Function run – unloads data from any query into a file --
and creates a control file to reload that --
data into another table --
--
p_query = SQL query to "unload". May be virtually any query. --
p_tname = Table to load into. Will be put into control file. --
p_mode = REPLACE
APPENDTRUNCATE – how to reload the data --
p_dir = directory we will write the ctl and dat file to. --
p_filename = name of file to write to. I will add .ctl and .dat --
to this name --
p_separator = field delimiter. I default this to a comma. --
p_enclosure = what each field will be wrapped in --
p_terminator = end of line character. We use this so we can unload --
and reload data with newlines in it. I default to --
"
\n" (a pipe and a newline together) and "\r\n" on NT. --
You need only to override this if you believe your --
data will have that sequence in it. I ALWAYS add the --
OS "end of line" marker to this sequence, you should not --
*/ --
function run( p_query in varchar2, --
p_tname in varchar2, --
p_mode in varchar2 default 'REPLACE', --
p_dir in varchar2, --
p_filename in varchar2, --
p_separator in varchar2 default ',', --
p_enclosure in varchar2 default '"', --
p_terminator in varchar2 default '
' ) --
return number; --
end; --
/ --
create or replace package body unloader --
as --
--
--
g_theCursor integer default dbms_sql.open_cursor; --
g_descTbl dbms_sql.desc_tab; -- G_DESCTBL은 DBMS_SQL.DESCRIBE 호출의 출력을 유지하는 plsql테이블
g_nl varchar2(2) default chr(10); -- 개행문자
--
--
function to_hex( p_str in varchar2 ) return varchar2 -- 16진수를 문자로 변환한느데 사용하는 함수
is --
begin --
return to_char( ascii(p_str), 'fm0x' ); --
end; --
--
function is_windows return boolean -- 윈도우즈 플랫폼 여부에 따라 TRUE,FALSE를 반환하는 함수
is --
l_cfiles varchar2(4000); --
l_dummy number; --
begin --
if (dbms_utility.get_parameter_value( 'control_files', l_dummy, l_cfiles )>0) -- control_files파라미터를 조회하고 \를 찾으면 윈도우임
then --
return instr( l_cfiles, '\' ) > 0; --
else --
return FALSE; --
end if; --
end; --
--
procedure dump_ctl( p_dir in varchar2, -- DBMS_SQL.DESCRIBE_COLUMNS로 생성한 DESCRIBE테이블을 사용해서
p_filename in varchar2, -- 언로드 된 데이터를 다시 로드하기 위한 컨트롤 파일을 생성하는 프로시저
p_tname in varchar2, --
p_mode in varchar2, --
p_separator in varchar2, --
p_enclosure in varchar2, --
p_terminator in varchar2 ) --
is --
l_output utl_file.file_type; --
l_sep varchar2(5); --
l_str varchar2(5) := chr(10); --
--
begin --
if ( is_windows ) --
then --
l_str := chr(13)
chr(10); --
end if; --
--
l_output := utl_file.fopen( p_dir, p_filename
'.ctl', 'w' ); --
--
utl_file.put_line( l_output, 'load data' ); --
utl_file.put_line( l_output, 'infile '''
--
p_filename
'.dat'' "str x'''--
utl_raw.cast_to_raw( p_terminator
--
l_str )
'''"' ); --
utl_file.put_line( l_output, 'into table '
p_tname ); --
utl_file.put_line( l_output, p_mode ); --
utl_file.put_line( l_output, 'fields terminated by X'''
--
to_hex(p_separator)
--
''' enclosed by X'''
--
to_hex(p_enclosure)
''' ' ); --
utl_file.put_line( l_output, '(' ); --
--
for i in 1 .. g_descTbl.count --
loop --
if ( g_descTbl(i).col_type = 12 ) --
then --
utl_file.put( l_output, l_sep
g_descTbl(i).col_name--
' date ''ddmmyyyyhh24miss'' '); --
else --
utl_file.put( l_output, l_sep
g_descTbl(i).col_name--
' char('
--
to_char(g_descTbl(i).col_max_len*2)
' )' ); --
end if; --
l_sep := ','
g_nl ; --
end loop; --
utl_file.put_line( l_output, g_nl
')' ); --
utl_file.fclose( l_output ); --
end; --
--
function quote(p_str in varchar2, p_enclosure in varchar2) -- 인클로저 문자를 사용해서 인용된 문자열을 반환
return varchar2 -- 문자를 둘러싸는 것뿐만 아니라 문자열 내에 존재하는 인클로저 문자를 중복사용하면
is -- 인클로저 문자가 보존된다.
begin --
return p_enclosure
--
replace( p_str, p_enclosure, p_enclosure
p_enclosure )--
p_enclosure; --
end; --
--
function run( p_query in varchar2, -- RUN이라는 이름의 메인함수
p_tname in varchar2, --
p_mode in varchar2 default 'REPLACE', --
p_dir in varchar2, --
p_filename in varchar2, --
p_separator in varchar2 default ',', --
p_enclosure in varchar2 default '"', --
p_terminator in varchar2 default '
' ) return number --
is --
l_output utl_file.file_type; --
l_columnValue varchar2(4000); --
l_colCnt number default 0; --
l_separator varchar2(10) default ''; --
l_cnt number default 0; --
l_line long; --
l_datefmt varchar2(255); --
l_descTbl dbms_sql.desc_tab; --
begin --
select value -- 데이터를 디스크로 저장할 때 날짜와 시간을 보존하는 포맷으로변환하기위해
into l_datefmt -- NLS_DATE_FORMAT을 변수에 저장
from nls_session_parameters --
where parameter = 'NLS_DATE_FORMAT'; --
--
/* --
Set the date format to a big numeric string. Avoids --
all NLS issues and saves both the time and date. --
*/ --
execute immediate -- 오류가 발생했을때 NLS_DAE_FORMAT을 재설정 하기위해 예외처리 블록 설정
'alter session set nls_date_format=''ddmmyyyyhh24miss'' '; --
--
/* --
Set up an exception block so that in the event of any --
error, we can at least reset the date format. --
*/ --
begin --
/* --
Parse and describe the query. We reset the -- 파싱을 수행하고 쿼리를 정의
descTbl to an empty table so .count on it --
will be reliable. --
*/ --
dbms_sql.parse( g_theCursor, p_query, dbms_sql.native ); --
g_descTbl := l_descTbl; -- 글로벌 테이블ㅇ르 재설정
dbms_sql.describe_columns( g_theCursor, l_colCnt, g_descTbl ); --
--
/* --
Create a control file to reload this data --
into the desired table. --
*/ --
dump_ctl( p_dir, p_filename, p_tname, p_mode, p_separator, -- 한번수행하고 나면 실제로 컨트롤 파일에 생성하기위해 DUMP_CTL호출
p_enclosure, p_terminator ); --
--
/* --
Bind every single column to a varchar2(4000). We don't care --
if we are fetching a number or a date or whatever. --
Everything can be a string. --
*/ --
for i in 1 .. l_colCnt loop -- 실제 데이터를 디스크에 저장할 준비 완료
dbms_sql.define_column( g_theCursor, i, l_columnValue, 4000); -- 모든컬럼에 VARCHAR2(4000)으로 저장함으로써 모든 NUMBER,DATE,RAW
end loop; -- 등의 데이터타입은 VARCHAR2로 변환
--
/* --
Run the query - ignore the output of execute. It is only --
valid when the DML is an insert/update or delete. --
*/ --
l_cnt := dbms_sql.execute(g_theCursor); -- 데이터파일을 쓰기모드로 오픈하고, 쿼리로 모든데이터를 가져온 후 데이터 파일에 복사
--
/* --
Open the file to write output to and then write the --
delimited data to it. --
*/ --
l_output := utl_file.fopen( p_dir, p_filename
'.dat', 'w', --
32760 ); --
loop --
exit when ( dbms_sql.fetch_rows(g_theCursor) <= 0 ); --
l_separator := ''; --
l_line := null; --
for i in 1 .. l_colCnt loop --
dbms_sql.column_value( g_theCursor, i, --
l_columnValue ); --
l_line := l_line
l_separator--
quote( l_columnValue, p_enclosure ); --
l_separator := p_separator; --
end loop; --
l_line := l_line
p_terminator; --
utl_file.put_line( l_output, l_line ); --
l_cnt := l_cnt+1; --
end loop; --
utl_file.fclose( l_output ); --
--
/* --
Now reset the date format and return the number of rows -- DATE포맷을 이전 값으로 설정
written to the output file. --
*/ --
execute immediate --
'alter session set nls_date_format='''
l_datefmt''''; --
return l_cnt; --
exception --
/* --
In the event of ANY error, reset the data format and --
re-raise the error. --
*/ --
when others then --
execute immediate --
'alter session set nls_date_format='''
l_datefmt''''; --
RAISE; --
end; --
end run; --
--
--
end unloader; --
/ --
set serveroutput on --
create or replace directory my_dir as '/tmp'; --
declare --
l_rows number; --
begin --
l_rows := unloader.run --
( p_query => 'select * from scott.emp order by empno', -- 먼저 scott.emp에대한 select 권한부여
p_tname => 'emp', --
p_mode => 'replace', --
p_dir => 'MY_DIR', --
p_filename => 'emp', --
p_separator => ',', --
p_enclosure => '"', --
p_terminator => '~' ); --
--
dbms_output.put_line( to_char(l_rows)
--
' rows extracted to ascii file' ); --
end; --
/ --
|
|h4. 컨트롤파일
{code:sql}
load data
infile 'emp.dat' "str x'7E0A'"                                                      -- SQLLDR의 STR 속성 사용
into table emp                                                                      --
replace                                                                             --
fields terminated by X'2C enclosed by X'22'                                         --구분자 문자와 인클로저 문자 사용
(                                                                                   --
EMPNO char(44),                                                                     --
ENAME char(20),                                                                     --
JOB char(18),                                                                       --
MGR char(44),                                                                       --
HIREDATE date 'ddmmyyyyhh24miss',                                                   -- NLS문제를 피하고, 날짜필드의 시간 컴포넌트 유지하기위해 
SAL char(44),                                                                       --
COMM char(44),                                                                      --
DEPTNO char(44)                                                                     --
)                                                                                   --

  • Ask Tom 웹사이트에 PL/SQL뿐만아니라 Pro*C완 SQL*Plus 스크립트로 수행할 수 있는 예제를 볼 수 있다.
  • Pro*C는 가장빠르게 수행할 수 있고, 항상 클라이언트 워크스테이션 파일 시스템에 쓸 수 있다.
  • PL/SQL은 모든환경에서 실행하는데 유용(컴파일 및 설치가 필요없다)하나 서버파일시스템에만 기록할 수 있다..
  • SQL*Plus는 좋은 절충안으로 적정수준의 성능을 제공하고 클라이언트 파일 시스템에 기록 할 수 있다.