How can i use variables dynamically by changing their names using code - sql

Want a way to refer different variables by changing their names dynamically in a plsql block.
Tried with simple loops which will change effective names of the variables.
Writing a cascaded plsql block using statement but due to size of existing code it is of no use.
set serveroutput on;
declare
foo1 number:=111;
foo2 number:=222;
begin
execute immediate 'dbms_output.put_line(foo'||'1)';
for i in 0..2
loop
dbms_output.put_line(foo||i);
end loop;
end;
/
expected output to be 111
output is error as below
Error report -
ORA-00900: invalid SQL statement
ORA-06512: at line 5
00900. 00000 - "invalid SQL statement"

Your variables are local. The DBMS_OUTPUT package doesn't know them. So the resulting string dbms_output.put_line(foo1) can not be executed with execute immediate.
The typical way to solve this is to use an array of values rather than separate variables:
declare
type t_array is varray(2) of integer;
v_array t_array := t_array(111, 222);
begin
for i in 1..2 loop
dbms_output.put_line(v_array(i));
end loop;
end;
The same with a dynamic array:
declare
type t_array is table of integer;
v_array t_array := t_array();
begin
v_array.extend(1);
v_array(1) := 111;
v_array.extend(1);
v_array(2) := 222;
for i in 1 .. v_array.count loop
dbms_output.put_line(v_array(i));
end loop;
end;

Let's take it one step at a time. First, static code that works:
SQL> declare
2 foo1 number:=111;
3 foo2 number:=222;
4 begin
5 dbms_output.put_line(foo1);
6 end;
7 /
111
Now, if you want to EXECUTE IMMEDIATE some text, you have to put all the code in the text:
SQL> declare
2 l_code_block varchar2(4000) := '
3 declare
4 foo1 number:=111;
5 foo2 number:=222;
6 begin
7 dbms_output.put_line(foo2);
8 end;
9 ';
10 begin
11 execute immediate l_code_block;
12 end;
13 /
222
Next step: if we want to change a value dynamically, we can and should use a "bind variable".
SQL> declare
2 l_code_block varchar2(4000) := '
3 declare
4 foo1 number:=111;
5 foo2 number:=222;
6 begin
7 dbms_output.put_line(:n);
8 end;
9 ';
10 begin
11 execute immediate l_code_block using 1;
12 end;
13 /
1
But if we try to use a bind variable to change the code, it doesn't work.
SQL> declare
2 l_code_block varchar2(4000) := '
3 declare
4 foo1 number:=111;
5 foo2 number:=222;
6 begin
7 dbms_output.put_line(foo:n);
8 end;
9 ';
10 begin
11 execute immediate l_code_block using 1;
12 end;
13 /
...
Error report -
ORA-06550: line 6, column 25:
PLS-00103: Encountered the symbol "" when expecting one of the following
...
So if we want to change the code dynamically, we have to do a REPLACE on the text.
SQL> declare
2 l_code_block varchar2(4000) := '
3 declare
4 foo1 number:=111;
5 foo2 number:=222;
6 begin
7 dbms_output.put_line(foo#N#);
8 end;
9 ';
10 begin
11 execute immediate replace(l_code_block,'#N#',1);
12 end;
13 /
111
Finally, here is your loop:
SQL> declare
2 l_code_block varchar2(4000) := '
3 declare
4 foo1 number:=111;
5 foo2 number:=222;
6 begin
7 dbms_output.put_line(foo#N#);
8 end;
9 ';
10 begin
11 for i in 1..2 loop
12 execute immediate replace(l_code_block,'#N#',i);
13 end loop;
14 end;
15 /
111
222
Please understand, I am trying to answer your question as asked. This "dynamic" approach should be avoided whenever possible, and it is almost always possible. We would have to back up to the business requirement in order to recommend the most appropriate technique.
Best regards,
Stew Ashton

Related

Find id then assign 1 if id found from table PL sql create procedure

I'm looking to create a procedure that looks for the given customer ID in the database. If the customer exists, it sets the variable found to 1. Otherwise, the found variable is set to 0. However, my call out code block does not provide a result. Did I miss something or my SELECT statement should be something else? Thank you.
CREATE OR REPLACE PROCEDURE find_customer(CUST_ID IN NUMBER, found OUT NUMBER) AS
CUSTID NUMBER := CUST_ID;
BEGIN
SELECT CUSTOMER_ID INTO CUSTID
FROM CUSTOMERS
WHERE CUSTOMER_ID = CUST_ID;
IF CUST_ID = NULL THEN
found := 1;
END IF;
EXCEPTION
WHEN no_data_found THEN
found := 0;
END;
/
DECLARE
CUSTOMER_ID NUMBER := 1;
found NUMBER;
BEGIN
find_customer(1,found);
DBMS_OUTPUT.PUT_LINE (found);
END;
I don't think there's anything other to it than the following part bellow. In your given example, it is not possible to get a null value from it as any null id would probably mean the item doesn't exist. Meaning it doesn't return a row, which triggers the NO_DATA_FOUND exception, which you catch.
This is what you wrote:
IF CUST_ID = NULL THEN
found := 1;
END IF;
This is probably what you meant:
IF CUST_ID IS NOT NULL THEN
found := 1;
END IF;
I'd rewrite it so that
you distinguish parameters from local variables from column names
use table aliases
fix what happens when something is found (is not null, line #11)
while testing, use variable you declared, not a constant (1)
So:
SQL> CREATE OR REPLACE PROCEDURE find_customer (par_cust_id IN NUMBER,
2 par_found OUT NUMBER)
3 AS
4 l_custid NUMBER;
5 BEGIN
6 SELECT c.customer_id
7 INTO l_custid
8 FROM customers c
9 WHERE c.customer_id = par_cust_id;
10
11 IF l_custid IS NOT NULL
12 THEN
13 par_found := 1;
14 END IF;
15 EXCEPTION
16 WHEN NO_DATA_FOUND
17 THEN
18 par_found := 0;
19 END;
20 /
Procedure created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL> SELECT * FROM customers;
CUSTOMER_ID
-----------
100
SQL> DECLARE
2 l_customer_id NUMBER := 1;
3 l_found NUMBER;
4 BEGIN
5 find_customer (l_customer_id, l_found);
6 DBMS_OUTPUT.put_line (l_found);
7 END;
8 /
0
PL/SQL procedure successfully completed.
SQL> DECLARE
2 l_customer_id NUMBER := 100;
3 l_found NUMBER;
4 BEGIN
5 find_customer (l_customer_id, l_found);
6 DBMS_OUTPUT.put_line (l_found);
7 END;
8 /
1
PL/SQL procedure successfully completed.
SQL>
You can simplify it down to:
CREATE OR REPLACE PROCEDURE find_customer(
p_cust_id IN CUSTOMERS.CUSTOMER_ID%TYPE,
p_found OUT NUMBER
) AS
BEGIN
SELECT 1
INTO p_found
FROM CUSTOMERS
WHERE CUSTOMER_ID = p_cust_id;
EXCEPTION
WHEN no_data_found THEN
p_found := 0;
END;
/
The line CUSTOMER_ID = p_cust_id will not match if either side is NULL so you don't need any further checks.
Then you can call it using:
DECLARE
v_found NUMBER;
BEGIN
find_customer(1,v_found);
DBMS_OUTPUT.PUT_LINE (v_found);
END;
/
db<>fiddle here

How to update a clob column with data over 2 million characters

I am trying to update a clob value with length > 2 million characters in PL/SQL. I am getting the error
String literal too long
Is there any way I can get around this error?
This is the PL/SQL code snippet I am trying to update the clob value with:
DECLARE
value clob;
clob_field clob;
fromindex integer;
offset integer;
chunks integer;
eclob clob;
sql_stmt clob;
BEGIN
fromindex := 1;
offset := 2;
clob_field := '<clob_value_with_length_2Million>';
chunks := 1+(dbms_lob.Getlength(clob_field) / 2);
value :='';
FOR chunk IN 1 .. chunks LOOP
IF ( chunk != 1) THEN
value := value || ' || ';
END IF;
value := value || 'to_clob('''||dbms_lob.Substr(clob_field, offset, fromindex)||''')';
fromindex := fromindex + 2;
END LOOP;
dbms_output.put_line(value);
sql_stmt := 'update mytable
set sources = ' || value ||' where scenario_id = 1 and entry_index = 1';
EXECUTE IMMEDIATE sql_stmt;
END;
I am getting the error at clob_field initialization and it is obvious as PL/SQl wont allow more than 32k characters. So, I am reaching out here to see if I can have any solution to my problem.
You can reduce the amount of code needed and improve performance of your code if you used bind variables. If you are attempting to build a different update statement each time, the database will need to come up with a execution plan for each different query. Using bind variables also removes the need of having to do any SQL sanitization to protect against SQL injection.
Example
SQL> CREATE TABLE mytable
2 AS
3 SELECT 1 AS scenario_id, 1 AS entry_index, EMPTY_CLOB () || 'clob1' AS sources FROM DUAL
4 UNION ALL
5 SELECT 2 AS scenario_id, 2 AS entry_index, EMPTY_CLOB () || 'clob2' AS sources FROM DUAL;
Table created.
SQL> SELECT * FROM mytable;
SCENARIO_ID ENTRY_INDEX SOURCES
----------- ----------- --------------------------------------------------------------------------------
1 1 clob1
2 2 clob2
SQL> DECLARE
2 clob_field CLOB;
3 l_scenario_id NUMBER;
4 l_entry_index NUMBER;
5 sql_stmt CLOB;
6 BEGIN
7 clob_field := '<clob_value_with_length_2Million>';
8 l_scenario_id := 1;
9 l_entry_index := 1;
10
11 sql_stmt :=
12 'update mytable set sources = :bind_clob where scenario_id = :scenario and entry_index = :entry';
13
14 EXECUTE IMMEDIATE sql_stmt
15 USING clob_field, l_scenario_id, l_entry_index;
16 END;
17 /
PL/SQL procedure successfully completed.
SQL> SELECT * FROM mytable;
SCENARIO_ID ENTRY_INDEX SOURCES
----------- ----------- --------------------------------------------------------------------------------
1 1 <clob_value_with_length_2Million>
2 2 clob2

How do I assign the value of a refcursor to a variable in PL/SQL

This stored procedure call returns a single varchar2 type value. The issue is that it comes back in a refcursor. I need to get the ID value and assign it to a variable instead of printing it to the console.
var r refcursor;
DECLARE
BEGIN
P_PACKAGE.INSERT_INVOICE(
IN_INVOICE_TYPE => L_INVOICE.INVOICE_TYPE,
OUTPUT => :R);
END;
/
print r;
Exactly as you said - you need to assign it to a variable. Here's an example based on Scott's schema.
SQL> create or replace procedure p_rc (par_deptno in number, par_rc out sys_refcursor)
2 is
3 begin
4 open par_rc for select ename, sal
5 from emp
6 where deptno = par_deptno;
7 end;
8 /
Procedure created.
Let's test it; pay attention to lines #4, 5 (declaration of variables) and #10 (fetch into those variables):
SQL> declare
2 l_rc sys_refcursor;
3 -- declare variables which will get values returned by refcursor
4 l_ename emp.ename%type;
5 l_sal emp.sal%type;
6 begin
7 p_rc(10, l_rc);
8
9 loop
10 fetch l_rc into l_ename, l_sal;
11 exit when l_rc%notfound;
12 dbms_output.put_line(rpad(l_ename, 10, ' ') ||': '|| l_sal);
13 end loop;
14 end;
15 /
CLARK : 2450
KING : 5001
MILLER : 1300
PL/SQL procedure successfully completed.
SQL>
I wouldn't define the REFCURSOR in SQL*Plus as you've shown it. Assuming that your REFCURSOR was opened with a statement similar to
SELECT ID
FROM SOME_TABLE
WHERE SOMEFIELD = some_value
then your PL/SQL code should look something like
DECLARE
aString VARCHAR2(2000);
rc SYS_REFCURSOR;
BEGIN
P_PACKAGE.INSERT_INVOICE(
IN_INVOICE_TYPE => L_INVOICE.INVOICE_TYPE,
OUTPUT => rc);
FETCH rc
INTO aString;
DBMS_OUTPUT.PUT_LINE('aString = ''' || aString || '''');
END;

is there a function without an input but return something

CREATE OR REPLACE FUNCTION FN_MULTAS_TOTAL
RETURN INTEGER
IS
V_TOTAL INT;
BEGIN
SELECT SUM(MULTA)
INTO V_TOTAL
FROM DETALLE_ARRIENDO;
RETURN V_TOTAL;
END;
im working on SQL Developer 11 and i have to call it with
exec FN_MULTAS_TOTAL;
and get an integer value
No problem with such a function:
SQL> create or replace function fn_multas_total
2 return integer
3 is
4 v_total int;
5 begin
6 select sum(multa)
7 into v_total
8 from detalle_arriendo;
9 return v_total;
10 end;
11 /
Function created.
However, you don't call it with exec - it is used in SQL*Plus or SQL Developer or some other tools which support it; it is a "shortcut" for an anonymous begin-end PL/SQL block:
SQL> exec p_test
PL/SQL procedure successfully completed.
SQL> begin
2 p_test;
3 end;
4 /
PL/SQL procedure successfully completed.
It is used for procedures, not functions. You can't just
SQL> begin
2 detalle_arriendo;
3 end;
4 /
detalle_arriendo;
*
ERROR at line 2:
ORA-06550: line 2, column 3:
PLS-00221: 'DETALLE_ARRIENDO' is not a procedure or is undefined
ORA-06550: line 2, column 3:
PL/SQL: Statement ignored
SQL>
which means that you have to declare a variable and fetch function's result into it, e.g.
SQL> declare
2 l_result number;
3 begin
4 l_result := fn_multas_total;
5 end;
6 /
PL/SQL procedure successfully completed.
SQL>
Usually, we use functions as
SQL> select fn_multas_total from dual;
FN_MULTAS_TOTAL
---------------
100
SQL>
If you desperately want to use exec, then you still have to declare a variable and
SQL> var result number
SQL> exec :result := fn_multas_total;
PL/SQL procedure successfully completed.
SQL> print :result
RESULT
----------
100
SQL>

how to make a select query with functions pl sql

Function 1:
create or replace function get_books (l_id in number)
return varchar
is l_return varchar2(100);
begin
select books into l_return from people where id=l_id;
return l_return;
end
/
Function 2:
create or replace function get_author (l_id in number)
return varchar
is l_return varchar2(100);
begin
select author in l_return from authors where id=l_id;
return l_return;
end
/
I want to make a select with 2 functions, I want to display books and authors.
is it possible?
Yes, it is possible. For example:
select get_books (1) book,
get_author(1) author
from dual;
Presumably, both functions return a single value. If any of those selects (used in functions) returns more than one row, it'll fail.
As of the procedure: code you posted is invalid so I fixed it.
SQL> create or replace procedure get_categories (l_categories in varchar2,
2 l_id in number,
3 l_boolean out boolean,
4 l_error out varchar2)
5 is
6 begin
7 -- INSERT INTO categories (id, categories)
8 -- VALUES (l_categories, l_id);
9
10 l_boolean := true;
11 commit;
12 exception
13 when others
14 then
15 l_boolean := false;
16 l_error := sqlerrm;
17 end;
18 /
Procedure created.
As it returns 2 OUT parameters, you have to declare variables to accept those values (v_boolean and v_error). Furthermore, as you can't directly display Boolean value, use CASE and display a string instead.
Procedure can't be called as a function within the SQL SELECT statement so you have to use another PL/SQL piece of code; an anonymous PL/SQL block is OK.
SQL> set serveroutput on
SQL> declare
2 v_categories varchar2(10) := 'ABC';
3 v_id number := 1;
4 v_boolean boolean;
5 v_error varchar2(200);
6 begin
7 get_categories(l_categories => v_categories,
8 l_id => v_id,
9 l_boolean => v_boolean,
10 l_error => v_error
11 );
12
13 dbms_output.put_line(case when v_boolean then 'true' else 'false' end ||' - '||
14 v_error
15 );
16 end;
17 /
true -
PL/SQL procedure successfully completed.
SQL>