oracle 12 - select string after last occurrence of a character - oracle

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.

Related

Compare two value in Oracle Procedure

i'm very noob at that, can you help me? I have to see if cash>=price and cash is taken from "portafoglio" from "studente" table and price is taken from "costo" from "corso" table but it gave me several error
CREATE OR REPLACE FUNCTION PAGA_CORSO
(
MAT IN NUMBER
, COR IN NUMBER
, DAT IN DATE
, cash DOUBLE PRECISION
, price DOUBLE PRECISION
) RETURN BOOLEAN AS
BEGIN
SELECT portafoglio
INTO cash
FROM studente
WHERE matricola=MAT;
SELECT costo
INTO price
FROM corso
WHERE codicecorso=COR;
IF cash >= price THEN
RETURN FALSE;
ELSE
RETURN TRUE;
END IF;
END PAGA_CORSO;
Thanks!
You are missing OUT clauses, because you want to return values "out" of the function,
SQL> CREATE OR REPLACE FUNCTION PAGA_CORSO
2 (
3 MAT IN NUMBER
4 , COR IN NUMBER
5 , DAT IN DATE
6 , cash DOUBLE PRECISION
7 , price DOUBLE PRECISION
8 ) RETURN BOOLEAN AS
9 BEGIN
10 SELECT sal
11 INTO cash
12 FROM emp
13 WHERE empno=MAT;
14
15 SELECT deptno
16 INTO price
17 FROM dept
18 WHERE deptno=COR;
19
20 IF cash >= price THEN
21 RETURN FALSE;
22 ELSE
23 RETURN TRUE;
24 END IF;
25 END PAGA_CORSO;
26 /
Warning: Function created with compilation errors.
SQL>
SQL> sho err
Errors for FUNCTION PAGA_CORSO:
LINE/COL ERROR
-------- -----------------------------------------------------------------
10/5 PL/SQL: SQL Statement ignored
11/10 PLS-00403: expression 'CASH' cannot be used as an INTO-target of
a SELECT/FETCH statement
12/5 PL/SQL: ORA-00904: : invalid identifier
15/5 PL/SQL: SQL Statement ignored
16/10 PLS-00403: expression 'PRICE' cannot be used as an INTO-target of
a SELECT/FETCH statement
17/5 PL/SQL: ORA-00904: : invalid identifier
Now we add OUT to the definition
SQL> CREATE OR REPLACE FUNCTION PAGA_CORSO
2 (
3 MAT IN NUMBER
4 , COR IN NUMBER
5 , DAT IN DATE
6 , cash out DOUBLE PRECISION
7 , price out DOUBLE PRECISION
8 ) RETURN BOOLEAN AS
9 BEGIN
10 SELECT sal
11 INTO cash
12 FROM emp
13 WHERE empno=MAT;
14
15 SELECT deptno
16 INTO price
17 FROM dept
18 WHERE deptno=COR;
19
20 IF cash >= price THEN
21 RETURN FALSE;
22 ELSE
23 RETURN TRUE;
24 END IF;
25 END PAGA_CORSO;
26 /
Function created.
Also, "double precision" is rare - I'd suggest you probably just want to go with NUMBER.

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.

Bulk data file output PL/SQL

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.

Compare values in each column of two Oracle Types

I've been playing around with the pluto-test-framework today, and I'd like to get some existing functions into a test harness with it.
I have lots of functions with this type of specification.
FUNCTION DO_SOME_STUFF (pOldSchedule IN SCHEDULE_OBJ,
pNewSchedule OUT SCHEDULE_OBJ,
pLoggerContext IN OUT LOGGER_CONTEXT_OBJ)
RETURN NUMBER;
It takes pOldSchedule, does some stuff to it, and then returns pNewSchedule. The logger_context just does logging.
As part of a test, I'd like to be able to compare the values in each of the columns of the type, without having to write individual IF statements.
It'll need to return boolean to signify whether or not pOldSchedule and pNewSchedule match.
Any ideas?
Straightforward equality tests work with nested tables:
SQL> declare
2 type nt is table of number;
3 nt1 nt;
4 nt2 nt;
5 nt3 nt;
6 begin
7 nt1 := nt(1,2,3);
8 nt2 := nt(1,2,3);
9 if nt1 = nt2 then
10 dbms_output.put_line('NT2 is the same nested table as NT1');
11 else
12 dbms_output.put_line('NT2 is a different nested table from NT1');
13 end if;
14 nt2 := nt(1,2,3,4);
15 if nt1 = nt3 then
16 dbms_output.put_line('NT3 is the same nested table as NT1');
17 else
18 dbms_output.put_line('E3 is a different nested table from NT1');
19 end if;
20 end;
21 /
NT2 is the same nested table as NT1
E3 is a different nested table from NT1
PL/SQL procedure successfully completed.
SQL>
However the same is not true of full-on objects:
SQL> create or replace type new_emp as object (
2 ename varchar2(10)
3 , sal number
4 , deptno number
5 , job varchar2(10))
6 /
Type created.
SQL> declare
2 e1 new_emp;
3 e2 new_emp;
4 begin
5 e1 := new_emp('KESTELYN', 3700, 30, 'MARKETING');
6 e2 := new_emp('KESTELYN', 3700, 30, 'MARKETING');
7 if e1 = e2 then
8 dbms_output.put_line('E2 is the same as E1');
9 else
10 dbms_output.put_line('E2 is different from E1');
11 end if;
12 end;
13 /
if e1 = e2 then
*
ERROR at line 7:
ORA-06550: line 7, column 11:
PLS-00526: A MAP or ORDER function is required for comparing objects in PL/SQL.
SQL>
We need to explicitly define a member function for executing comparisons. So here is the same object with a MAP function. The example implementation generates a hashed string, which is useful if we want to store the value for later comparison, but it could just return the concatenated string (especially as EXECUTE on DBMS_CRYPTO is not granted by default). The NVL() functions are necessary to avoid (null, value) and (value, null) being evaluated as equal. There is the always a risk when using magic values, so we need to choose them carefully.
SQL> create or replace type new_emp as object (
2 ename varchar2(10)
3 , sal number
4 , deptno number
5 , job varchar2(10)
6 , map member function equals return raw)
7 /
Type created.
SQL> create or replace type body new_emp as
2 map member function equals return raw
3 is
4 begin
5 return dbms_crypto.hash(
6 utl_raw.cast_to_raw(nvl(self.ename,'***')||
7 nvl(self.sal,-99)||
8 nvl(self.deptno,-99)||
9 nvl(self.job,'***')
10 )
11 , 1);
12 end equals;
13 end;
14 /
Type body created.
SQL>
Now we have a basis for comparing instances of our objects:
SQL> declare
2 e1 new_emp;
3 e2 new_emp;
4 begin
5 e1 := new_emp('KESTELYN', 3700, 30, 'MARKETING');
6 e2 := new_emp('KESTELYN', 3700, 30, 'MARKETING');
7 if e1 = e2 then
8 dbms_output.put_line('E2 is the same as E1');
9 else
10 dbms_output.put_line('E2 is different from E1');
11 end if;
12 end;
13 /
E2 is the same as E1
PL/SQL procedure successfully completed.
SQL>
You might be wondering why Oracle doesn't do this by default. Well, the TYPE implemntation only allows one comparison method (if we have a MAP function we cannot have an ORDER function) so we need to have the capability to choose our own definition of equality. For instance, a type called rectangle might have a MAP function called area() which returns self.width * self.length.

Resources