Why Plsql package variables and constants cannot be used in Sql? - sql

Create some package:
create or replace package my_package is
some_var number := 10;
end;
and use it in Sql:
select my_package.some_var from dual
So it gives error PLS-221.
P.S. I know that I can use wrapper function. My question is why this is not allowed. Can anyone point to the documentation where the reason?

I am going to jump and try to answer your question. It is one I heard of sometimes and indeed has been for a while as a Enhancement Request ( Base ER: ENH 6525013 ), as it was very well pointed by #Anum Kumar.
Why is not possible ? Well, I think the Oracle developers of PL/SQL though about Packages merely as collections of logical subprograms and routines. So make available variables directly to external APIs ( OCI or Java ) was never the intention of the concept.
You don't really have a document link telling you that is not possible, as far as I know, but if you read the documentation of the concept itself, you might get some insights:
A package is a schema object that groups logically related PL/SQL types, variables, constants, subprograms, cursors, and exceptions. A package is compiled and stored in the database, where many applications can share its contents. A package always has a specification, which declares the public items that can be referenced from outside the package. In either the package specification or package body, you can map a package subprogram to an external Java or C subprogram by using a call specification, which maps the external subprogram name, parameter types, and return type to their SQL counterparts.
That is the key, you can map any subprogram of a package to any external Java, C routine; but you can't reference a constant variable directly without the corresponding subprogram ( in your case a function ).
However, you can't use it on SQL, but you can in PL/SQL. Keep in mind that Oracle contains different areas in the Library cache to handle SQL and PLSQL. The library cache holds executable forms of PL/SQL programs and Java classes. These items are collectively referred to as program units.
Example
CREATE OR REPLACE PACKAGE pkg IS
n NUMBER := 5;
END pkg;
/
Package created.
SQL> CREATE OR REPLACE PACKAGE sr_pkg IS
PRAGMA SERIALLY_REUSABLE;
n NUMBER := 5;
END sr_pkg;
/
Package created.
SQL> select sr_pkg.n from dual ;
select sr_pkg.n from dual
*
ERROR at line 1:
ORA-06553: PLS-221: 'N' is not a procedure or is undefined
SQL> set serveroutput on
SQL> BEGIN
pkg.n := 10;
sr_pkg.n := 10;
END;
/
PL/SQL procedure successfully completed.
SQL> BEGIN
DBMS_OUTPUT.PUT_LINE('pkg.n: ' || pkg.n);
DBMS_OUTPUT.PUT_LINE('sr_pkg.n: ' || sr_pkg.n);
END;
/
pkg.n: 10
sr_pkg.n: 5
PL/SQL procedure successfully completed.
In the example above, I can't use the SQL engine to reference a constant variable, as I would need a subprogram or routine ( this is an OCI call, but it might very well be a Java program, ProC, etc. I specifically used one of the packages as serially_reusable that you can check how the variable does not change even if I try to.
However, if I don't use the SQL engine, I can use it without any problem within PL/SQL.
I hope it clarifies.

Related

How to retrieve PL/SQL record type in SQL? [duplicate]

This question already has answers here:
Oracle: Select From Record Datatype
(6 answers)
Closed 5 years ago.
A shared package exists which defines a student record type and a function which returns a student:
CREATE OR REPLACE PACKAGE shared.student_utils IS
--aggregates related data from several tables
TYPE student_rec IS RECORD (
id student.id%TYPE,
username student.username%TYPE,
name student.name%TYPE,
phone phone.phone%TYPE
/*etc.*/
);
FUNCTION get_student(student_id IN student.id%TYPE) RETURN student_rec;
END;
Now, I'm writing a package that provides a API for an Apex application to consume. In particular, I need to provide the student record and other relevant data in a format that can be selected via SQL (and displayed in a report page in Apex.)
So far I've been trying to find the most direct way to select the data in SQL. Obviously, a record type cannot be used in SQL, so my quick-and-dirty idea was to define a table type in my package spec and a PIPELINED function in my package spec/body:
CREATE OR REPLACE PACKAGE my_schema.api IS
TYPE student_tab IS TABLE OF shared.student_utils.student_rec;
FUNCTION get_all_student_data(student_id IN student.id%TYPE) RETURN student_tab PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY my_schema.api IS
FUNCTION get_all_student_data(student_id IN student.id%TYPE) RETURN student_tab PIPELINED IS
BEGIN
PIPE ROW(shared.student_utils.get_student(student_id));
END;
END;
...which lets me select it like so:
SELECT * FROM TABLE(my_schema.api.get_all_student_data(1234));
This works, but building a pipelined table just for one row is overkill, and Oracle's explain plan seems to agree.
Supposedly in Oracle 12c, there should be more options available:
More PL/SQL-Only Data Types Can Cross PL/SQL-to-SQL Interface
...but I can't seem to figure it out in my scenario. Changing the function to:
FUNCTION get_all_student_data RETURN student_tab IS
r_student_tab student_tab;
BEGIN
r_student_tab(1) := shared.student_utils.get_student(student_id);
RETURN r_student_tab;
END;
...will compile, but I cannot SELECT from it as before.
OK, enough rambling, here's my actual question - what is the most direct method to call a PL/SQL function that returns a record type and select/manipulate the result in SQL?
The killer line in the documentation is this one:
A PL/SQL function cannot return a value of a PL/SQL-only type to SQL.
That appears to rule out querying directly from a function which returns a PL/SQL Record or associative array like this:
select * from table(student_utils.get_students(7890));
What does work is this, which is technically SQL (because the docs define anonymous blocks as SQL not PL/SQL):
declare
ltab student_utils.students_tab;
lrec student_utils.student_rec;
rc sys_refcursor;
begin
ltab := student_utils.get_students(1234);
open rc for select * from table(ltab);
fetch rc into lrec;
dbms_output.put_line(lrec.name);
close rc;
end;
/
This is rather lame. I suppose there are a few times when we want to open a ref cursor from an array rather than just opening it for the SQL we would use to populate the array, but it's not the most pressing of use cases.
The problem is Oracle's internal architecture: the kernel has C modules for SQL and C modules for PL/SQL (this is why you will hear people talking about "context switches"). Exposing more PL/SQL capabilities to the SQL engine requires modifying the interfaces. We can only imagine how difficult it is to allow the SQL compiler to work against the definitions of PL/SQL data structures, which are extremely unstable (potentially they can change every time we run create or replace package ....
Of course this does work for PIPELINED functions but that's because under the bonnet Oracle creates SQL types for the PL/SQL types referenced in the function. It cannot create objects on the fly for an arbitrary function we might wish to slip into a table() call. Some day it might be possible but just consider this one sticking point: what happens when a user who has execute on our package but lacks CREATE TYPE privilege tries to use the function?

oracle forms 6i missing package body

In a software we've acquired, we found that there are some package bodies that have procedures that are just defined as null. For example:
procedure execute_report(parameters) is
begin
null;
end;
Despite this, these procedures are being used through the forms, and they seem to work properly. As in the example, calling the procedure "execute_report" actually runs a report.
The procedure is not defined in any library, or even using the query:
SELECT * FROM ALL_OBJECTS WHERE OBJECT_NAME LIKE '%EXEC%'
does not return anything from the database that might be called...
Is there a way to hide the code in the forms, libraries or DB that I'm missing?
You probably have a pll (forms library) that contains the procedure execute_report
Then this will be called instead of the database version.

Using function returning custom type in multi database environment

I got simple utils package with function which turn delimited string into custom table. It is usefull. The definition is like this:
create or replace type StringArray as table of varchar2(60);
create or replace PACKAGE utils
IS
FUNCTION Explode(p_Seperator IN VARCHAR2,p_String IN VARCHAR2) RETURN Stringarray;
END utils;
create or replace PACKAGE BODY utils
IS
FUNCTION Explode(p_Seperator IN VARCHAR2,p_String IN VARCHAR2) RETURN Stringarray
AS
v_String LONG DEFAULT p_String || p_Seperator;
v_Data Stringarray := Stringarray();
n NUMBER;
BEGIN
LOOP
EXIT WHEN v_String IS NULL;
n := Instr(v_String, p_Seperator);
v_Data.Extend;
v_Data(v_Data.Count) := Ltrim(Rtrim(SUBSTR(v_String, 1, n - 1)));
v_String := SUBSTR(v_String, n + 1);
END LOOP;
RETURN v_Data;
END Explode;
END utils;
And use example:
SELECT COLUMN_VALUE as letter FROM table(utils.explode(',','a,b,c,d'))
I used it succesfully from some time. Now, I need to use it in many database environment, and here the problems begin.
Lets consider database A with table T and database B with dblink #A to database A. And our task is to update table T in database A from database B. And the keys are comma separated string like this: 'a,b,c,d', so it is convinient to use our utils.explode function.
To make things easy, lets say that we can define type and package in both databases.
When I try to use function from database B like this:
UPDATE
T#A
SET
field = 'TEST'
WHERE
key IN (SELECT COLUMN_VALUE FROM table(utils.explode(',','1,2,3')))
;
I get error:
SQL Error: ORA-22804: "remote operations not permitted on object tables or user-defined type columns"
22804. 00000 - "remote operations not permitted on object tables or user-defined type columns"
*Cause: An attempt was made to perform queries or DML operations on
remote object
tables or on remote table columns whose type is one of object,
REF, nested table or VARRAY.
And when I want to invoke function from database A like this:
UPDATE
T#A
SET
field = 'TEST'
WHERE
key IN (SELECT COLUMN_VALUE FROM table(utils.explode#A(',','1,2,3')))
;
I get error:
SQL Error: ORA-30626:"function/procedure parameters of remote object types are not supported"
30626. 00000 - "function/procedure parameters of remote object types are not supported"
*Cause:
*Action:
This is my problem.
I know that I can create custom procedure in database A and send it keys as string, and inside use explode function and do the update.
Or I can do what Tom suggested here.
But I use this technique in many places, with different tables and different fields. This explode function is clean, simple and all-purpose tool for use.
That is why I am not seeking some one shot solution which involves creating custom objects per use case, but something more universal.
In more abstract way of putting it, my problem is about sending collection (or array or table) of values between databases in plsql.
Can somebody help me with it, please? :)
[Edit]
Nobody can help me with this? Not even comment?
For a while i was hoping this article can solve your problem, but unfortunately the only working method (i came with and surely you too) is not so elegant, but at least gets the job done:
insert into global_temporary_explode (select column_value as letter from table(utils.explode(',','1,2,3')));
and
update T#A set field = 'TEST' where key in (select column_value from global_temporary_explode);

Oracle PL/SQL: How to find unused variables in a long package?

Please suppose you have an Oracle PL/SQL package of about 200,000 rows of code.
Is there any fast way to detect variables declared, but not used in the package?
Thank you in advance for your kind help.
EDIT (April 7th, 2014): I am using Oracle 10G.
EDIT: I am looking for a pure PL/SQL solution.
The following only applies to 11g R2. It looks like PL/Scope has become available in 11g R1.
You won't get information about unused variables with PLSQL_WARNINGS='ENABLE:ALL':
SQL> !cat test.sql
set serveroutput on
alter session set plsql_warnings = 'ENABLE:ALL';
create or replace procedure foo is
v_a number;
v_b varchar2(10);
begin
dbms_output.put_line('hello world!');
end;
/
show errors
exec foo
SQL> #test
Session altered.
SP2-0804: Procedure created with compilation warnings
Errors for PROCEDURE FOO:
LINE/COL ERROR
-------- -----------------------------------------------------------------
1/1 PLW-05018: unit FOO omitted optional AUTHID clause; default
value DEFINER used
hello world!
PL/SQL procedure successfully completed.
SQL>
As you can see the only reported warning is not related to the unused variables at all. Instead PL/Scope has to be used.
The following example has bee derived from Oracle 11g – Generating PL/SQL Compiler Warnings (Java style) using PL/Scope:
SQL> alter session set plscope_settings = 'identifiers:all';
Session altered.
SQL> alter procedure foo compile;
SP2-0805: Procedure altered with compilation warnings
SQL> show errors
Errors for PROCEDURE FOO:
LINE/COL ERROR
-------- -----------------------------------------------------------------
1/1 PLW-05018: unit FOO omitted optional AUTHID clause; default
value DEFINER used
SQL> #plsql-unused-variables.sql
Enter value for name: foo
old 10: where object_name = upper('&name')
new 10: where object_name = upper('foo')
Enter value for type: procedure
old 11: and object_type = upper('&type')
new 11: and object_type = upper('procedure')
COMPILER_WARNING
--------------------------------------------------------------------------------
V_B: variable is declared but never used (line 3)
V_A: variable is declared but never used (line 2)
SQL>
The script plsql-unused-variables.sql is just a cut and paste from the blog post mentioned above. Because I found it useful I have also made the script available in Bitbucket.
Set your session to report all warnings:
ALTER SESSION SET PLSQL_WARNINGS='ENABLE:ALL';
then compile your code. If the compilation indicates that there are errors, as in you get something like the following:
SP2-0804: Procedure created with compilation warnings
display any errors:
SHO ERR
If you have any unreferenced variables they should be mentioned in the list of errors. Alternatively, use a tool like PL/SQL Developer which automatically shows you these errors following a compile.
Share and enjoy.
PL/SQL waits to allocate memory until you assign the variable, then only
allocates as much storage as needed. So no need to worry about consuming memory. (Source)
So finding and fixing unused variables is just a housekeeping exercise and won't improve the performance of the package. In other words, the simplest solution would be to do nothing.
If you're worried about unused variables anyway, you might be able to find them just by parsing the package source with command-line tools such as grep, sort and uniq (especially if they follow a coding standard such as starting all variables with v_).

Possible to run oracle package from sql plus without compiling it into a database?

I have a certain oracle package file (pbk with pks). I want to execute one of the methods in the package from sqlplus. I want to do so without compiling the package into the oracle database.
Is this possible ? if so how ?
You can use an anonymous PLSQL block to run something without compiling to the database, but objects that don't exist in the database (package, function, stored procedure, type, etc) need to be declared within the PLSQL anonymous block.
That means you'll have to copy the method(s) you want to test from the package/etc, pasting them within the PLSQL block:
DECLARE
FUNCTION your_fnc() RETURN ... AS ...
BEGIN
SELECT your_fnc()
FROM DUAL;
END;