Bulk data file output PL/SQL - performance

In my PL/SQL function, I m going to output a csv file for 14M million rows. There will be 3 fields in each row.
Im afraid the I/O process running time. I can create and output more than one csv file for this batch. I don want to use line by line output.
What is the best and quick way to writing file. For example, fetching data then filling into a record. And then write the file.
Thanks.

From Oracle 10g onwards, it is possible to write a CLOB to a file with a single call, using the DBMS_XSLPROCESSOR.CLOB2FILE procedure. In the following example, we will prepare a temporary CLOB with our data instead of writing it with UTL_FILE. When all source data has been added to the CLOB, we will write it to a flat-file in a single call.
SQL> DECLARE
2
3 v_file CLOB;
4 v_buffer VARCHAR2(32767);
5 v_name VARCHAR2(128) := 'clob2file_buffered.txt';
6 v_lines PLS_INTEGER := 0;
7 v_eol VARCHAR2(2);
8 v_eollen PLS_INTEGER;
9 c_maxline CONSTANT PLS_INTEGER := 32767;
10
11 BEGIN
12
13 v_eol := CASE
14 WHEN DBMS_UTILITY.PORT_STRING LIKE 'IBMPC%'
15 THEN CHR(13)||CHR(10)
16 ELSE CHR(10)
17 END;
18 v_eollen := LENGTH(v_eol);
19
20 DBMS_LOB.CREATETEMPORARY(v_file, TRUE);
21
22 FOR r IN (SELECT x || ',' || y || ',' || z AS csv
23 FROM source_data)
24 LOOP
25
26 IF LENGTH(v_buffer) + v_eollen + LENGTH(r.csv) <= c_maxline THEN
27 v_buffer := v_buffer || v_eol || r.csv;
28 ELSE
29 IF v_buffer IS NOT NULL THEN
30 DBMS_LOB.WRITEAPPEND(
31 v_file, LENGTH(v_buffer) + v_eollen, v_buffer || v_eol
32 );
33 END IF;
34 v_buffer := r.csv;
35 END IF;
36
37 v_lines := v_lines + 1;
38
39 END LOOP;
40
41 IF LENGTH(v_buffer) > 0 THEN
42 DBMS_LOB.WRITEAPPEND(
43 v_file, LENGTH(v_buffer) + v_eollen, v_buffer || v_eol
44 );
45 END IF;
46
47 DBMS_XSLPROCESSOR.CLOB2FILE(v_file, 'DUMP_DIR', v_name);
48 DBMS_LOB.FREETEMPORARY(v_file);
49
50 DBMS_OUTPUT.PUT_LINE('File='||v_name||'; Lines='||v_lines);
51
52 END;
53 /
File=clob2file_buffered.txt; Lines=1000000
PL/SQL procedure successfully completed.
Elapsed: 00:00:28.65
The CLOB-specific code is highlighted above and is self-explanatory (perhaps with the exception of the end-of-line character assignment on lines 13-17 which is different for Windows. UTL_FILE manages the port-specific end-of-line conversions for us, but with CLOBs we must manage this ourselves).
Of particular interest is the DBMS_XSLPROCESSOR call on line 47, which is our only write operation to the destination flat-file. We can see overall that this technique is similar in performance to our buffered UTL_FILE mechanism (the CLOB method was slightly quicker). We therefore have an alternative method for writing data, but there will be additional costs associated with using CLOBs (for example, temporary tablespace and buffer cache). If the volume of data to be dumped is high, then this method might put too much stress on our temporary tablespace and cause problems for other users (large sort operations, hash joins, global temporary tables etc).
Care should therefore be taken when using this method.

Related

oracle 12 - select string after last occurrence of a character

I am currently using the UPS API to validate addresses and in our system the max length the address line 1 is 35 characters and when its over 35 characters it doesn't update our database because of the length issue.
so what I want to be able to do is if the length of the address is > 35 then
substring only 35 characters but not breaking the flow of the address so for example if the address is
10620 SOUTHERN HIGHLANDS PKWY # 110-811
as you can see for this example "10620 SOUTHERN HIGHLANDS PKWY # 110" is exactly 35 characters but I dont want to break the "110-811" part so I want to go back to the last space and then put the rest of the address on address line 2
I would like it to return
address 1: 10620 SOUTHERN HIGHLANDS PKWY #
address 2: 110-811
Here's one option; read comments within code. As you said that address can have max 2 parts (35 characters in length), function accepts whole address as the 1st parameter and address part (1 or 2) as the 2nd parameter.
SQL> create or replace function f_test (par_string in varchar2, par_part in number)
2 return varchar2
3 is
4 /* This code presumes that PAR_STRING won't be longer than 70 characters
5 */
6 l_str varchar2(35);
7 l_pos number;
8 retval varchar2(35);
9 begin
10 -- if PAR_STRING is up to 35 characters in length, return it as the first part
11 -- of address / null for the second part
12 if length(par_string) <= 35 then
13 if par_part = 1 then
14 retval := par_string;
15 elsif par_part = 2 then
16 retval := null;
17 end if;
18 else
19 -- PAR_STRING is longer than 35 characters - split it in two, depending
20 -- on which part of address you want
21 if par_part = 1 then
22 -- first part: return substring from 1st position up to first space that
23 -- precedes 35th position
24 l_str := substr(par_string, 1, 35);
25 l_pos := instr(l_str, ' ', -1, 1);
26 retval := substr(l_str, 1, l_pos);
27 elsif par_part = 2 then
28 -- second part: return substring from position of the first space that
29 -- precedes 35th position, up to total length of the string
30 l_str := substr(par_string, 1, 35);
31 l_pos := instr(l_str, ' ', -1, 1);
32
33 l_str := substr(par_string, l_pos + 1);
34 retval := l_str;
35 end if;
36 end if;
37
38 return retval;
39 end f_test;
40 /
Function created.
Testing:
SQL> select f_test('10620 SOUTHERN HIGHLANDS PKWY # 110-811', 1) address_1,
2 f_test('10620 SOUTHERN HIGHLANDS PKWY # 110-811', 2) address_2
3 from dual;
ADDRESS_1 ADDRESS_2
---------------------------------------- --------------------
10620 SOUTHERN HIGHLANDS PKWY # 110-811
SQL> select f_test('12345 SHORT ADDRESS # 123-456', 1) address_1,
2 f_test('12345 SHORT ADDRESS # 123-456', 2) address_2
3 from dual;
ADDRESS_1 ADDRESS_2
---------------------------------------- --------------------
12345 SHORT ADDRESS # 123-456
SQL>
Something like this could work:
WITH dat AS
(
SELECT '10620 SOUTHERN HIGHLANDS PKWY # 110-811' AS addr FROM dual
)
SELECT SUBSTR(addr,0,INSTR(SUBSTR(addr,0,35), ' ', -1)) --address line 1
, SUBSTR(addr,INSTR(SUBSTR(addr,0,35), ' ', -1)) --address line 2
FROM dat;
For complicated string manipulation, I would recommend making pl/sql functions, so that you can apply procedural programming.
In general, you cannot rely on the last space for the line break, as the second line may have a number of spaces too.
You will need to split the string in words, and collect words for the first line, not exceeding 35 chars. The remaining words are for line 2. You will need an array, a for-loop, and some if's. We switched to PostgreSQL where you can write functions in a choice of languages, also python, but in Oracle, plain pl/sql will work too.

PL/SQL escape string for string interpolation

I have the following API which takes a clob as one of it's arguments.
Receive_Order_API.Packed_Arrival(clob_,inParam1_,inParam2_);
When I use the debugger in our ERP, the api-call looks like this:
DECLARE
clob_ VARCHAR2(32000) := '!
$HEADER_START=TRUE
$SOURCE_REF1=45963
$SOURCE_REF2=1
$SOURCE_REF3=1
$SOURCE_REF4=
$SOURCE_REF_TYPE_DB=PURCHASE_ORDER
$CONV_FACTOR=1
$CONTRACT=3566
$DESCRIPTION=EX 1000V 3x95mm² AL
$LINE_END=TRUE
$HEADER_END=TRUE
';
inParam1_ VARCHAR2(32000) := '';
inParam2_ VARCHAR2(32000) := 'FALSE';
BEGIN
clob_ := Receive_Order_API.Packed_Arrival(clob_,inParam1_,inParam2_);
END;
What I am trying to do is to replace the data with my own values fetched from another table, something like this:
$SOURCE_REF1=:someOtherValue
$SOURCE_REF2=1
$SOURCE_REF3=1
$SOURCE_REF4=
$SOURCE_REF_TYPE_DB=PURCHASE_ORDER
$CONV_FACTOR=1
$CONTRACT=:someOtherValue
I guess somehow I need to escape the string to be able to replace the values but I can't figure out how and have been stuck on this for a while now.
Any help would be much appreciated!
Something like this?
SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
2 clob_ VARCHAR2 (32000) := '!
3 $HEADER_START=TRUE
4 $SOURCE_REF1=45963
5 $SOURCE_REF2=1
6 $SOURCE_REF3=1
7 $SOURCE_REF4=
8 $SOURCE_REF_TYPE_DB=PURCHASE_ORDER
9 $CONV_FACTOR=1
10 $CONTRACT=3566
11 $DESCRIPTION=EX 1000V 3x95mm² AL
12 $LINE_END=TRUE
13 $HEADER_END=TRUE
14 ';
15
16 inParam1_ VARCHAR2 (32000) := '';
17 inParam2_ VARCHAR2 (32000) := 'FALSE';
18
19 l_source_ref1 NUMBER;
20 l_contract NUMBER;
21 BEGIN
22 SELECT 11111, 22222
23 INTO l_source_ref1, l_contract
24 FROM DUAL;
25
26 clob_ :=
27 REGEXP_REPLACE (clob_,
28 '\$SOURCE_REF1=\d+', --> escape $
29 '$SOURCE_REF1=' || l_source_ref1);
30 clob_ :=
31 REGEXP_REPLACE (clob_, '\$CONTRACT=\d+', --> escape $
32 '$CONTRACT=' || l_contract);
33
34 DBMS_OUTPUT.put_line (clob_);
35 END;
36 /
Result:
!
$HEADER_START=TRUE
$SOURCE_REF1=11111 --> new value here
$SOURCE_REF2=1
$SOURCE_REF3=1
$SOURCE_RE
F4=
$SOURCE_REF_TYPE_DB=PURCHASE_ORDER
$CONV_FACTOR=1
$CONTRACT=22222 --> and here
$DESCRIPTI
ON=EX 1000V 3x95mm2 AL
$LINE_END=TRUE
$HEADER_END=TRUE
PL/SQL procedure successfully completed.
SQL>

declaring cursor in compound trigger pl/sql

When I ran this code, I've got this error:
PLS-00103: Encountered the symbol "IS" when expecting one of the following:
:= . ( # % ; not null range default character.
I think something wrong with declaration section but the same declaration works in a row-lvl trigger, so I'm confused is this permitted in compound triggers at all. I couldn't find the examples, so I ask you here.
create or replace trigger ivan_moving
for update of country on clients
compound trigger
declare
n_country clients.country%type;
min_date clients.bday%type;
cursor ivans is select* from clients where fname like 'Ivan%' and Bday > (select min(Bday) from clients where fname like 'Ivan%');
client_ivan ivans%rowtype;
before statement is
min_date = select Bday from clients where fname like 'Ivan%' and Bday = (select min(Bday) from clients where fname like 'Ivan%');
end before statement;
after each row is
begin
if :OLD.country <> :NEW.country and fname = 'Ivan%' and bday = min_date
n_country := :new.country;
end if;
end after each row;
after statement is
begin
open ivans;
loop
fetch ivans into client_ivan;
exit when ivans%notfound;
update clients set country = n_country where id = client_ivan.id;
end loop;
close ivans;
end after statement;
end ivan_moving;
/
Quite a few mistakes, e.g.
you shouldn't use declare
in PL/SQL, SELECT requires INTO
it is not = 'Ivan%' but like 'Ivan%'
it seems you "forgot" to specify :new for some columns
There might be some other errors I didn't mention. I don't know if trigger does what you intended, but - at least, syntax errors are now fixed.
SQL> create or replace trigger ivan_moving
2 for update of country on clients
3 compound trigger
4 n_country clients.country%type;
5 min_date clients.bday%type;
6 cursor ivans is
7 select * from clients
8 where fname like 'Ivan%'
9 and bday > min_date;
10 client_ivan ivans%rowtype;
11
12 before statement is
13 begin
14 select bday
15 into min_date
16 from clients
17 where fname like 'Ivan%'
18 and bday = (select min(bday)
19 from clients
20 where fname like 'Ivan%');
21 end before statement;
22
23 after each row is
24 begin
25 if :old.country <> :new.country
26 and :new.fname like 'Ivan%'
27 and :new.bday = min_date
28 then
29 n_country := :new.country;
30 end if;
31 end after each row;
32
33 after statement is
34 begin
35 open ivans;
36 loop
37 fetch ivans into client_ivan;
38 exit when ivans%notfound;
39 update clients set country = n_country where id = client_ivan.id;
40 end loop;
41 close ivans;
42 end after statement;
43
44 end ivan_moving;
45 /
Trigger created.

ORACLE use custom tableObject into a function

I want insert a cursor into my custom tableObject, but it is not always found.
My RECORD:
create or replace type "RECORDRANKING" as object
(
-- Attributes
COL1 NUMBER,
COL2 VARCHAR(50),
COL3 NUMBER
-- Member functions and procedures
-- member procedure <ProcedureName>(<Parameter> <Datatype>)
)
This is object table:
CREATE OR REPLACE TYPE "TBRANKING" AS TABLE OF RECORDRANKING;
Now I go into creating a function (into a package):
CREATE OR REPLACE PACKAGE BODY PKLBOTTONI as
FUNCTION testlb
(
p_gapup IN NUMBER,
p_gadown IN NUMBER
)
RETURN SYS_REFCURSOR IS
cursor_ranking SYS_REFCURSOR;
position NUMBER ;
gap_ranking TBRANKING;
gaprecord RECORDRANKING;
upgap NUMBER;
downgap NUMBER;
BEGIN
select * into cursor_ranking from(
select pkranking.getRanking( 100 ) from dual);
LOOP
FETCH cursor_ranking INTO gap_ranking;
EXIT WHEN cursor_ranking%NOTFOUND;
INSERT INTO gap_ranking (COL1,COL2,COL3)
VALUES
(cursor_ranking.C1,
cursor_ranking.C2,
cursor_ranking.C3);
END LOOP;
return gap_ranking;
END;
END PKLBOTTONI;
I always get:
Compilation errors for PACKAGE BODY PKLBOTTONI
Error: PL/SQL: ORA-00942: table or view does not exist
Line: 32
Text: INSERT INTO gap_ranking
In the loop you're both fetching into "gap_ranking" and then trying to "insert into" it again. Insert into a collection is not a valid syntax. Fetch is ok, either in a loop or using bulk collect to fetch multiple records at once.
From your excerpt it doesn't look like you have a physical database table, so the way to do it in PL/SQL would be something like below.
For more help using collections you can check Oracle docs below too:
http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/collections.htm
SQL> set serveroutput on
SQL> declare
2 gap_ranking tbranking := tbranking(null); -- initialize
3 begin
4 gap_ranking.delete; -- clear empty record
5 for cur in
6 (select level as i from dual connect by level <= 5)
7 loop
8 -- insert empty
9 gap_ranking.extend;
10 -- attribute values
11 gap_ranking(gap_ranking.last) := recordranking(1000 + cur.i, 'TEST' || cur.i, 10 + cur.i);
12 end loop;
13 -- loop to print - just to illustrate
14 for j in gap_ranking.first .. gap_ranking.last
15 loop
16 dbms_output.put_line(gap_ranking(j).col1 || ',' ||
17 gap_ranking(j).col2 || ',' ||
18 gap_ranking(j).col3);
19
20 end loop;
21 -- same as...
22 for j in 1 .. gap_ranking.count
23 loop
24 dbms_output.put_line(gap_ranking(j).col1 || ',' ||
25 gap_ranking(j).col2 || ',' ||
26 gap_ranking(j).col3);
27
28 end loop;
29 end;
30 /
1001,TEST1,11
1002,TEST2,12
1003,TEST3,13
1004,TEST4,14
1005,TEST5,15
1001,TEST1,11
1002,TEST2,12
1003,TEST3,13
1004,TEST4,14
1005,TEST5,15
PL/SQL procedure successfully completed
SQL>

DELETE Collection Method in oracle work wrong

I want to use DELETE Collection Method to delete some elements in collection
such as:
create or replace procedure testloop3 (clearaaa out nestedtable) as
type nestedtable is table of varchar2(255);
reply_ref_messageIds nestedtable;
getDelete_messageIds nestedtable;
begin
select distinct r.messagebox_id bulk collect into reply_ref_messageIds from reply r;
select m.id bulk collect into getDelete_messageIds from messagebox m;
getDelete_messageIds.delete(2);
getDelete_messageIds.delete(4);
getDelete_messageIds.delete(7);
getDelete_messageIds.delete(11);
getDelete_messageIds.delete(13);
for i in getDelete_messageIds.FIRST .. getDelete_messageIds.LAST loop
dbms_output.put_line(i);
end loop;
for i in 5 .. 12 loop
dbms_output.put_line(i);
end loop;
end;
and then I debug this procedure with plsql dev
-- Created on 2013/4/4 by THINKPAD
declare
-- Local variables here
aa nestedtable;
begin
-- Test statements here
testloop3(aa);
end;
and I get the indexes of getDelete_messageIds before remove which are 1 to 15.
However: when I debug to getDelete_messageIds.delete(2); it removes index 1 and 2...I can't explain why.
And then when I debug next statement getDelete_messageIds.delete(4); it removes index 3 and 4. And then getDelete_messageIds.delete(7); only removes index 7...
I can't understand...
your procedure, as posted, is showing nothing of the sort. you are simply looping around like
for idx in 1..15 loop
(.FIRST will resolve to 1 and .LAST will resolve to 15). it does not mean there are still 15 elements in the NT.
you are not checking to see if the indexes are deleted. I think you're confused about the proper way to loop through a nested table where there are gaps.
i.e. you can see the elements are deleted:
SQL> create table messagebox(id ) as select to_char(rownum) from dual connect by level <= 15;
Table created.
SQL> create or replace procedure testloop3
2 as
3 type nestedtable is table of varchar2(255);
4 getDelete_messageIds nestedtable;
5 v_idx number;
6 begin
7 select m.id bulk collect into getDelete_messageIds from messagebox m;
8 getDelete_messageIds.delete(2);
9 getDelete_messageIds.delete(4);
10 getDelete_messageIds.delete(7);
11 getDelete_messageIds.delete(11);
12 getDelete_messageIds.delete(13);
13 v_idx := getDelete_messageIds.first;
14 while v_idx is not null
15 loop
16 dbms_output.put_line(v_idx);
17 v_idx := getDelete_messageIds.next(v_idx);
18 end loop;
19 end;
20 /
Procedure created.
SQL> exec testloop3
1
3
5
6
8
9
10
12
14
15
so 2, 4, 7, 11, 13 are deleted. exactly as expected.
you can see
DECLARE
TYPE NumList IS TABLE OF NUMBER;
n NumList := NumList(1,3,5,7);
counter INTEGER;
BEGIN
DBMS_OUTPUT.PUT_LINE('N''s first subscript is ' || n.FIRST);
DBMS_OUTPUT.PUT_LINE('N''s last subscript is ' || n.LAST);
-- When the subscripts are consecutive starting at 1,
-- it's simple to loop through them.
FOR i IN n.FIRST .. n.LAST
LOOP
DBMS_OUTPUT.PUT_LINE('Element #' || i || ' = ' || n(i));
END LOOP;
n.DELETE(2); -- Delete second element.
-- When the subscripts have gaps
-- or the collection might be uninitialized,
-- the loop logic is more extensive.
-- Start at the first element
-- and look for the next element until there are no more.
IF n IS NOT NULL THEN
counter := n.FIRST;
WHILE counter IS NOT NULL
LOOP
DBMS_OUTPUT.PUT_LINE
('Element #' || counter || ' = ' || n(counter));
counter := n.NEXT(counter);
END LOOP;
ELSE
DBMS_OUTPUT.PUT_LINE('N is null, nothing to do.');
END IF;
END;
reference Finding the First or Last Collection Element

Resources