How to write a PL/SQL program to separate the code into its two parts as shown in the following example: if the input is ABC031, the output should be:
Product Name is: ABC
Serial Number is: 031
Hints: Use the following functions if needed:
instr( string1, string2 [, start_position [, nth_appearance ] ] )
substr( string, start_position, [ length ] )
length( string )
DBMS_OUTPUT.PUT_LINE( )
Here are two examples, see if any of these helps.
SQL> set serveroutput on
SQL>
SQL> create or replace procedure p_test (par_input in varchar2)
2 is
3 l_product_name varchar2(10);
4 l_serial_number varchar2(10);
5 begin
6 -- first example
7 l_product_name := substr(par_input, 1, 3);
8 l_serial_number := substr(par_input, 4);
9
10 dbms_output.put_line('First example : ' || l_product_name ||', '|| l_serial_number);
11
12 -- second example, possibly better as it splits letters from digits
13 l_product_name := regexp_substr(par_input, '^[[:alpha:]]+');
14 l_serial_number := regexp_substr(par_input, '[[:digit:]]+$');
15
16 dbms_output.put_line('Second example: ' || l_product_name ||', '|| l_serial_number);
17 end;
18 /
Procedure created.
SQL> begin
2 p_test('ABC031');
3 end;
4 /
First example : ABC, 031
Second example: ABC, 031
PL/SQL procedure successfully completed.
SQL>
Related
How can I display a string, separating each letter by a dash with a for loop?
For example i want to display:
h-e-l-l-o-w-o-r-l-d
I tried with the substr function but I can't get it out
If it must be PL/SQL and FOR loop, then you could
SQL> set serveroutput on
SQL> declare
2 l_str varchar2(20) := 'helloworld';
3 retval varchar2(50);
4 begin
5 for i in 1 .. length(l_str) loop
6 retval := retval || substr(l_str, i, 1) ||'-';
7 end loop;
8 retval := rtrim(retval, '-');
9 dbms_output.put_line(retval);
10 end;
11 /
h-e-l-l-o-w-o-r-l-d
PL/SQL procedure successfully completed.
SQL>
Otherwise, consider e.g.
SQL> select rtrim(regexp_replace('helloworld', '(.)', '\1-'), '-') result from dual;
RESULT
-------------------
h-e-l-l-o-w-o-r-l-d
SQL>
or
SQL> select listagg(substr('helloworld', level, 1), '-') within group (order by level) result
2 from dual
3 connect by level <= length('helloworld');
RESULT
--------------------------------------------------------------------------------
h-e-l-l-o-w-o-r-l-d
SQL>
You can just output each successive character and, after the first, output a hyphen between them:
DECLARE
string VARCHAR2(20) := 'helloworld';
BEGIN
DBMS_OUTPUT.PUT(SUBSTR(string, 1, 1));
FOR i IN 2 .. LENGTH(string)
LOOP
DBMS_OUTPUT.PUT('-');
DBMS_OUTPUT.PUT(SUBSTR(string, i, 1));
END LOOP;
DBMS_OUTPUT.NEW_LINE();
END;
/
Which outputs:
h-e-l-l-o-w-o-r-l-d
If you want to do it without loops then you can use:
DECLARE
string VARCHAR2(20) := 'helloworld';
BEGIN
DBMS_OUTPUT.PUT_LINE( SUBSTR(REGEXP_REPLACE(string, '(.)', '-\1'), 2) );
END;
/
You should not use LTRIM (or RTRIM) to remove the hyphens as, if the input string has leading (or trailing) hyphens then these would be removed from the output and that would be erroneous.
For example:
DECLARE
string VARCHAR2(20) := '--helloworld--';
BEGIN
DBMS_OUTPUT.PUT_LINE('Correct:');
DBMS_OUTPUT.PUT_LINE(SUBSTR(REGEXP_REPLACE(string, '(.)', '-\1'), 2));
DBMS_OUTPUT.PUT_LINE('Incorrect:');
DBMS_OUTPUT.PUT_LINE(LTRIM(REGEXP_REPLACE(string, '(.)', '-\1'), '-'));
END;
/
Outputs:
Correct:
----h-e-l-l-o-w-o-r-l-d----
Incorrect:
h-e-l-l-o-w-o-r-l-d----
db<>fiddle here
There is a requirement from client side that after function get executed more than 2 times then its output should be concatinated with some string and this function is inside a package .
for example...
function get called 7 times from package (its a backend job which executed automatically) and returns 'abc' but when the job runs for the 3rd time i want output 'abcde'.
One option is to create a separate log table and insert a row for each of function calls; then - within a function - check how many times it was invoked and return appropriate output. Something like this:
Log table:
SQL> CREATE TABLE flog
2 (
3 cuser VARCHAR2 (30),
4 sid NUMBER
5 );
Table created.
Package:
SQL> CREATE OR REPLACE PACKAGE pkg_test
2 IS
3 FUNCTION f_test
4 RETURN VARCHAR2;
5
6 PROCEDURE p_test;
7 END;
8 /
Package created.
Package body:
SQL> CREATE OR REPLACE PACKAGE BODY pkg_test
2 IS
3 FUNCTION f_test
4 RETURN VARCHAR2
5 IS
6 l_cnt NUMBER;
7 retval VARCHAR2 (10);
8 BEGIN
9 SELECT COUNT (*)
10 INTO l_cnt
11 FROM flog
12 WHERE cuser = USER
13 AND sid = SYS_CONTEXT ('USERENV', 'SID');
14
15 retval := CASE WHEN l_cnt <= 2 THEN 'abc' ELSE 'abc' || 'de' END;
16 RETURN retval;
17 END;
18
19 PROCEDURE p_test
20 IS
21 BEGIN
22 FOR i IN 1 .. 3
23 LOOP
24 INSERT INTO flog (cuser, sid)
25 VALUES (USER, SYS_CONTEXT ('USERENV', 'SID'));
26
27 DBMS_OUTPUT.put_line ('Execution #' || i || ', result = ' || f_test);
28 END LOOP;
29 END;
30 END pkg_test;
31 /
Package body created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL> EXEC pkg_test.p_test;
Execution #1, result = abc
Execution #2, result = abc
Execution #3, result = abcde
PL/SQL procedure successfully completed.
SQL>
my procedure is supposed to change 2 values but when i call it it shows the same values entered
code
CREATE OR REPLACE PROCEDURE commande_remise(pourcentage_rem IN Decimal,
c_client IN commande.code_client%type,
c_reg IN commande.reglement%type,
c_montantht IN OUT commande.montant_ht%type,
c_montantttc IN OUT commande.montant_ttc%type)
IS
c_ref commande.ref_commande%type;
BEGIN
SELECT COUNT(ref_commande) INTO c_ref FROM commande;
c_ref := c_ref + 1;
c_montantht := c_montantht-c_montantht*pourcentage_rem;
c_montantttc := c_montantttc-c_montantttc*pourcentage_rem;
INSERT INTO commande(ref_commande, code_client, reglement, montant_ht, montant_ttc)
VALUES(c_ref, c_client, c_reg, c_montantht, c_montantttc);
COMMIT;
END commande_remise;
/
procedure call
DECLARE
c_remise DECIMAL :=0.2;
c_code commande.code_client%type :=2;
c_reglement commande.reglement%type :='oui';
c_montantht commande.montant_ht%type :=3080.12;
c_montantttc commande.montant_ttc%type :=3530.56;
c_com commande%rowtype;
CURSOR c_cur IS SELECT ref_commande, code_client, reglement, montant_ht, montant_ttc FROM commande;
BEGIN
commande_remise(c_remise, c_code, c_reglement, c_montantht, c_montantttc);
OPEN c_cur;
LOOP
FETCH c_cur INTO c_com;
exit when c_cur%notfound;
dbms_output.put_line(c_com.ref_commande || ' ' || c_com.code_client || ' ' || c_com.reglement || ' ' || c_com.montant_ht || ' ' || c_com.montant_ttc);
END LOOP;
CLOSE c_cur;
END;
/
the values are c_montantht and c_montantttc
the result is the third:, please help.
Works for me (I'll tell you a secret later).
Table first:
SQL> create table commande
2 (ref_commande number,
3 code_client number,
4 reglement varchar2(10),
5 montant_ht number,
6 montant_ttc number);
Table created.
SQL>
Procedure:
SQL> CREATE OR REPLACE PROCEDURE commande_remise
2 (pourcentage_rem IN NUMBER,
3 c_client IN commande.code_client%type,
4 c_reg IN commande.reglement%type,
5 c_montantht IN OUT commande.montant_ht%type,
6 c_montantttc IN OUT commande.montant_ttc%type)
7 IS
8 c_ref commande.ref_commande%type;
9 BEGIN
10 SELECT COUNT(ref_commande) INTO c_ref FROM commande;
11 c_ref := c_ref + 1;
12 dbms_output.put_line('pourcentage_rem = ' || pourcentage_rem);
13 c_montantht := c_montantht - c_montantht * pourcentage_rem;
14 c_montantttc := c_montantttc - c_montantttc * pourcentage_rem;
15
16 INSERT INTO commande
17 (ref_commande, code_client, reglement, montant_ht, montant_ttc)
18 VALUES
19 (c_ref, c_client, c_reg, c_montantht, c_montantttc);
20 END commande_remise;
21 /
Procedure created.
SQL>
Anonymous PL/SQL block:
SQL> set serveroutput on;
SQL> DECLARE
2 c_remise NUMBER :=0.2;
3 c_code commande.code_client%type :=2;
4 c_reglement commande.reglement%type :='oui';
5 c_montantht commande.montant_ht%type :=3080.12;
6 c_montantttc commande.montant_ttc%type :=3530.56;
7 c_com commande%rowtype;
8 CURSOR c_cur IS SELECT ref_commande, code_client, reglement, montant_ht, montant_ttc FROM commande;
9 BEGIN
10 commande_remise(c_remise, c_code, c_reglement, c_montantht, c_montantttc);
11 OPEN c_cur;
12 LOOP
13 FETCH c_cur INTO c_com;
14 exit when c_cur%notfound;
15 dbms_output.put_line(c_com.ref_commande || ' ' || c_com.code_client
16 || ' ' || c_com.reglement || ' ' || c_com.montant_ht
17 || ' ' || c_com.montant_ttc);
18 END LOOP;
19 CLOSE c_cur;
20 END;
21 /
pourcentage_rem = ,2
1 2 oui 2464,096 2824,448
PL/SQL procedure successfully completed.
SQL>
Table contents:
SQL> select * From commande;
REF_COMMANDE CODE_CLIENT REGLEMENT MONTANT_HT MONTANT_TTC
------------ ----------- ---------- ---------- -----------
1 2 oui 2464,096 2824,448
SQL>
Looks OK, right?
The secret: don't use DECIMAL in
procedure's parameter declaration: pourcentage_rem IN Decimal
anonymous PL/SQL block's variable declaration: c_remise DECIMAL :=0.2;
Use NUMBER instead. Because, if you use DECIMAL, then procedure's line #12 displays
pourcentage_rem = 0
so - when you subtract something that is multiplied by zero, you subtract zero and get the input value itself.
I have string like this: str:='ac_Abc.88,ac_Abc.99,ac_Abc.77'. I need to get first element after splitting with comma(,). So im using using like this:
str VARCHAR2(500);
dbms_utility.comma_to_table
( list => regexp_replace(str,'(^|,)','\1')
, tablen => l_count
, tab => l_array
);
I'm getting following error:
ORA-20001: comma-separated list invalid near bc.88
ORA-06512: at "SYS.DBMS_UTILITY", line 239
ORA-06512: at "SYS.DBMS_UTILITY", line 272
But if i have string like this, str:='ac_Abc88,ac_Abc99,ac_Abc77', the same method working fine and giving me expected results.
So i guess there is something need to be corrected to consider "." as regular character. Can you please suggest how can i solve this.
See How to split comma delimited string into rows
1. REGEXP_SUBSTR approach
SQL> WITH DATA AS(
2 SELECT 'ac_Abc.88,ac_Abc.99,ac_Abc.77' str FROM dual)
3 SELECT regexp_substr(str,'[^,]+',1,level) str
4 FROM DATA
5 CONNECT BY regexp_substr(str, '[^,]+', 1, level) IS NOT NULL
6 /
STR
-----------------------------
ac_Abc.88
ac_Abc.99
ac_Abc.77
SQL>
2. XML approach
SQL> SELECT EXTRACT (VALUE (d), '//row/text()').getstringval () str
2 FROM
3 (SELECT XMLTYPE ( '<rows><row>'
4 || REPLACE ('ac_Abc.88,ac_Abc.99,ac_Abc.77', ',', '</row><row>')
5 || '</row></rows>' ) AS xmlval
6 FROM DUAL
7 ) x,
8 TABLE (XMLSEQUENCE (EXTRACT (x.xmlval, '/rows/row'))) d
9 /
STR
--------------------
ac_Abc.88
ac_Abc.99
ac_Abc.77
3. Table function
SQL> CREATE TYPE test_type
2 AS
3 TABLE OF VARCHAR2(100)
4 /
Type created.
SQL>
SQL> CREATE OR REPLACE
2 FUNCTION comma_to_table(
3 p_list IN VARCHAR2)
4 RETURN test_type
5 AS
6 l_string VARCHAR2(32767) := p_list || ',';
7 l_comma_index PLS_INTEGER;
8 l_index PLS_INTEGER := 1;
9 l_tab test_type := test_type();
10 BEGIN
11 LOOP
12 l_comma_index := INSTR(l_string, ',', l_index);
13 EXIT
14 WHEN l_comma_index = 0;
15 l_tab.EXTEND;
16 l_tab(l_tab.COUNT) := SUBSTR(l_string, l_index, l_comma_index - l_index);
17 l_index := l_comma_index + 1;
18 END LOOP;
19 RETURN l_tab;
20 END comma_to_table;
21 /
Function created.
SQL> sho err
No errors.
SQL>
SQL> SELECT * FROM TABLE(comma_to_table('ac_Abc.88,ac_Abc.99,ac_Abc.77'))
2 /
COLUMN_VALUE
--------------------------------------------------------------------------------
ac_Abc.88
ac_Abc.99
ac_Abc.77
SQL>
4. Pipelined Function
SQL> CREATE OR REPLACE
2 FUNCTION comma_to_table(
3 p_list IN VARCHAR2)
4 RETURN test_type PIPELINED
5 AS
6 l_string LONG := p_list || ',';
7 l_comma_index PLS_INTEGER;
8 l_index PLS_INTEGER := 1;
9 BEGIN
10 LOOP
11 l_comma_index := INSTR(l_string, ',', l_index);
12 EXIT
13 WHEN l_comma_index = 0;
14 PIPE ROW ( SUBSTR(l_string, l_index, l_comma_index - l_index) );
15 l_index := l_comma_index + 1;
16 END LOOP;
17 RETURN;
18 END comma_to_table;
19 /
Function created.
SQL> sho err
No errors.
SQL>
SQL> SELECT * FROM TABLE(comma_to_table('ac_Abc.88,ac_Abc.99,ac_Abc.77'))
2 /
COLUMN_VALUE
--------------------------------------------------------------------------------
ac_Abc.88
ac_Abc.99
ac_Abc.77
It is because (Oracle doc reference)
COMMA_TO_TABLE Procedures
These procedures converts a comma-delimited list of names into a
PL/SQL table of names. The second version supports fully-qualified
attribute names.
A "name" referred to here is a valid Oracle (DB object) identifier, for which all naming rules apply. ac_Abc.88 is not a valid name, because in Oracle you can't have an identifier starting with a digit.
To resolve your problem with parsing strings of comma-delimited values, use the solution of Lalit Kumar B's.
I have a requirement in Oracle where I have to pass a comma separated string of country codes to an Oracle stored procedure.
Inside a stored procedure I want to split the string on comma and iterate for each country code.
I want a SP which is similar as follows:
PROCEDURE Get_Query
(
v_company IN VARCHAR2,
) IS
sqlstr VARCHAR2(32767);
BEGIN
/*
split the v_company on comma (e.g. IN,US,...etc)
iterate for each country code
*/
END;
You can use DBMS_UTILITY.COMMA_TO_TABLE.
The DBMS_UTILITY package provides various utility subprograms. One such useful utility is COMMA_TO_TABLE procedure, which converts a comma-delimited list of names into a PL/SQL table of names.
For example,
SQL> set serveroutput on;
SQL> DECLARE
2 l_tablen BINARY_INTEGER;
3 l_tab DBMS_UTILITY.uncl_array;
4 CURSOR cur
5 IS
6 SELECT 'word1, word2, word3, word4, word5, word6' val FROM dual;
7 rec cur%rowtype;
8 BEGIN
9 OPEN cur;
10 LOOP
11 FETCH cur INTO rec;
12 EXIT
13 WHEN cur%notfound;
14 DBMS_UTILITY.comma_to_table (
15 list => rec.val, tablen => l_tablen, tab => l_tab);
16 FOR i IN 1 .. l_tablen
17 LOOP
18 DBMS_OUTPUT.put_line(i || ' : ' || trim(l_tab(i)));
19 END LOOP;
20 END LOOP;
21 CLOSE cur;
22 END;
23 /
1 : word1
2 : word2
3 : word3
4 : word4
5 : word5
6 : word6
PL/SQL procedure successfully completed.
SQL>
UPDATE As #ruudvan points out, there are restrictions for using COMMA_TO_TABLE like it won't work if you have keywords as delimited string like IS,AS etc.
To overcome the restrictions of COMMA_TO_TABLE, there are many other ways of doing it, see How to split comma delimited string into rows in Oracle
For example, you could use REGULAR EXPRESSION as shown below:
Test case
SQL> CREATE OR REPLACE
2 PROCEDURE Get_Query(
3 v_company IN VARCHAR2 )
4 IS
5 BEGIN
6
7 FOR i IN
8 (SELECT level,
9 trim(regexp_substr(v_company, '[^,]+', 1, LEVEL)) str
10 FROM dual
11 CONNECT BY regexp_substr(v_company , '[^,]+', 1, LEVEL) IS NOT NULL
12 )
13 LOOP
14 -- do something
15 dbms_output.put_line('Company code no.'||i.level||' = '||i.str);
16 END LOOP;
17 END;
18 /
Procedure created.
SQL> sho err
No errors.
Let's check:
SQL> set serveroutput on
SQL> EXEC get_query('COMP1,COMP2,COMP3,COMP4');
Company code no.1 = COMP1
Company code no.2 = COMP2
Company code no.3 = COMP3
Company code no.4 = COMP4
PL/SQL procedure successfully completed.
SQL>
This use of regular expressions converts the input string into a stream of tokens which can be processed like the result set of any other SELECT statement.
PROCEDURE Get_Query
(
v_company IN VARCHAR2,
) IS
sqlstr VARCHAR2(32767);
BEGIN
for rec in (select distinct regexp_substr(v_company, '[^,]+', 1, level) as ctry
from dual
connect by level <= regexp_count (v_company, '[,]') +1
loop
do_something ( rec.ctry );
end loop;
END;