Oracle, calling PL/SQL issues from within SQL-Plus file x.sql says my_function "may not be a function" - oracle

so simple, if I create my function as CREATE OR REPLACE FUNCTION MD5_ENCODE it will run smoothly, but if it stays anonymously within the SQL-Plus block as PL/SQL --> "may not be a function" error.
What is this Oracle "feature" again?
DECLARE
FUNCTION MD5_ENCODE(CLEARTEXT IN VARCHAR2) RETURN VARCHAR2 IS
CHK VARCHAR2(16);
HEX VARCHAR2(32);
I INTEGER;
C INTEGER;
H INTEGER;
BEGIN
IF CLEARTEXT IS NULL THEN
RETURN '';
ELSE
CHK := DBMS_OBFUSCATION_TOOLKIT.MD5(INPUT_STRING
=> CLEARTEXT);
FOR I IN 1 .. 16 LOOP
C := ASCII(SUBSTR(CHK, I, 1));
H := TRUNC(C / 16);
IF H >= 10 THEN
HEX := HEX || CHR(H + 55);
ELSE
HEX := HEX || CHR(H + 48);
END IF;
H := MOD(C, 16);
IF H >= 10 THEN
HEX := HEX || CHR(H + 55);
ELSE
HEX := HEX || CHR(H + 48);
END IF;
END LOOP;
RETURN HEX;
END IF;
END;
BEGIN
UPDATE ADDRESSES_T SET STREET = MD5ENCODE(STREET) ;
-- etc...
END

http://forums.oracle.com/forums/thread.jspa?threadID=245112
There are a number of things it could be.
My #1 candidate is, we can only use functions in SQL statements that are public i.e. declared in a package spec. This is the case even for SQL statements executed within the same Package Body. The reson is that SQL statements are executed by a different engine which can only see publicly declared functions.
Long story short, function must be defined in a package, like CREATE OR REPLACE FUNCTION MD5ENCODE(IN_TEXT IN VARCHAR2) RETURN VARCHAR2 IS ...

In order to call a function within a SQL statement, it needs to exist as a function object within the database. The SQL statement is parsed and executed separately from your PL/SQL code, so it doesn't have access to the locally defined function.
If you really don't want to create the function object, you could use a cursor to loop over the rows in the table, execute the function in a PL/SQL statement, and update each row as you go.

Your first problem is you have a typo.
FUNCTION MD5_ENCODE(CLEARTEXT IN
VARCHAR2) RETURN VARCHAR2 IS
versus
UPDATE ADDRESSES_T SET STREET =
MD5ENCODE(STREET) ;
You are missing the underscore in the function call in the update statement.
The next error you will encounter is :
PLS-00231: function 'MD5_ENCODE' may not be used in SQL
So you can simply assign the function results to a variable and use it in the Update statement.

As it mentioned above comments, this could be useful.
DECLARE
V NUMBER := 0;
FUNCTION GET_SQ(A NUMBER) RETURN NUMBER AS
BEGIN
RETURN A * A;
END;
BEGIN
V := GET_SQ(5);
--DBMS_OUTPUT.PUT_LINE(V);
UPDATE MYTABLE A SET A.XCOL = V;
END;

Related

How to define a pipelined table function within a with statement?

It's possible to defined a function within a with statement (How to define a function and a query in the same with block?). But I fail to define a pipelined function inside a with statement.
WITH
FUNCTION f (a IN INTEGER)
RETURN SYS.odcinumberlist
PIPELINED
IS
ret INTEGER;
BEGIN
FOR z IN 1 .. a
LOOP
PIPE ROW (z);
END LOOP;
RETURN;
END;
SELECT * FROM f(3); --I tried with table(f(3)) too
[Error] Execution (2: 1): ORA-06553: PLS-653: aggregate/table functions are not allowed in PL/SQL scope
Is it possible to define a pipelined table function within a with statement and how?
If it is possible, I would like to do that with a table of record. Therefore I need to know how defined type within a with statement too.
Within a subquery-factoring clause, you can make a non-pipelined function that returns a collection:
WITH FUNCTION f (a IN INTEGER)
RETURN SYS.ODCINUMBERLIST
IS
v_list SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST();
BEGIN
FOR z IN 1 .. a LOOP
v_list.EXTEND;
v_list(v_list.COUNT) := z;
END LOOP;
RETURN v_list;
END f;
SELECT *
FROM TABLE(f(3));
Which outputs:
COLUMN_VALUE
1
2
3
db<>fiddle here
If it is possible, I would like to do that with a table of record.
A record is a PL/SQL data type; you cannot use it in an SQL statement. Instead use an OBJECT data type and declare it in the global SQL scope before you run your query (no, you cannot declare it locally inside your query).
Therefore I need to know how defined type within a with statement too.
Again, you cannot as the SQL syntax does not allow it. See the answer to your previous question.
You can declare it globally:
CREATE TYPE test_obj IS OBJECT (
w1 INTEGER
);
CREATE TYPE test_obj_table IS TABLE OF test_obj;
WITH FUNCTION f (a IN INTEGER)
RETURN test_obj_table
IS
v_list test_obj_table := test_obj_table();
BEGIN
FOR z IN 1 .. a LOOP
v_list.EXTEND;
v_list(v_list.COUNT) := test_obj(z);
END LOOP;
RETURN v_list;
END f;
SELECT *
FROM TABLE(f(3));
Outputs:
W1
1
2
3
db<>fiddle here

Calling procedure in function

Can you call a PL/SQL procedure from inside a function?
I haven't come across with the practical example.So if anyone has come across with this please share.
Yes. You can call any pl/sql program from inside any other pl/sql program. A function can call a function, a procedure can call a procedure which calls a function, a function can invoke a TYPE BODY...do an INSERT..which causes a TRIGGER to fire.
A simple (not really practical) example.
Using the HR schema where you have an EMPLOYEES table which includes columns EMPLOYEE_ID, FIRST_NAME, and LAST_NAME.
I have a function that takes in an INTEGER which we use to look up an EMPLOYEE record. We take their first and last names, and concat them together, and return the value back in UPPERCASE text.
Before we do that however, we call a procedure which does nothing but take a 5 second nap using the DBMS_LOCK package.
The code:
create or replace procedure do_nothing_comments (x in integer, y in integer)
is
begin
null;
-- yeah, this is a dumb demo
dbms_lock.sleep(5);
end;
/
create or replace FUNCTION upper_name (
x IN INTEGER
) RETURN VARCHAR2 IS
upper_first_and_last VARCHAR2 (256);
BEGIN
SELECT upper (first_name)
|| ' '
|| upper (last_name)
INTO upper_first_and_last
FROM employees
WHERE employee_id = x;
do_nothing_comments (1, 2); -- here we are calling the procedure
RETURN upper_first_and_last;
END;
/
Now let's invoke the function.
DECLARE
X NUMBER;
v_Return VARCHAR2(200);
BEGIN
X := 101;
v_Return := UPPER_NAME(
X => X
);
:v_Return := v_Return;
END;
/
I'm going to do this in SQL Developer using the Execute feature with the function open:
I get the answer back...it just takes 5 seconds longer than it really needed to.
Here you go:
create or replace function demo
return varchar2
as
begin
dbms_output.put_line('Hello');
return 1;
end demo;

Can PL/SQL procedures call themselves recursively?

Using PL/SQL, it is possible to call a stored function from within that same function. This can be demonstrated with the following example:
CREATE OR REPLACE FUNCTION factorial(x in number)
RETURN number
IS
f number;
BEGIN
IF x = 0 THEN
f := 1;
ELSE
f := x * factorial(x-1);
END IF;
RETURN f;
END;
/
DECLARE
num number;
factorial number;
BEGIN
num := &num;
factorial := factorial(num);
dbms_output.put_line(' The factorial of '|| num || ' is ' || factorial);
END;
/
Can this be done using PL/SQL stored procedures as well?
Yes, you can write a procedure that calls itself recursively in PL/SQL. Here is an example - implementing the factorial.
With that said, don't ever write a procedure (or a function like yours) without error handling. If you don't understand why, change 5 to 5.3 in the anonymous block below, and you'll see why.
CODE window:
create or replace procedure fact ( x in number, x_fact out number )
as
begin
if x = 0 then x_fact := 1;
else fact(x-1, x_fact);
x_fact := x * x_fact;
end if;
end;
/
set serveroutput on
declare
z number;
begin
fact(5, z);
dbms_output.put_line(z);
end;
/
SCRIPT OUTPUT window (matching each "result" to the corresponding part of the code left as an exercise):
Procedure FACT compiled
PL/SQL procedure successfully completed.
120
You can certainly call PL/SQL functions recursively (with all the usual warnings about the dangers of doing so in any language!).
You are, however, going to run into trouble if you name a local variable the same as your function. You will, for example, get this error when you try to execute the block:
PLS-00222: no function with name 'FACTORIAL' exists in this scope

PLSQL when trying to change value of length while loop gives error

In my pl sql while loop i try to change the length of a number, which also then changes the length of my value on which my while loop is based the while loop stops working. The code is as follows
CREATE OR REPLACE FUNCTION neemrest(restnumber number)
RETURN NUMBER
AS
partrest NUMBER;
afternumber NUMBER;
BEGIN
WHILE LENGTH(TO_CHAR(restnumber))>9 LOOP
partrest := TO_NUMBER(SUBSTR(restnumber,1,10));
afternumber := TO_NUMBER(SUBSTR(restnumber,11,20));
restnumber := partrest||afternumber;
END LOOP;
RETURN MOD(restnumber,64);
END;
Now by taking each step seperatly i found that the problem liest in the last line of the while loop
restnumber := partrest||afternumber;
Now my question how can i change the lenght of my number whilst not breaking the while loop since this is vital for my function. and the usage of my function
The problem you are running into is that you are trying to assign a value to a readonly parameter.
You should modify your function to be like this:
CREATE OR REPLACE FUNCTION neemrest(restnumber in out number)
RETURN NUMBER
AS
partrest NUMBER;
afternumber NUMBER;
BEGIN
WHILE LENGTH(TO_CHAR(restnumber))>9 LOOP
partrest := TO_NUMBER(SUBSTR(restnumber,1,10));
afternumber := TO_NUMBER(SUBSTR(restnumber,11,20));
restnumber := partrest||afternumber;
END LOOP;
RETURN MOD(restnumber,64);
END;

Is it possible to use "return" in stored procedure?

CREATE PROCEDURE Pname(in_Tid IN VARCHAR2,in_IP IN VARCHAR2,outstaticip OUT VARCHAR2,outcount OUT NUMBER)
AS
BEGIN
select STATIC_IP into outstaticip from OP_TTER_MAPPING where TERMINAL_ID = in_Tid;
if in_IP = outstaticip then
return 1;
else
select COUNT(*) into outcount from OP_TTER_MAPPING where DYNAMIC_IP_LOW <= in_IP AND DYNAMIC_IP_HIGH >= in_IP AND TERMINAL_ID = in_Tid;
if outcount = 1 then
return 1;
else
return 0;
end if;
end if;
END;
Is it possible to use return in stored procedure like above?
If we can use return, how can i get that return value in Executesql("begin Pname(----)END") method
EDIT
Now I edited my return value in stored procedure like this, am I doing it right ?
CREATE PROCEDURE P_ValidateTIDIP(in_Tid IN VARCHAR2,in_IP IN VARCHAR2,outstaticip OUT VARCHAR2,outcount OUT NUMBER,outretvalue OUT NUMBER)
AS
BEGIN
select STATIC_IP into outstaticip from OP_TTER_MAPPING where TERMINAL_ID = in_Tid;
if in_IP = outstaticip then
outretvalue:=1;
else
select COUNT(*) into outcount from OP_TTER_MAPPING where DYNAMIC_IP_LOW <= in_IP AND DYNAMIC_IP_HIGH >= in_IP AND TERMINAL_ID = in_Tid;
if outcount = 1 then
outretvalue:=1;
else
outretvalue:=0;
end if;
end if;
END;
In Stored procedure, you return the values using OUT parameter ONLY. As you have defined two variables in your example:
outstaticip OUT VARCHAR2, outcount OUT NUMBER
Just assign the return values to the out parameters i.e. outstaticip and outcount and access them back from calling location. What I mean here is: when you call the stored procedure, you will be passing those two variables as well. After the stored procedure call, the variables will be populated with return values.
If you want to have RETURN value as return from the PL/SQL call, then use FUNCTION. Please note that in case, you would be able to return only one variable as return variable.
Use FUNCTION:
CREATE OR REPLACE FUNCTION test_function
RETURN VARCHAR2 IS
BEGIN
RETURN 'This is being returned from a function';
END test_function;
-- IN arguments : you get them. You can modify them locally but caller won't see it
-- IN OUT arguments: initialized by caller, already have a value, you can modify them and the caller will see it
-- OUT arguments: they're reinitialized by the procedure, the caller will see the final value.
CREATE PROCEDURE f (p IN NUMBER, x IN OUT NUMBER, y OUT NUMBER)
IS
BEGIN
x:=x * p;
y:=4 * p;
END;
/
SET SERVEROUTPUT ON
declare
foo number := 30;
bar number := 0;
begin
f(5,foo,bar);
dbms_output.put_line(foo || ' ' || bar);
end;
/
-- Procedure output can be collected from variables x and y (ans1:= x and ans2:=y) will be: 150 and 20 respectively.
-- Answer borrowed from: https://stackoverflow.com/a/9484228/1661078
It is possible.
When you use Return inside a procedure, the control is transferred to the calling program which calls the procedure. It is like an exit in loops.
It won't return any value.
CREATE PROCEDURE pr_emp(dept_id IN NUMBER,vv_ename out varchar2 )
AS
v_ename emp%rowtype;
CURSOR c_emp IS
SELECT ename
FROM emp where deptno=dept_id;
BEGIN
OPEN c;
loop
FETCH c_emp INTO v_ename;
return v_ename;
vv_ename := v_ename
exit when c_emp%notfound;
end loop;
CLOSE c_emp;
END pr_emp;

Resources