目的
oracle ebs + PL/SQL实现将查询出来的数据保存为csv格式文件,并定期上传到FTP服务器。
用到oracle utl_file包,FTP文件上传
第一次接触这种类型的任务,也是在网上查询了很多参考资料才弄出来。
下面是具体的例子。
先在前台配置请求:系统管理员→并发→程序→定义
create or replace PACKAGE ML_OPCM_INFORMATION_UPLOAD AS
procedure ML_OPCM_INFORMATION(
o_err_code OUT NUMBER,
o_err_msg OUT VARCHAR2,
IC_TABLE_NAME in varchar2 , -- FTP服务器列表
IC_ROW_NAME in varchar2 , --FTP服务器行名
IC_FILE_NAME in varchar2 -- 文件名称
) ;
END ML_OPCM_INFORMATION_UPLOAD;
包体
create or replace PACKAGE BODY ML_OPCM_INFORMATION_UPLOAD AS
PROCEDURE SEND_OUTPUT(
ic_output IN VARCHAR2)
AS
BEGIN
fnd_file.put_line(fnd_file.output, ic_output);
--dbms_output.put_line (ic_output);
END SEND_OUTPUT;
PROCEDURE SEND_LOG(
ic_log IN VARCHAR2)
AS
BEGIN
fnd_file.put_line(fnd_file.log, ic_log);
--dbms_output.put_line (ic_log);
END SEND_LOG;
procedure ML_OPCM_INFORMATION(
o_err_code OUT NUMBER,
o_err_msg OUT VARCHAR2,
IC_TABLE_NAME in VARCHAR2 , -- FTP服务器列表
IC_ROW_NAME in VARCHAR2 , --FTP服务器行名
IC_FILE_NAME in VARCHAR2 -- 文件名称
)
is
l_conn UTL_TCP.connection;
vc_ftp_server_host VARCHAR2(100) :=NULL; --服务器地址
vc_ftp_server_port VARCHAR2(10) :=NULL; --端口
vc_ftp_account VARCHAR2(100) :=NULL; --用户名
vc_ftp_password VARCHAR2(100) :=NULL; --密码
l_output utl_file.file_type;
l_theCursor integer default dbms_sql.open_cursor; --OPEN_CURSOR: 返回新游标的ID值
l_columnValue VARCHAR2(2000);
l_status integer;
l_colCnt number default 0;
l_separator VARCHAR2(1) ;
p_filename VARCHAR2(50) ;
l_cnt number default 0;
p_query varchar2(4000);
l_desctbl DBMS_SQL.DESC_TAB;
date_famart VARCHAR2(10) default 'YYYY-MM-DD';
CURSOR cur_ftp_info(ic_cur_table_name VARCHAR2,ic_cur_row_name VARCHAR2)
IS
SELECT put.user_table_name table_name,
puc.user_column_name column_name,
purf.row_low_range_or_name row_name,
pucif.VALUE p_value
FROM pay_user_tables put,
pay_user_columns puc,
pay_user_rows_f purf,
pay_user_column_instances_f pucif
WHERE put.user_table_id = puc.user_table_id
AND put.user_table_id = purf.user_table_id
AND pucif.user_row_id = purf.user_row_id
AND pucif.user_column_id = puc.user_column_id
AND purf.row_low_range_or_name = ic_cur_row_name
AND put.user_table_name = ic_cur_table_name ;
begin
p_filename := 'TERMINATION_EMP.CSV';
-- 先要创建路径 create or replace directory UTL_FILE_DIR as '/tmp';
begin
l_output := utl_file.fopen('UTL_FILE_DIR', p_filename, 'w');
exception
when others then
SEND_LOG('Error when open the file from UTL_FILE_DIR :'||SQLCODE||'-'||SQLERRM);
end;
BEGIN
--p_query定义为varchar2类型,如果超过varchar2最大值可以用clob类型
p_query :='select * from emp ';
-- DBMS_OUTPUT.PUT_LINE(p_query);
--PARSE:解析要执行的语句
-- p_query sql语句
dbms_sql.parse(l_theCursor, p_query, dbms_sql.native );
DBMS_SQL.DESCRIBE_COLUMNS(l_theCursor, l_colCnt, l_desctbl);
--DUMP TABLE COLUMN NAME
FOR I IN 1 .. l_colCnt LOOP
UTL_FILE.PUT(l_output,l_separator || l_desctbl(I).COL_NAME ); --输出表字段
DBMS_SQL.DEFINE_COLUMN(l_theCursor, I, l_columnValue, 4000); --DEFINE_COLOUMN:定义字段变量,其值对应于指定游标中某个位置元素的值
l_separator := ',';
END LOOP;
--记事本打开会换行 chr(13),chr(10)分别对应\r,\n
UTL_FILE.put(l_output,chr(13)||chr(10));
UTL_FILE.NEW_LINE(l_output,0);
--UTL_FILE.NEW_LINE(l_output); --这种方式记事本打开文件不换行
--EXECUTE THE QUERY STATEMENT
L_STATUS := DBMS_SQL.EXECUTE(l_theCursor); --EXECUTE:执行指定的游标
--DUMP TABLE COLUMN VALUE
WHILE (DBMS_SQL.FETCH_ROWS(l_theCursor) > 0) LOOP -- FETCH_ROWS:从指定的游标中取出记录
l_separator := '';
FOR I IN 1 .. l_colCnt LOOP
DBMS_SQL.COLUMN_VALUE(l_theCursor, I, l_columnValue); --COLUMN_VALUE:返回游标中指定位置的元素
UTL_FILE.PUT(l_output,l_separator || l_columnValue );
l_separator := ',';
END LOOP;
UTL_FILE.put(l_output,chr(13)||chr(10));
UTL_FILE.NEW_LINE(l_output,0);
--UTL_FILE.NEW_LINE(l_output);
END LOOP;
--下面这部分为另外一种写法,也可行
-- for i in 1 .. 255 loop
-- begin
-- dbms_sql.define_column( l_theCursor, i,l_columnValue, 2000 ); --DEFINE_COLOUMN:定义字段变量,其值对应于指定游标中某个位置元素的值
-- l_colCnt := i;
-- exception
-- when others then
-- if ( sqlcode = -1007 ) then exit;
-- else
-- raise;
-- end if;
-- end;
-- end loop;
-- DBMS_OUTPUT.PUT_LINE('l_colCnt: '||l_colCnt);
-- dbms_sql.define_column( l_theCursor, 1, l_columnValue,2000 );
--
-- l_status := dbms_sql.execute(l_theCursor); --EXECUTE:执行指定的游标
-- --EmpID, First, PreferredName, Middle, Last, Gender, DateOfBirth ,OrigHireDate, LastHireDate, JobCode, JobTitle,
-- --SupervisorID ,SupvName ,EmailAddress ,WrkPhoneNo, WrkLoc, TermDate ,Business Group, ITAR Compliant ,MobilePhoneNo ,
-- --FuncReportsTo ,Department ,AltTitle, MorE, Mailstop, Company, FullorPartTime, AD Acct_NTUserID
--
-- utl_file.put( l_output, 'Empid,First,PreferredName,Middle,last,Gender,DateOfBirth,OrigHireDate,LastHireDate,JobCode,JobTitle,SupervisorID,SupvName,EmailAddress,WrkPhoneNo,WrkLoc,TermDate,Business Group,ITAR Compliant,MobilePhoneNo,FuncReportsTo,Department,AltTitle,MorE,Mailstop,Company,FullorPartTime,AD Acct_NTUserID,BANKNAME,BANKBRANCHNAME, BANKACCNAME, BANKACCNUMBER, BANKACCCURRENCY');
-- utl_file.new_line( l_output );
---- utl_file.put_line( l_output, 'EmpID,First,PreferredName,Middle,Last,Gender,DateOfBirth,OrigHireDate,LastHireDate,JobCode,JobTitle,SupervisorID,SupvName,EmailAddress,WrkPhoneNo,WrkLoc,TermDate,Business Group,ITAR Compliant,MobilePhoneNo,FuncReportsTo,Department,AltTitle,MorE,Mailstop,Company,FullorPartTime,AD Acct_NTUserID');
--
-- loop
-- exit when ( dbms_sql.fetch_rows(l_theCursor) <= 0 ); -- FETCH_ROWS:从指定的游标中取出记录
-- l_separator := '';
-- for i in 1 .. l_colCnt loop
-- dbms_sql.column_value( l_theCursor, i,l_columnValue );
-- utl_file.put( l_output, l_separator ||l_columnValue); --COLUMN_VALUE:返回游标中指定位置的元素
-- l_separator := ',';
-- end loop;
-- utl_file.put(l_output, ' ');
-- utl_file.new_line( l_output );
-- l_cnt := l_cnt+1;
-- end loop;
dbms_sql.close_cursor(l_theCursor); --CLOSE_CURSOR:关闭指定的游标并释放内存
utl_file.fclose( l_output ); --CLOSE FILE
EXCEPTION
WHEN OTHERS THEN
SEND_LOG('Error when output file :'||SQLCODE||'-'||SQLERRM);
RAISE;
END;
-------获取ftp服务器信息,这里我开始在前台有定义一个表结构和表值来保存服务器信息
BEGIN
FOR cur_temp IN cur_ftp_info(ic_table_name,ic_row_name)
LOOP
IF cur_temp.column_name ='FTP_SERVER_PASSWORD' THEN
vc_ftp_password :=cur_temp.p_value;
ELSIF cur_temp.column_name='FTP_SERVER_PORT' THEN
vc_ftp_server_port :=cur_temp.p_value;
ELSIF cur_temp.column_name='FTP_SERVER_ACCOUNT' THEN
vc_ftp_account :=cur_temp.p_value;
ELSIF cur_temp.column_name='FTP_SERVER_HOST' THEN
vc_ftp_server_host :=cur_temp.p_value;
END IF;
END LOOP;
IF vc_ftp_password IS NULL OR vc_ftp_server_port IS NULL OR vc_ftp_account IS NULL OR vc_ftp_server_host IS NULL THEN
SEND_OUTPUT('Can not get ftp server information: please check the value of table name,row name,form');
RAISE_APPLICATION_ERROR(-20001,'Error when get ftp server information:'||SQLCODE||'-'||SQLERRM);
END IF;
--ML_FTP 就是网上的ftp
l_conn := ML_FTP.login(vc_ftp_server_host,vc_ftp_server_port,vc_ftp_account,vc_ftp_password);
ML_FTP.binary(p_conn => l_conn); -- Binary模式不会对数据进行任何处理
ML_FTP.put(p_conn => l_conn,
p_from_dir => 'UTL_FILE_DIR', --这里的路径名注意要用大写
p_from_file => p_filename, --服务器上的文件名
p_to_file => IC_FILE_NAME ); --想要在服务器上生成的文件名称
ML_FTP.logout(l_conn);
utl_tcp.close_all_connections;
SEND_OUTPUT('upload file to ftp completed.');
EXCEPTION
WHEN OTHERS THEN
SEND_LOG('Error when upload file to ftp:'||SQLCODE||'-'||SQLERRM);
RAISE_APPLICATION_ERROR(-20001,'Error when upload file to ftp:'||SQLCODE||'-'||SQLERRM);
END;
END ML_OPCM_INFORMATION;
END ML_OPCM_INFORMATION_UPLOAD;
定期上传就在ebs前台设置一个job即可
或者后台设置
ORACLE JOB有定期执行的功能,具体的可以看这个文档
ORACLE JOB定时任务 https://wenku.baidu.com/view/7cc4cf47866fb84ae55c8d04.html
–查询后台JOB
SELECT * FROM DBA_JOBS;