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;
/
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,
Im trying to create a procedure which takes sys refcursor as in out parameter and modifies it based on the logic explained in comments in the below code
TYPE t_params IS
TABLE OF VARCHAR2(32767 CHAR);
/
CREATE OR REPLACE PROCEDURE modify_cursor (
p_cursor IN OUT SYS_REFCURSOR,
p_array_binary IN t_params,
p_values IN t_params
)
/*
p_cursor IN OUT SYS_REFCURSOR
-- contains a single row {empId:123, ename:"king", mgr:"Porter",deptNo:200}
p_array_binary IN t_params
-- contains one binary value corresponding to each column in above cursor ["1","0","1","1"]
p_values IN t_params
-- contains one binary value corresponding to each column in above cursor ["123","king2","new manager","200"]
*/
IS
BEGIN
/*
Based on p_array_binary
if binary value 0 then take cursor should retain value as it is fro corresponding column
if binary value 1 then cusrsor should have the correspondoing column value from p_values
In short, the out cursor should be {empId:123, ename:"king", mgr:"new manager", deptNo:200}
*/
END;
/
Any help in this regard will be highly appreciated.
If you knew the ref cursor structure - it was always four columns of the data types shown - then this would be relatively simple:
CREATE OR REPLACE PROCEDURE modify_cursor (
p_cursor IN OUT SYS_REFCURSOR,
p_array_binary IN t_params,
p_values IN t_params
)
IS
l_empid number;
l_ename varchar2(30);
l_mgr varchar2(30);
l_deptNo number;
BEGIN
-- get original values into local variables
fetch p_cursor into l_empId, l_ename, l_mgr, l_deptNo;
-- re-open cursor using either local variables of p_values depending on p_binary flag
open p_cursor for
select
case when p_array_binary(1) = '1' then to_number(p_values(1)) else l_empId end as empId,
case when p_array_binary(2) = '1' then p_values(2) else l_ename end as ename,
case when p_array_binary(3) = '1' then p_values(3) else l_mgr end as mgr,
case when p_array_binary(4) = '1' then to_number(p_values(4)) else l_deptNo end as deptNo
from dual;
END;
/
Demo using your sample data, via SQL*Plus/SQL Developer/SQLcl bind variables:
var rc refcursor;
begin
open :rc for
select 123 as empId, 'king' as ename, 'Porter' as mgr, 200 as deptNo
from dual;
modify_cursor(:rc, t_params('1', '0', '1', '1'), t_params('123', 'king2', 'new manager', '200'));
end;
/
print rc
EMPID ENAME MGR DEPTNO
---------- -------------------------------- -------------------------------- ----------
123 king new manager 200
db<>fiddle
Since you don't know the structure in advance, you will have to use dynamic SQL, which is bit more complicated. Here's an outline:
CREATE OR REPLACE PROCEDURE modify_cursor (
p_cursor IN OUT SYS_REFCURSOR,
p_array_binary IN t_params,
p_values IN t_params
)
IS
l_c integer;
l_col_cnt integer;
l_desc_t dbms_sql.desc_tab3;
l_varchar2 varchar2(32767 char);
l_values t_params := new t_params();
l_result integer;
BEGIN
-- convert ref cursor to dbms_sql cursor
l_c := dbms_sql.to_cursor_number(rc => p_cursor);
-- analyse the cursor (columns, data types)
dbms_sql.describe_columns3(c => l_c, col_cnt => l_col_cnt, desc_t => l_desc_t);
-- optionally check l_col_cnt matches sise of t_params arguments?
l_values.extend(l_col_cnt);
-- define each column for fetch; here you're treating everything as strings,
-- which will cause issues with some other data types
for i in 1..l_col_cnt loop
dbms_sql.define_column(c => l_c, position => i, column => l_varchar2, column_size => 32767);
end loop;
-- fetch original values - only one row to worry about so no loop
l_result := dbms_sql.fetch_rows(c => l_c);
for i in 1..l_col_cnt loop
-- depending on p_array_binary, set l_values from either fetched data or p_values
if p_array_binary(i) = '1' then
l_values(i) := p_values(i);
else
-- this forces everything to varchar2, which is OK (ish) for your sample data;
-- if you have other data types e.g. dates then you will probably want type-specific
-- handling so you can control the conversions - which affects this, define_column
-- and the final cursor to retrieve the values. But you have the same issue with p_values.
dbms_sql.column_value(c => l_c, position => i, value => l_values(i));
end if;
end loop;
-- finished with original cursor, so close it
dbms_sql.close_cursor(c => l_c);
-- re-open ref cursor using l_values data, with another dynamic SQL statement
l_varchar2 := 'select ';
for i in 1..l_col_cnt loop
if i > 1 then
l_varchar2 := l_varchar2 || ', ';
end if;
if l_desc_t(i).col_type = 2 then
l_varchar2 := l_varchar2 || l_values(i);
else
l_varchar2 := l_varchar2 || '''' || l_values(i) || '''';
end if;
l_varchar2 := l_varchar2 || ' as "' || l_desc_t(i).col_name || '"';
end loop;
l_varchar2 := l_varchar2 || ' from dual';
open p_cursor for l_varchar2;
END;
/
Running exactly the same demo block gives:
EMPID ENAM MGR DEPTNO
---------- ---- ----------- ----------
123 king new manager 200
db<>fiddle
You can add handling for other data types if needed, error handling etc.
Read more about dbms_sql.
I have a pl/sql query and I want it's output to be sent in email in CSV format straightaway. I have no directory to first create and save a CSV file and then pick it up to send as an attachment.
Please help with your inputs as I am not able to get away.
Regards,
Sachin
Finally figured out a solution with the help of pointers received and providing the same to further help in case someone else needs in future.
My problem was that I was mostly seeing the examples where i could either save the file on a directory or pick the file from a directory to send as an attchment but I had no provision of directory and I wanted query result to be put in CSV and sent in email dynamically. So here is the complete solution.
CREATE OR REPLACE PROCEDURE SEND_CSV_ATTACHMENT AS
v_sender VARCHAR2(130);
v_recipients VARCHAR2(4000);
v_cc VARCHAR2(4000);
v_bcc VARCHAR2(2000);
v_subj VARCHAR2(200);
v_msg CLOB;
v_mime VARCHAR2(40);
v_tbl VARCHAR2(20000);
c_cr_lf CONSTANT CHAR (2) := (CHR (13) || CHR (10)); -- Carriage Return/Line Feed characters for formatting text emails
v_loop_count PLS_INTEGER := 0;
v_attachment CLOB;
v_block_qry VARCHAR2(3000);
v_block_row VARCHAR2(6000);
TYPE bl_cur IS REF CURSOR;
v_result bl_cur;
v_rowcount NUMBER;
errMsg VARCHAR2(15000);
BEGIN
v_sender := 'somesender#xyzcommunications.com';
SELECT NVL(EMAIL_LIST, 'someone#abcd.com')
FROM
(
SELECT LISTAGG(EMAIL_ID, ',') WITHIN GROUP (ORDER BY EMAIL_ID) AS EMAIL_LIST FROM RECIPEINTS_TABLE WHERE SEND_TO = 1 AND IS_ACTIVE = 1
);
SELECT NVL(EMAIL_LIST, 'someone#abcd.com')
FROM
(
SELECT LISTAGG(EMAIL_ID, ',') WITHIN GROUP (ORDER BY EMAIL_ID) AS EMAIL_LIST FROM RECIPEINTS_TABLE WHERE SEND_CC = 1 AND IS_ACTIVE = 1
);
v_bcc := 'someone#abcd.com';
-- Generate attachment - Begin
v_attachment := '"COL1", "COL2"' || CHR(13) || CHR(10);
v_block_qry := 'SELECT ''"'' || COL1 || ''", "'' || COL2 || ''"'' AS ROWTXT
FROM MY_TABLE';
OPEN v_result FOR v_block_qry;
LOOP
v_rowcount := v_result%ROWCOUNT;
FETCH v_result INTO v_block_row;
EXIT WHEN v_result%NOTFOUND;
v_attachment := v_attachment || v_block_row || chr(13) || chr(10);
END LOOP;
CLOSE v_result;
-- Generate attachment - End
v_subj:= 'MAIL_SUBJECT ' || TO_CHAR(TRUNC(SYSDATE-1), 'YYYY-MM-DD');
UTL_MAIL.send_attach_varchar2(sender => v_sender,
recipients => v_recipients,
cc => v_cc,
bcc => v_bcc,
subject => v_subj,
message => v_msg,
mime_type => 'text/html; charset=us-ascii', -- send html e-mail
attachment => v_attachment,
att_inline => FALSE,
att_filename => 'Change_Report' || TO_CHAR(TRUNC(SYSDATE-1), 'YYYY-MM-DD') || '.csv');
EXCEPTION
WHEN OTHERS THEN
errMsg := SQLERRM;
SEND_MAIL_HTML ('someone#abcd.com', NULL, NULL, errMsg, 'SEND_MAIL ERROR: ' || errMsg);
END SEND_CSV_ATTACHMENT;
You may create such a procedure :
create or replace procedure prFileSend is
v_mail_owner varchar2(100):='myname#someComp.com';
v_url varchar2(4000);
v_rep varchar2(4000);
delimiter varchar2(1) := chr(38);
begin
for c in ( select * from myTable )
loop
begin
v_url := 'http://www.mycompany.com/einfo/default.aspx?email='||c.email || delimiter || 'p1=' || c.col1 || delimiter ||'p2='||c.col2;
v_rep := utl_http.request(utl_url.escape(v_url, false,'ISO-8859-9'));
end;
end loop;
exception
when others then
prErrorMsgSend(v_mail_owner,'Error : ' || sqlerrm); -- a function like this one which sends an error message back to you.
end;
and create a scheduler job
begin
dbms_scheduler.create_job (
job_name => 'jbFileSend ',
job_type => 'STORED_PROCEDURE',
job_action => 'prFileSend',
start_date => '22-jan-2018 09:00:00 am',
repeat_interval => 'FREQ=DAILY; INTERVAL=1',
comments => 'Sending Every day'
enabled => true);
end;
working every day as an example.
I am trying to take a comma delimited string and insert each value as a new row into a table. I have taken the below example from Lalit Kumar B and modified the data to resemble what my data will look like.
DECLARE
L_INPUT VARCHAR2(4000) := '522,33-23,125,658,25,12-500';
L_COUNT BINARY_INTEGER;
L_ARRAY DBMS_UTILITY.LNAME_ARRAY;
BEGIN
DBMS_UTILITY.COMMA_TO_TABLE(LIST => REGEXP_REPLACE(L_INPUT, '(^|,)', '\1x'), TABLEN => L_COUNT, TAB => L_ARRAY);
DBMS_OUTPUT.PUT_LINE(L_COUNT);
FOR I IN 1 .. L_COUNT
LOOP
DBMS_OUTPUT.PUT_LINE('Element ' || TO_CHAR(I) || ' of array contains: ' || SUBSTR(L_ARRAY(I), 2));
INSERT INTO TEST22 VALUES
(SUBSTR(L_ARRAY(I), 2)
);
COMMIT;
END LOOP;
END;
I am receiving the following oracle error: ORA-20001: comma-separated list invalid near 33-23
What can i do to handle data of the form "33-23"? If I take the '-' out of my data the above will run as desired. This is not ideal as some of my data will have '-' in it and it cannot be removed.
One way is to use CONNECT BY to effectively loop through the string elements. If you run just the query you'll see how this works. The regular expression allows for NULL list elements should they occur.
insert into TEST(col_a)
select regexp_substr('522,33-23,125,658,25,12-500', '(.*?)(,|$)', 1, level, null, 1)
from dual
connect by level <= regexp_count('522,33-23,125,658,25,12-500', ',')+1
I had same problem with DBMS_UTILITY.COMMA_TO_TABLE. It has some bugs with numeric strings. I tried some methods and finally write this function instead of it.
CREATE OR REPLACE PACKAGE UTILITY_METHODS IS
TYPE STRING_TAB IS TABLE OF VARCHAR2(512) INDEX BY BINARY_INTEGER;
FUNCTION SPLIT_STR( P_STRING IN VARCHAR2
, P_SEPRATOR_CHAR IN VARCHAR2)
RETURN STRING_TAB;
END UTILITY_METHODS;
CREATE OR REPLACE PACKAGE BODY UTILITY_METHODS IS
FUNCTION SPLIT_STR( P_STRING IN VARCHAR2
, P_SEPRATOR_CHAR IN VARCHAR2)
RETURN STRING_TAB
IS
STR_TAB STRING_TAB;
L_SEP_CHAR VARCHAR2(1) := NVL(P_SEPRATOR_CHAR, ',');
L_PATERN VARCHAR2(10) := '[^' || L_SEP_CHAR || ']+';
BEGIN
IF P_STRING IS NULL THEN
RETURN STR_TAB;
END IF;
FOR RC IN (
WITH L_LINE(STR) AS
(
SELECT P_STRING
FROM DUAL
)
SELECT REGEXP_SUBSTR(STR, L_PATERN, 1, LEVEL) SP_STR
FROM L_LINE
CONNECT BY LEVEL <= REGEXP_COUNT(STR, L_SEP_CHAR) + 1
)
LOOP
STR_TAB(STR_TAB.COUNT) := RC.SP_STR;
END LOOP;
RETURN STR_TAB;
END;
END UTILITY_METHODS;
If you want use this function in a select statement you can change the return type of function to PIPE_LINED.
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).