Basically its a letter printing in text I'm trying to do via an Oracle PL/SQL Procedure.
My Code below: -
DECLARE
out_file UTL_FILE.file_type; lv_file VARCHAR2 (200) DEFAULT TO_CHAR (SYSDATE, 'yyyymmddhhmiss')||'.txt' ;LVSQL varchar2(4000); lV_TXT varchar2(4000);lv_txt1 varchar2(4000);lv_final_str varchar2(4000); BEGIN out_file := UTL_FILE.fopen ('SPOOL_DIR', lv_file, 'W'); for i in 1..5 loop
begin
select tn,tt into lv_txt,lv_txt1 from
(SELECT rownum r, TNAME tn ,TABTYPE tt FROM tab) where r=j;
lv_final_str:=lV_TXT||chr(9)||lv_txt1||CHR(13);
utl_file.put_line(Out_file,lv_final_str );
end; end loop;
UTL_FILE.fclose (out_file);
END;
I'm able to print the values in next next line by concatenating the lv_final_str with CHR(13) and that I use the *utl_file.put_line(Out_file,lv_final_str );* within the loop
I want to do the same thing by collecting the text in a variable inside the loop.
and writing the value of the variable outside the loop, using the below code.
DECLARE
out_file UTL_FILE.file_type;
lv_file VARCHAR2 (200) DEFAULT TO_CHAR (SYSDATE, 'yyyymmddhhmiss')||'.txt' ;LVSQL varchar2(4000);
lV_TXT varchar2(4000);lv_txt1 varchar2(4000);tmpvar varchar2(4000);lv_build_str varchar2(4000);lv_final_str varchar2(4000);
BEGIN
--SELECT TO_CHAR (SYSDATE, 'yyyymmddhhmiss') INTO lv_file FROM DUAL;
out_file := UTL_FILE.fopen ('SPOOL_DIR', lv_file, 'W');
for i in 1..5 loop
begin
select tn,tt into lv_txt,lv_txt1 from
(SELECT rownum r, TNAME tn ,TABTYPE tt FROM tab) where r=i;
tmpvar:=lV_TXT||chr(9)||lv_txt1||CHR(13);
lv_build_str := lv_build_str || chr(9)||tmpvar;
-- utl_file.put_line(Out_file,lv_final_str );
end;
end loop;
lv_final_str:=lv_build_str;
utl_file.put_line(Out_file,lv_final_str );
UTL_FILE.fclose (out_file);
END;
how to do this, please help.
I used chr(10) as well, it is not printing in new line.
if you observe chr(9) which is used to print tab space is working well.
only next line chr(10) or chr(13) is not working. Why...?
any help..
I'm trying since last 3 days... please help.
It depends on whether you're writing to a file for a Unix environment, or Windows.
Unix uses just a single LF character CHR(10) whereas Windows expects a CR followed by LF CHR(13)||CHR(10).
Related
I have used a PL/SQL code to export data to csv file.The code is similar to below code:
CREATE OR REPLACE PROCEDURE export_to_csv IS
v_file UTL_FILE.file_type;
v_string VARCHAR2(4000);
CURSOR c_emp IS
SELECT empno, ename, deptno, sal, comm
FROM emp;
BEGIN
v_file := UTL_FILE.fopen('CSVDIR', 'empdata.csv', 'w', 1000);
-- if you do not want heading then remove below two lines v_string := 'Emp Code, Emp Name, Dept, Salary, Commission'; UTL_FILE.put_line (v_file, v_string);
FOR cur IN c_emp LOOP
v_string := cur.empno || ',' || cur.ename || ',' || cur.deptno || ',' ||cur.sal || ',' || cur.comm;
UTL_FILE.put_line(v_file, v_string);
END LOOP;
UTL_FILE.fclose(v_file);
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.is_open(v_file) THEN
UTL_FILE.fclose(v_file);
END IF;
END;
/
But one column which datatype is varchar2 is getting converted to number and hence loss of data is occuring.Could anyone please tell me how to stop the conversion?
There is probably no conversion and/or data loss at all. You can test it if you read the file you created. The result should be the same as you see it when you open it in Notepad++. If it's true that Notepad++ shows all the data then there's nothing lossed. So, create the file and read it afterwards. Compare the data with the result and you will know.
create or replace PROCEDURE read_csv IS
v_file UTL_FILE.file_type;
v_string VARCHAR2(4000);
BEGIN
v_file := UTL_FILE.fopen('CSVDIR', 'empdata.csv', 'r', 1000);
LOOP
UTL_FILE.GET_LINE(v_file, v_string);
DBMS_OUTPUT.PUT_LINE(v_string);
END LOOP;
UTL_FILE.fclose(v_file);
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.is_open(v_file) THEN
UTL_FILE.fclose(v_file);
END IF;
END;
--
-- R e s u l t
--
-- anonymous block completed
-- 7369,SMITH,20,800,
-- 7499,ALLEN,30,1600,300
-- 7521,WARD,30,1250,500
-- 7566,JONES,20,2975,
-- 7654,MARTIN,30,1250,1400
-- 7698,BLAKE,30,2850,
-- 7782,CLARK,10,2450,
-- 7788,SCOTT,20,3000,
-- 7839,KING,10,5000,
-- 7844,TURNER,30,1500,0
-- 7876,ADAMS,20,1100,
-- 7900,JAMES,30,950,
-- 7902,FORD,20,3000,
-- 7934,MILLER,10,1300,
I need to write one procedure in which there will be table name as an input parameter (Should have only one table at a time) and column names (should have multiple column names comma separated) and column values(should have multiple column names comma separated).
My Attempt:
CREATE OR REPLACE PROCEDURE sp_test_insert(
p_table_name IN VARCHAR2,
p_column_name IN VARCHAR2,-- It can have multiple column names separated by comma
p_column_value IN VARCHAR2-- It can have multiple column names separated by
)
AS
lv_str VARCHAR2(4000);
BEGIN
lv_str := 'INSERT INTO '||p_table_name||'should have multiple column names' ||
'VALUES('||'should have multiple column names' ||')';
EXECUTE IMMEDIATE lv_str;
END;
Tool used: SQL Developer(18c)
I am stuck on how to handle multiple column names and column values inside the procedure body. How will I define an array and proceed accordingly?
Don't. You are setting yourself up to have a procedure that is vulnerable to SQL injection attacks.
If you really want to (please don't):
CREATE OR REPLACE PROCEDURE sp_test_insert(
p_table_name IN VARCHAR2,
p_column_name IN VARCHAR2,
p_column_value IN VARCHAR2
)
AS
lv_str VARCHAR2(4000);
BEGIN
lv_str := 'INSERT INTO '||p_table_name||' (' || p_column_name || ') VALUES(' || p_column_value ||')';
EXECUTE IMMEDIATE lv_str;
END;
/
Then you can do:
BEGIN
sp_test_insert(
'my_table',
'col1, col2, col3',
q'['a', DATE '2022-05-31', 42]'
);
END;
/
But you can also do:
BEGIN
sp_test_insert(
'my_table',
'col1, col2, col3',
q'['a', (SELECT DATE '1970-01-01' FROM secret_table WHERE username = 'Admin' AND password_hash = 'abcgefg1234'), 42]'
);
END;
/
Don't make your application vulnerable to SQL injection attacks; avoid dynamic SQL if you can help it!
If you want to make it more resistant to SQL injection attacks then you can use the DBMS_ASSERT package:
CREATE OR REPLACE PROCEDURE sp_test_insert(
p_table_name IN VARCHAR2,
p_column_name IN SYS.ODCIVARCHAR2LIST,
p_column_value IN SYS.ODCIVARCHAR2LIST
)
AS
lv_str VARCHAR2(4000);
BEGIN
lv_str := 'INSERT INTO '
|| DBMS_ASSERT.SQL_OBJECT_NAME(
DBMS_ASSERT.ENQUOTE_NAME(p_table_name, FALSE)
)
||' ('
|| DBMS_ASSERT.ENQUOTE_NAME(p_column_name(1), FALSE);
FOR i IN 2 .. p_column_name.COUNT LOOP
lv_str := lv_str || ', '
|| DBMS_ASSERT.ENQUOTE_NAME(p_column_name(i), FALSE);
END LOOP;
lv_str := lv_str || ') VALUES('
|| DBMS_ASSERT.ENQUOTE_LITERAL(p_column_value(1));
FOR i IN 2 .. p_column_name.COUNT LOOP
lv_str := lv_str || ', '
|| DBMS_ASSERT.ENQUOTE_LITERAL(p_column_value(i));
END LOOP;
lv_str := lv_str || ')';
EXECUTE IMMEDIATE lv_str;
END;
/
Then:
BEGIN
sp_test_insert(
'MY_TABLE',
SYS.ODCIVARCHAR2LIST( 'COL1', 'COL2', 'COL3'),
SYS.ODCIVARCHAR2LIST( 'a', '31-MAY-2022', '42')
);
END;
/
However, the values are now all passed into the dynamic SQL statement as strings which makes it more difficult to pass data to DATE, TIMESTAMP or INTERVAL (etc.) columns as it relies on implicit data-type conversions.
You should still avoid dynamic SQL if possible.
db<>fiddle here
I have a column Value Méroné in my Oracle DB.
We are writing it to a csv file using utl_file package.Since this value has a special character we have used convert function to change the character coding to remove the junk character while writing. So the convert function goes like this-
convert(REC.DT25, 'WE8DEC'). But the problem now is that the value is coming as only Méron and the last character is missing. I have tried everything from changing it to different character encoding, but still no luck. Could you please help?
The code is as follows
CREATE OR REPLACE PROCEDURE SAMPLE_MERONE AS
CREATE OR REPLACE PROCEDURE SAMPLE_MERONE AS
CURSOR C1 IS select * from gsal_mosaic_prf_output where CS46SIGFORMALNAME
LIKE 'Lt. Jowens Méroné' AND ID_NUMBER='8-13678728';
MERONE_FILE UTL_FILE.FILE_TYPE;
V_MERONE_FILE VARCHAR2(300);
BEGIN
V_MERONE_FILE := 'MREONE_FILE.csv';
MERONE_FILE := UTL_FILE.FOPEN ( 'GSAL_PRF',V_MERONE_FILE,'w',32767) ;
IF UTL_FILE.IS_OPEN(MERONE_FILE) THEN
FOR REC IN C1
LOOP
UTL_FILE.PUT_LINE(MERONE_FILE,'"'||REC.ID_NUMBER||'","'||
REC.GROUP_ID||'","'||convert(REC.CS46LASTNAME,'WE8ISO8859P1',
'UTF8')||'","'||
convert(REC.CS46SIGFORMALNAME,'WE8ISO8859P1',
'UTF8')||'"',TRUE);
END LOOP;
UTL_FILE.FCLOSE ( MERONE_FILE ) ;
END IF;
END SAMPLE_MERONE;
Running the following script on a 12.2 AL32UTF8 database reproduces the issue.
declare
cursor c1 is
select lastname
,convert(lastname, 'WE8ISO8859P1','UTF8') convertedname
,dump(convert(lastname, 'WE8ISO8859P1','UTF8')) dumpconvertedname
from (select 'Lt. Jowens Méroné' as lastname
from dual);
merone_file utl_file.file_type;
v_merone_file varchar2(300);
begin
merone_file := utl_file.fopen('NGM1_PAD_IN', 'test.csv', 'w', 32767);
if utl_file.is_open(merone_file)
then
for rec in c1
loop
dbms_output.put_line(rec.lastname ||' - ' ||rec.convertedname);
dbms_output.put_line(rec.dumpconvertedname);
utl_file.put_line(merone_file
,rec.lastname || '","' ||
rec.convertedname || '"'
,true);
end loop;
utl_file.fclose(merone_file);
end if;
end;
/
Screen output:
Lt. Jowens Méroné - Lt. Jowens Méron
Typ=1 Len=17: 76,116,46,32,74,111,119,101,110,115,32,77,233,114,111,110,233
File contents:
Lt. Jowens Méroné","Lt. Jowens Méron"
The conversion changes 195 169 into character 233. You can see it as the last character in the converted string. But somehow does not make it's way into the file.
Exploring the file with a hex editor confirms this.
As a workaround you can assemble all your data into a CLOB and write it as follows. This seems to convert your data correctly.
declare
l_clob clob;
l_string varchar2(32767);
cursor c1 is
select lastname
from (select 'Lt. Jowens Méroné' as lastname
from dual);
begin
dbms_lob.createtemporary(lob_loc => l_clob, cache => true, dur => dbms_lob.call);
dbms_lob.open(lob_loc => l_clob, open_mode => dbms_lob.lob_readwrite);
for rec in c1
loop
l_string := rec.lastname || '","' || rec.lastname || '"' || chr(13) || chr(10);
dbms_lob.writeappend(lob_loc => l_clob, amount => length(l_string), buffer => l_string);
end loop;
dbms_xslprocessor.clob2file(flocation => 'NGM1_PAD_IN'
,fname => 'test2.csv'
,cl => l_clob
,csid => nls_charset_id('WE8ISO8859P1'));
end;
/
I wrote a procedure that will automatically get the data from the given query and put the reuslt data set into a file in directory with delimiters. It works good for smaller queries like select * from table_1 but not for big queries like :
SELECT
i.row_id,
translate(i.x_notes_txt, chr(10)||chr(13)||'|' , ' '),
null
FROM communication i,
contact c
WHERE i.last_upd >= (SELECT to_char(last_updated_dt, 'DD-MON-YYYY')
FROM extract_status
WHERE extract_nm = 'INTN300')
AND i.last_upd < sysdate
AND i.x_interaction_type_cd NOT IN ('XRAC','FMS','ATV','IRL')
AND i.pr_con_id = c.row_id
AND c.x_Prospect_Ind = 'Y';
while calling procedure that accepts query procdure : - tab_to_flat('with_the_above_query') it is showing errors like
1) PLS-00103: Encountered the symbol "|" .
2) Encountered the symbol "),
null
FROM communication i,
And many more ..Can anybody help how to pass these queries as a input????
/* Formatted on 06/06/2013 1:42:56 PM (QP5 v5.163.1008.3004) */
CREATE OR REPLACE FUNCTION tab_to_flat (input_query IN CLOB,
dir_name IN VARCHAR2,
file_name IN VARCHAR2,
seperator IN VARCHAR2)
RETURN NUMBER
IS
c_seperator VARCHAR2 (3) := ' ';
incoming_seperator VARCHAR2 (3) := seperator;
no_of_rows NUMBER;
rec_tab DBMS_SQL.DESC_TAB;
col_cnt INTEGER;
src_id INTEGER DEFAULT DBMS_SQL.open_cursor;
val_varchar VARCHAR2 (32767);
val_num NUMBER;
val_date DATE;
file_input UTL_FILE.file_type;
l_start NUMBER;
row_cnt NUMBER := 0;
BEGIN
l_start := DBMS_UTILITY.get_time;
file_input :=
UTL_FILE.fopen (dir_name,
file_name,
'w',
32767);
DBMS_SQL.parse (src_id, input_query, 1);
DBMS_SQL.describe_columns (src_id, col_cnt, rec_tab);
FOR i IN 1 .. col_cnt
LOOP
CASE (rec_tab (i).col_type)
WHEN 1
THEN
DBMS_SQL.define_column (src_id,
i,
val_varchar,
32767);
WHEN 2
THEN
DBMS_SQL.define_column (src_id, i, val_num);
--when 8 then dbms_sql.define_column_long(src_id,i);
WHEN 12
THEN
DBMS_SQL.define_column (src_id, i, val_date);
ELSE
DBMS_SQL.define_column (src_id,
i,
val_varchar,
32767);
END CASE;
END LOOP;
no_of_rows := DBMS_SQL.execute (src_id);
LOOP
EXIT WHEN (DBMS_SQL.FETCH_ROWS (src_id) <= 0);
c_seperator := ' ';
FOR j IN 1 .. col_cnt
LOOP
CASE (rec_tab (j).col_type)
WHEN 1
THEN
DBMS_SQL.COLUMN_VALUE (src_id, j, val_varchar);
UTL_FILE.put (file_input, c_seperator || val_varchar);
WHEN 2
THEN
DBMS_SQL.COLUMN_VALUE (src_id, j, val_num);
UTL_FILE.put (file_input, c_seperator || val_num);
-- when 8 then dbms_sql.column_value_long(src_id,j,4000,1);
WHEN 12
THEN
DBMS_SQL.COLUMN_VALUE (src_id, j, val_date);
UTL_FILE.put (
file_input,
c_seperator || TO_CHAR (val_date, 'MM/DD/YYYY HH24:MI:SS'));
ELSE
DBMS_SQL.COLUMN_VALUE (src_id, j, val_varchar);
UTL_FILE.put (file_input, c_seperator || val_varchar);
END CASE;
c_seperator := incoming_seperator;
END LOOP;
UTL_FILE.new_line (file_input);
row_cnt := row_cnt + 1;
END LOOP;
DBMS_SQL.close_cursor (src_id);
DBMS_OUTPUT.put_line (
'The execution time is : ' || (DBMS_UTILITY.get_time - l_start));
RETURN row_cnt;
EXCEPTION
WHEN OTHERS
THEN
IF (SQLCODE = -942)
THEN
DBMS_OUTPUT.put_line ('Please check the table_name');
ELSE
RAISE;
END IF;
END;
It looks like you just aren't escaping the quotes that are included in the query string, so you're calling it as
tab_to_flat('SELECT i.row_id, translate(i.x_notes_txt, chr(10)||chr(13)||'|' , ' '),...')
The single quote around the | you're concatenating after the chr(13) is the immediate problem, but there are others. You could either go through and carefully double-up every quote inside the string:
tab_to_flat('SELECT i.row_id, translate(i.x_notes_txt, chr(10)||chr(13)||''|'' , '' ''),...')
... or more readibly use the quoted value syntax:
tab_to_flat(q'[SELECT i.row_id, translate(i.x_notes_txt, chr(10)||chr(13)||'|' , ' '),...]')
... where the q'[ ... ]' enclose your original string and allow you to use single quote marks without having to escape them. You just need to be sure that the actual query doesn't contain [ or ], or pick different delimiters if it does.
This says nothing about whether what you're doing is a good approach and if you can find a better way to approach your problem, and doesn't address SQL injection etc.; this is just to fix the problem with what you're currently calling and how you're doing it.
What you can do is to use am oracle cursor like this :
FUNCTION SELECT_FROM_MY_TABLE(v_QUERY_TO_BE_EXECUTED VARCHAR2)
RETURN SYS_REFCURSOR
IS
c_my_cursor SYS_REFCURSOR;
BEGIN
OPEN c_my_cursor FOR
v_QUERY_TO_BE_EXECUTED -- When working with Ref Cursors, open-for can be used directly, instead of execute immediate.
RETURN c_my_cursor;
END SELECT_FROM_MY_TABLE;
So basically you have a function, that returns a cursor which contains the information from your query. When you use the cursor, you do it like this :
PROCEDURE procedure_use_cursor
IS
c_my_cursor SYS_REFCURSOR;
r_my_table_row my_table%ROWTYPE;
BEGIN
c_my_cursor := SELECT_FROM_MY_TABLE;
LOOP
FETCH c_my_cursor INTO r_my_table_row;
EXIT WHEN c_my_cursor%NOTFOUND ;
-- do what you want with r_my_table_row
END LOOP;
END procedure_use_cursor;
I am writing a procedure to create a CSV file with the data in an Oracle table. I used "spool filename;" but an error is coming. Can I use spool in PL/SQL?
I think that there are better ways to implement this on Oracle 10g/11g, but this should work fine on Oracle 9i or higher:
CREATE OR REPLACE PROCEDURE prc_file_mult_column_generate(
p_file_dir VARCHAR2, -- mandatory (Oracle directory name)
p_file_name VARCHAR2, -- mandatory
p_sql_query VARCHAR2, -- Multiple column SQL SELECT statement that needs to be executed and processed
p_delimiter CHAR -- column delimiter
)
AS
l_cursor_handle INTEGER;
l_dummy NUMBER;
l_col_cnt INTEGER;
l_rec_tab DBMS_SQL.DESC_TAB;
l_current_col NUMBER(16);
l_current_line VARCHAR2(2047);
l_column_value VARCHAR2(300);
l_file_handle UTL_FILE.FILE_TYPE;
l_print_text VARCHAR2(100);
l_record_count NUMBER(16) := 0;
BEGIN
/* Open file for append*/
l_file_handle := UTL_FILE.FOPEN(p_file_dir, p_file_name, 'a', 2047); --Append Mode, 2047 chars per line max, possibly increasable
l_cursor_handle := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(l_cursor_handle, p_sql_query, DBMS_SQL.native);
l_dummy := DBMS_SQL.EXECUTE(l_cursor_handle);
/* Output column names and define them for latter retrieval of data */
DBMS_SQL.DESCRIBE_COLUMNS(l_cursor_handle, l_col_cnt, l_rec_tab); -- get column names
/* Append to file column headers */
l_current_col := l_rec_tab.FIRST;
IF (l_current_col IS NOT NULL) THEN
LOOP
DBMS_SQL.DEFINE_COLUMN(l_cursor_handle, l_current_col, l_column_value, 300);
l_print_text := l_rec_tab(l_current_col).col_name || p_delimiter;
UTL_FILE.PUT (l_file_handle, l_print_text);
l_current_col := l_rec_tab.NEXT(l_current_col);
EXIT WHEN (l_current_col IS NULL);
END LOOP;
END IF;
UTL_FILE.PUT_LINE (l_file_handle,' ');
/* Append data for each row */
LOOP
EXIT WHEN DBMS_SQL.FETCH_ROWS(l_cursor_handle) = 0; -- no more rows to be fetched
l_current_line := '';
/* Append data for each column */
FOR l_current_col IN 1..l_col_cnt LOOP
DBMS_SQL.COLUMN_VALUE (l_cursor_handle, l_current_col, l_column_value);
l_print_text := l_column_value || p_delimiter;
l_current_line := l_current_line || l_column_value || p_delimiter;
END LOOP;
l_record_count := l_record_count + 1;
UTL_FILE.PUT_LINE (l_file_handle, l_current_line);
END LOOP;
UTL_FILE.FCLOSE (l_file_handle);
DBMS_SQL.CLOSE_CURSOR(l_cursor_handle);
EXCEPTION
WHEN OTHERS THEN
-- Release resources
IF DBMS_SQL.IS_OPEN(l_cursor_handle) THEN
DBMS_SQL.CLOSE_CURSOR(l_cursor_handle);
END IF;
IF UTL_FILE.IS_OPEN (l_file_handle) THEN
UTL_FILE.FCLOSE (l_file_handle);
END IF;
--RAISE ;
DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.format_error_stack);
END;
/
spool is a sqlplus command. it cannot be used in pl/sql.
it seems that you have been trying a variety of ways to get oracle to do your formatting and file saving. why not have your program that is calling the proc do this work for you?
If you only need the data in a cvs file you can do this:
create a sql file with the query like this:
set feedback off verify off heading off pagesize 0
select field1 || ',' || field2 ... from table;
quit;
/
then call sqlplus from a terminal like this:
sqlplus -S user/password #file.sql> cvsfile.cvs
No, SPOOL is a SQL Plus command so you would have to do this in SQL Plus:
spool myfile.txt
exec myproc
spool off
You would probably also need to set some values before starting the process e.g.
set pagesize 0 linesize 1000 trimspool on
... to get the correct formatting.
Here are a couple of links you might find helpful:
A PL/SQL Tutorial and SQL*Plus User Guide (11g)
Creating an ascii file: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:459020243348 and http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:68212348056 and http://www.oracle-developer.net/display.php?id=425 .