UTL_FILE using append mode creates repeating headers - oracle

How to use "A"ppend mode in UTL_FILE package, but only create one header (not repeating)? Is it possible? I'm appending data, but everytime it appends, it will create repeating headers.
My code:
CREATE OR REPLACE PROCEDURE p_test AS
CURSOR c_test IS
select blah, blah from dual;
v_file UTL_FILE.FILE_TYPE;
v_header varchar2(25);
BEGIN
v_file := UTL_FILE.FOPEN(location => 'my_dir',
filename => 'filetest09102019.csv',
open_mode => 'A',
max_linesize => 32767);
If file exists = 0 then --using fgetattr; if I use 1, repeating headers will print
v_header := 'col1, col2, col3';
utl_file.put_line (v_file, v_header);
Else null; end if; --unfortunately headers did not print at all when false/0
FOR cur_rec IN c_test LOOP
UTL_FILE.PUT_LINE(v_file, data from c_test );
END LOOP;
UTL_FILE.FCLOSE(v_file);
EXCEPTION
WHEN OTHERS THEN
UTL_FILE.FCLOSE(v_file);
END;

If you are calling this procedure multiple times, you will get the header appended to the file each time the procedure is called.
You could first check if the file exists before appending the header, e.g. using fgetattr to detect if the file is going to be appended Check if a file exists?
Alternatively, modify your code so that it only calls the procedure once and writes all the data in one go, without appending.

You can solve this with a bit of design. At the moment you have one procedure which opens the file in append mode, writes the header to it, then writes the data to it. What you need is a sub-routine for opening the file. This procedure would be and
implements the following logic:
Test whether the file exists (like this one)
If the file doesn't exist, create the file in Write mode, write the header and then close the file
Open the file in Append mode.
Your existing procedure now just calls the routine described above and writes the data to the opened file. Something this (using borrowed and untested code):
CREATE OR REPLACE PROCEDURE p_test AS
CURSOR c_test IS
select blah, blah from dual;
v_file UTL_FILE.FILE_TYPE;
v_header varchar2(25) := 'col1, col2, col3';
function open_file (p_filename in varchar2
, p_dirname in varchar2
, p_header in varchar2
)
return UTL_FILE.FILE_TYPE
is
fh UTL_FILE.FILE_TYPE;
l_fexists boolean;
l_flen number;
l_bsize number;
l_res number(1);
begin
utl_file.fgetattr(upper(p_DirName), p_FileName, l_fexists, l_flen, l_bsize);
if not l_fexists then
fh := UTL_FILE.FOPEN(location => p_DirName,
filename => p_FileName,
open_mode => 'W',
max_linesize => 32767);
utl_file.put_line (fh, p_header);
utl_file.fclose(fh);
end if;
fh := UTL_FILE.FOPEN(location => p_DirName,
filename => p_FileName,
open_mode => 'A',
max_linesize => 32767);
return fh;
end open_file;
BEGIN
v_file := open_file 'my_dir', 'filetest09102019.csv', v_header);
FOR cur_rec IN c_test LOOP
UTL_FILE.PUT_LINE(v_file, data from c_test );
END LOOP;
UTL_FILE.FCLOSE(v_file);
EXCEPTION
WHEN OTHERS THEN
UTL_FILE.FCLOSE(v_file);
END;
Strictly speaking open_file () doesn't need to a private procedure in this example. But general I think it's good practice to hide low level stuff in separate procedures, because it makes the main body of the code easier to read. Also it is frequently the case that we'll want to do this in more than one place (for more than one type of file) so it's handy to encapsulate.

Related

I am Getting invalid file operation error though file is present in my local system

create or replace directory MYCSV as 'E:\sqlloader\';
grant read, write on directory MYCSV to public;
declare
F UTL_FILE.FILE_TYPE;
V_LINE VARCHAR2 (1000);
V_id NUMBER(4);
V_NAME VARCHAR2(10);
V_risk VARCHAR2(10);
BEGIN
F := UTL_FILE.FOPEN ('MYCSV', 'testfile.csv', 'R');
IF UTL_FILE.IS_OPEN(F) THEN
LOOP
BEGIN
UTL_FILE.GET_LINE(F, V_LINE, 1000);
IF V_LINE IS NULL THEN
EXIT;
END IF;
V_id := REGEXP_SUBSTR(V_LINE, '[^,]+', 1, 1);
V_NAME := REGEXP_SUBSTR(V_LINE, '[^,]+', 1, 2);
V_risk := REGEXP_SUBSTR(V_LINE, '[^,]+', 1, 3);
INSERT INTO loader_tab VALUES(V_id, V_NAME, V_risk);
COMMIT;
EXCEPTION
WHEN NO_DATA_FOUND THEN
EXIT;
END;
END LOOP;
END IF;
UTL_FILE.FCLOSE(F);
END;
/
CSV file content wherein I need to start loading from 1,a,aa and need to skip first 4 lines:
portal,,
ex portal,,
,,
i_id,i_name,risk
1,a,aa
2,b,bb
3,c,cc
4,d,dd
5,e,ee
6,f,ff
7,g,gg
8,h,hh
9,i,ii
10,j,jj
I want to load the data from excel but I am getting an invalid file operation error. Will someone help with this? Not able to load the data from an excel file. I am Getting invalid file operation error though file is present in my local system.
though file is present in my local system
It won't work unless your local system (I presume you mean your own PC) also runs the database into which you're trying to load data. Oracle directory (probably in 99% of all cases) resides on the database server.
I want to load the data from excel
It won't work either, if that's really an Excel file. Code you posted suggests that it is a comma-separated values file (textual, that is), and yes - it should be such a file, not XLSX.

Downloading files from the database server via APEX

I am trying to find a way to get files from the database server via APEX. I can't find any documentation about this issue.
Can I avoid using the plsql code?
PS: I'm launching APEX on tomcat on different server that db is.
As we talked on the comments, let me show you an example. I write you a lot of thing so take your time...
Upload file
You can have an object in Apex that allows the user to browse for a file to be uploaded. At the end, the user press a button that triggers the action . The button upload submits the page, and the action is after submit for the button itself. Any file upload is stored automatically in apex_application_temp_files ( keep in mind I removed a lot of controls I have regarding format of the file, size, etc ).
First create the directory
create or replace directory yourdirectory as '/your_path' ;
grant read, write on directory yourdirectory to your_user ;
The code in the button:
declare
v_error VARCHAR2(400);
v_filename VARCHAR2(400);
v_name VARCHAR2(400);
v_blob blob;
vodate number(8);
begin
SELECT filename,blob_content,name, to_number(regexp_replace(filename,'[0-9]{4}[0-9]{2}[0-9]{2}'))
INTO v_filename,v_blob,v_name,vodate
FROM apex_application_temp_files
WHERE name = :P2_FILE;
apex_debug.enable ( p_level => 5 );
apex_debug.message(p_message => 'v_filename is '||v_filename||' ', p_level => 5) ;
apex_debug.message(p_message => 'v_name is '||v_name||' ', p_level => 5) ;
apex_debug.message(p_message => 'vodate is '||to_number(substr(v_filename,14,8)) ||' ', p_level => 5) ;
-- insert into filesystem
p_write_blob_to_file(p_name=>v_name);
EXCEPTION
WHEN OTHERS THEN
raise;
end;
The important part here is the code p_write_blob_to_file. This is the code of that procedure, keeping in consideration that in my case p_dir takes a default value.
CREATE OR REPLACE procedure p_write_blob_to_file (p_name IN VARCHAR2, p_dir IN VARCHAR2 default 'your_directory' )
IS
l_blob BLOB;
l_blob_length INTEGER;
l_out_file UTL_FILE.file_type;
l_buffer RAW (32767);
l_chunk_size BINARY_INTEGER := 32767;
l_blob_position INTEGER := 1;
l_file_name varchar2(2000);
v_mime_type varchar2(2000);
BEGIN
-- Retrieve the BLOB for reading
SELECT blob_content, filename, mime_type
INTO l_blob, l_file_name, v_mime_type
FROM apex_application_temp_files
WHERE name = p_name;
-- Retrieve the SIZE of the BLOB
l_blob_length := DBMS_LOB.getlength (l_blob);
-- Open a handle to the location where you are going to write the BLOB
-- to file.
l_out_file :=
UTL_FILE.fopen (p_dir,
l_file_name,
'wb',
l_chunk_size);
-- Write the BLOB to file in chunks
WHILE l_blob_position <= l_blob_length
LOOP
IF l_blob_position + l_chunk_size - 1 > l_blob_length
THEN
l_chunk_size := l_blob_length - l_blob_position + 1;
END IF;
DBMS_LOB.read (l_blob,
l_chunk_size,
l_blob_position,
l_buffer);
UTL_FILE.put_raw (l_out_file, l_buffer, TRUE);
l_blob_position := l_blob_position + l_chunk_size;
END LOOP;
-- Close the file handle
UTL_FILE.fclose (l_out_file);
END p_write_blob_to_file;
/
Download File
In order to download the file, you need the opposite path.
The button or link download must be associated to a component PL/SQL
The button loads the file first from the directory in the server into a column
The button then download the file
I was going to write all the commands here , but you have a find very good example here of both actions:
Load file from directory into column
https://renaps.com/en/blog/how-to/how-to-load-file-content-to-a-blob-field-and-unload-blob-content-to-a-file-on-the-os
Download file
https://oracle-base.com/articles/misc/apex-tips-file-download-from-a-button-or-link#apex-button
Try to experiment with this and let me know any issues you might find. The only tricky thing here is that in Apex you need to pass the name of the file you want to download. So the user must know the name, exactly as it is in the server. What you can't do is provide a graphical interface to the server in order to select the file.

How to open direct CSV file using TEXT_IO in oracle forms?

I applied this code in oracle forms 10g on trigger WHEN-BUTTON-PRESSED. This code only save the file on target location.
CODE:
PROCEDURE GEN_EXCEL IS
IN_FILE TEXT_IO.FILE_TYPE;
VC_HEAD Varchar2(32000);
vc_file_path Varchar2(50) := 'C:\';
BEGIN
IN_FILE := TEXT_IO.FOPEN(vc_file_path||'Test'||'.CSV','W');
TEXT_IO.PUT_LINE(IN_FILE,'YOUR_TITLE'||chr(10));
VC_HEAD := 'header1,header2,header3,header4';
TEXT_IO.PUT_LINE(IN_FILE,VC_HEAD);
FOR C1 IN ( SELECT column1, column2, column3, column4
FROM Table_name)
LOOP
TEXT_IO.PUT_LINE(IN_FILE,C1.col1||','||C1.col2||','||C1.col3||','||C1.col4);
END LOOP;
TEXT_IO.FCLOSE(IN_FILE);
MESSAGE('Excel file has been created!');
MESSAGE('Excel file has been created!');
EXCEPTION
WHEN Others THEN
TEXT_IO.FCLOSE(IN_FILE);
MESSAGE('Error while writing file.');
MESSAGE('Error while writing file.');
END;
I want when-button-pressed then direct open CSV file from oracle forms
How to achieve this target?
You can use webutil to do this.
It has a function client_host.
Also instead of duplicating your messages you can put pause after the first message. Then you don't need the second message for a popup of the message.
If you want to read-in your .csv file I've found a suitable solution for my problem.
Maybe it helps you too if I did understand it right what you want.
Reading From File
March 19, 2009
--------------------------------------------------
Declare
vfilename varchar2(500);
in_file Client_Text_IO.File_Type;
linebuf VARCHAR2(1800);
BEGIN
vfilename := client_get_file_name('c:/temp/', File_Filter=>'Comma Dialimeted Files (*.csv)|*.csv|');
in_file := client_Text_IO.Fopen(vfilename, 'r');
GO_BLOCK('Emp');
FIRST_RECORD;
LOOP
Client_Text_IO.Get_Line(in_file, linebuf);
p_output_line(linebuf);
Client_Text_IO.New_Line;
Next_record;
END LOOP;
FIRST_RECORD;
EXCEPTION
WHEN no_data_found THEN
Client_Text_IO.Put_Line('Closing the file...');
Client_Text_IO.Fclose(in_file);
END;
-------------------------------------------------------
PROCEDURE p_output_line(p_line varchar2) IS
vLINE VARCHAR2(4000);
vVALUE VARCHAR2(1000);
vCOMMA_COUNT NUMBER;
vREPORT_DATE DATE;
BEGIN
vLINE := p_line;
vCOMMA_COUNT := LENGTH(vLINE)- LENGTH(REPLACE(vLINE,',','')); -- COUNT THE NUMBER OF COMMAS
FOR I IN 1.. vCOMMA_COUNT+1 LOOP
vVALUE := SUBSTR(vLINE,1,INSTR(vLINE,',')-1); -- IF vLINE = 123,ABC,9877 THEN VVALUE WILL BE 123
IF vVALUE IS NULL THEN
vVALUE := vLINE;
END IF;
vLINE := SUBSTR(vLINE,INSTR(vLINE,',')+1) ; -- CHANGE 123,ABC,9877 TO BE ABC,9877
IF I = 1 THEN
:DATA.BMK_NAME := vVALUE;
ELSIF I = 2 THEN
vREPORT_DATE := last_day(to_date(vVALUE,'dd-mm-yyyy'));
:DATA.REPORT_DATE := vREPORT_DATE;
ELSIF I = 3 THEN
:DATA.BMK_RETURN := to_number(vVALUE);
END IF;
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
MESSAGE('Please Check the data type is appropriate on you excel file');
MESSAGE('Please Check the data type is appropriate on you excel file');
END;
-----------------------------------------------------------------------
-- notes
1- you must install webutil version 106 or later
2- make sure that you attached and compiled the webutill.pll scucessfuly

Handling headers using utl_file upload procedure in oracle

I am trying to upload a csv file having headers into a table using UTL_FILE, but I am facing an issue. With normal upload using utl_file, the headers are also included and the data results to be recorded in double quotes(which I don't require). If I exclude headers by using an if condition as in below code and declaring headline variable, it uploads the records without headers but still in double quotes. When I manually remove headers from csv file it uploads fine without quotes. But the csv is created newly every day so headers can't be removed from file.
Please suggest some solution which matches the headers between target table and csv then uploads the csv file data. Below is my procedure.
create or replace procedure load_file_new(p_FileDir in varchar2, p_FileName in varchar2)
as
v_FileHandle utl_file.file_type;
v_NewLine varchar2(2000);
v_a varchar2(100);
v_b varchar2(100);
v_c varchar2(100);
v_d varchar2(100);
p_ignore_headerlines number;
begin
v_FileHandle := utl_file.fopen(p_FileDir, p_FileName, 'r', 32767);
p_ignore_headerlines := 1;
if p_ignore_headerlines > 0
then
begin
for i in 1 .. p_ignore_headerlines
loop
utl_file.get_line(v_FileHandle, v_NewLine);
end loop;
end;
end if;
loop
begin
utl_file.get_line(v_FileHandle, v_NewLine);
exception
when no_data_found then
exit;
end;
v_a := regexp_substr(v_NewLine, '("[^"]*"|[^,]+)', 1, 1);
v_b := regexp_substr(v_NewLine, '("[^"]*"|[^,]+)', 1, 2);
v_c := regexp_substr(v_NewLine, '("[^"]*"|[^,]+)', 1, 3);
v_d := regexp_substr(v_NewLine, '("[^"]*"|[^,]+)', 1, 4);
merge into test using dual on (a = v_a)
when not matched then insert (a, b, c, d)
values (v_a, v_b, v_c, v_d)
when matched then update set
b = v_b, c = v_c, d = v_d where a = v_a;
end loop;
utl_file.fclose(v_FileHandle);
commit;
end load_file_new;

referencing a variable from another procedure in plsql

I have written a package :
CREATE OR REPLACE
PACKAGE BODY NEW_HIRE_PKG
AS
PROCEDURE load_emp(
errbuf OUT VARCHAR2,
retcode OUT VARCHAR2 )
AS
CURSOR cur_person_info
IS
SELECT * FROM table_abc;
CURSOR cur_person_adr
IS
SELECT * FROM table_adr;
l_person_id NUMBER;
l_emp_num NUMBER;
lv_add_type VARCHAR2(100);
lv_address_line1 VARCHAR2(100);
BEGIN
FOR person_info_rec IN cur_address_info
LOOP
hr_employee_api.create_employee ( p_validate => FALSE,
--INPUT Parameter
P_HIRE_DATE =>person_info_rec.DATE_START,
-- output
p_employee_number => lc_employee_number, p_person_id => ln_person_id );
END LOOP ;
END;
PROCEDURE load_add(
errbuf OUT VARCHAR2,
retcode OUT VARCHAR2 )
as
ln_person_id number;
BEGIN
FOR address_info_rec IN cur_address_info
LOOP
BEGIN
hr_person_address_api.create_person_address
(p_validate => FALSE,
p_effective_date => TRUNC(SYSDATE),
p_person_id=> ln_person_id,
--output
p_address_type => lv_add_type,
p_address_line1 => lv_address_line1);
end;
end loop;
end;
end;
Now in the procedure the load_add there is a variable ln_person_id which should be the person ids generated in the procedure load_emp. I want to pass it in this procedure one by one. Can i do it by making ln_person_id an object ?
Judging by your code you have two concurrent programs - one that calls load_emp() and one that calls load_add(). If that's really the structure you want then the two calls will run in separate sessions and there's nothing you can do to pass a variable from one to the other. The best you could do would be to hold all the person_id values from load_emp in a custom table. The data could then later be consumed by load_add().
However I would restructure your package. Why not call load_add() from within the LOOP in load_emp() ?

Resources