Can an Oracle Procedure produce output records? - sql

I am new to Oracle 10g and know that in MS SQL Server I can create a procedure such as the one below to generate an output record. Is this possible with a standard Oracle Procedure or do I need to use a package/function?
CREATE PROCEDURE SAMPLE_STORED_PROCEDURE
#USERNAME varchar(10)
AS
BEGIN
SELECT NAME as Output from Employee where Username = #USERNAME
END
GO
Thank you

Assuming that username is unique in employee (which seems like a reasonable guess to me), you probably want a function
CREATE OR REPLACE FUNCTION function_name( p_username IN employee.username%type )
RETURN employee.name%type
IS
l_name employee.name%type;
BEGIN
SELECT name
INTO l_name
FROM employee
WHERE username = p_username;
RETURN l_name;
END function_name;
You could also use a procedure with an OUT parameter
CREATE OR REPLACE PROCEDURE procedure_name( p_username IN employee.username%type,
p_name OUT employee.name%type )
AS
BEGIN
SELECT name
INTO p_name
FROM employee
WHERE username = p_username;
END procedure_name;
Generally, in PL/SQL you'd want to use a function whenever you want to write some sort of "getter" that reads data from the database and a procedure whenever you want to write some sort of "setter" that writes data to the database. So a function would make more sense here. Both functions and procedures can and should be organized into packages that allow you to group together related functionality. It would probably make sense, for example, to have a package that groups together all the functions and procedures related to adding, modifying, deleting, and reading information about employees.
If my original guess that username is not unique in employee is incorrect and you expect your select statement to return multiple rows, there are a number of different choices depending on exactly what you're going to be doing with that data. You can write a function that returns a sys_refcursor. You can write a function that returns a collection. You can write a pipelined table function. Without knowing more, however, it's impossible to know which of these options would make more sense in your case.

Related

PL/pgSQL function to return the output of various SELECT queries from different database

I have found this very interesting article: Refactor a PL/pgSQL function to return the output of various SELECT queries
from Erwin Brandstetter which describes how to return all columns of various tables with only one function:
CREATE OR REPLACE FUNCTION data_of(_table_name anyelement, _where_part text)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT * FROM ' || pg_typeof(_table_name)::text || ' WHERE ' || _where_part;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM data_of(NULL::tablename,'1=1 LIMIT 1');
This works pretty well. I need a very similar solution but for getting data from a table on a different database via dblink. That means the call NULL::tablename will fail since the table does not exists on the database where the call is made. I wonder how to make this work. Any try to connect inside of the function via dblink to a different database failed to get the result of NULL::tablename. It seems the polymorph function needs a polymorph parameter which creates the return type of the function implicit.
I would appreciate very much if anybody could help me.
Thanks a lot
Kind regards
Brian
it seems this request is more difficult to explain than I thought it is. Here is a second try with a test setup:
Database 1
First we create a test table with some data on database 1:
CREATE TABLE db1_test
(
id integer NOT NULL,
txt text
)
WITH (
OIDS=TRUE
);
INSERT INTO db1_test (id, txt) VALUES(1,'one');
INSERT INTO db1_test (id, txt) VALUES(2,'two');
INSERT INTO db1_test (id, txt) VALUES(3,'three');
Now we create the polymorph function on database 1:
-- create a polymorph function with a polymorph parameter "_table_name" on database 1
-- the return type is set implicit by calling the function "data_of" with the parameter "NULL::[tablename]" and a where part
CREATE OR REPLACE FUNCTION data_of(_table_name anyelement, _where_part text)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT * FROM ' || pg_typeof(_table_name)::text || ' WHERE ' || _where_part;
END
$func$ LANGUAGE plpgsql;
Now we make test call if everything works as aspected on database 1
SELECT * FROM data_of(NULL::db1_test, 'id=2');
It works. Please notice I do NOT specify any columns of the table db1_test. Now we switch over to database 2.
Database 2
Here I need to make exactly the same call to data_of from database 1 as before and although WITHOUT knowing the columns of the selected table at call time. Unfortunatly this is not gonna work, the only call which works is something like that:
SELECT
*
FROM dblink('dbname=[database1] port=[port] user=[user] password=[password]'::text, 'SELECT * FROM data_of(NULL::db1_test, \'id=2\')'::text)
t1(id integer, txt text);
Conclusion
This call works, but as you can see, I need to specify at least once how all the columns look like from the table I want to select. I am looking for any way to bypass this and make it possible to make a call WITHOUT knowing all of the columns from the table on database 1.
Final goal
My final goal is to create a function in database 2 which looks like
SELECT * from data_of_dblink('table_name','where_part')
and which calls internaly data_of() on database1 to make it possible to select a table on a different database with a where part as parameter. It should work like a static view but with the possiblity to pass a where part as parameter.
I am extremly open for suggestions.
Thanks a lot
Brian

User defined function can only have select statements

One of the main differences between UDF and SP is that UDF can only have select statements inside it and not insert/update/delete statements. Can someone please explain the reason behind this?The below function:
create function test(..)
...
BEGIN
insert into EMPLOYEE('22',12000,'john');
return 0;
END
is not valid. But why is this so?
The insert statement inside your function is missing the values keyword;
insert into EMPLOYEE('22',12000,'john');
should be
insert into EMPLOYEE values ('22',12000,'john');
though it's better to include the list of column names too. From the small part of the code you showed that is the only thing that is invalid. There could be other errors in the bits you have omitted. (If the first column in your table is numeric then you shouldn't be passing a string - it works but does implicit conversion and is best avoided. And if the column is a string, should it be really?)
UDF can only have select statements inside it and not insert/update/delete statements
That is not correct. You can have DML (insert/update/delete) in a function, but you can only call it from a PL/SQL context (though even in PL/SQL, it's often said that functions should query data with no side effects and only procedures should modify data; but that is not restricted by the language itself):
create table employee (id varchar2(3), salary number, name varchar2(10));
Table EMPLOYEE created.
create function test(unused number)
return number as
BEGIN
insert into EMPLOYEE (id, salary, name)
values ('22',12000,'john');
return 0;
END;
/
Function TEST compiled
declare
rc number;
begin
rc := test(42);
end;
/
PL/SQL procedure successfully completed.
select * from employee;
ID SALARY NAME
--- ---------- ----------
22 12000 john
But you cannot call it from a SQL context:
select test(42) from dual;
ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "MYSCHEMA.TEST", line 4
The documentation lists restrictions on functions called from SQL, and goes into more detail in this warning:
Because SQL is a declarative language, rather than an imperative (or procedural) one, you cannot know how many times a function invoked by a SQL statement will run—even if the function is written in PL/SQL, an imperative language.
If the function was allowed to do DML then you would have no control over how many times that DML was performed. If it was doing an insert, for instance, it might try to insert the same row twice and either duplicate data or get a constraint violation.
Just to summarise the comments, you can have DML inside a PL/SQL function.
What you can't do is call that function from SQL, because a select statement shouldn't also apply updates and deletes and so on as a hidden side effect.
For one thing, the SQL language reserves the right to execute the query any way it chooses, in any order, and with any caching it decides make use of. (It might even stop and restart during execution. That's up to the SQL engine.) Therefore your function might get called once or a hundred times, in any order, depending on the execution plan, and so the results would be unpredictable.

Save stored procedure output to new table without repeating table type

I want to call an existing procedure and store its table-typed OUT parameters to new physical tables, without having to repeat the definitions of the output types when creating the new tables. For example, if the procedure were
CREATE PROCEDURE MYPROC
(IN X INTEGER, OUT Y TABLE(A INTEGER, B DOUBLE, C NVARCHAR(25)))
LANGUAGE SQLSCRIPT AS BEGIN
...
END;
I would want to create a physical table for the output without repeating the (A INTEGER, B DOUBLE, C NVARCHAR(25)) part.
If I already had a table with the structure I want my result to have, I could CREATE TABLE MY_OUTPUT LIKE EXISTING_TABLE, but I don't.
If I already had a named type defined for the procedure's output type, I could create my table based on that type, but I don't.
If it were a subquery instead of a procedure output parameter, I could CREATE TABLE MY_OUTPUT AS (<subquery>), but it's not a subquery, and I don't know how to express it as a subquery. Also, there could be multiple output parameters, and I don't know how you'd make this work with multiple output parameters.
In my specific case, the functions come from the SAP HANA Predictive Analysis Library, so I don't have the option of changing how the functions are defined. Additionally, I suspect that PAL's unusually flexible handling of parameter types might prevent me from using solutions that would work for ordinary SQLScript procedures, but I'm still interested in solutions that would work for regular procedures, even if they fail on PAL.
Is there a way to do this?
It's possible, with limitations, to do this by using a SQLScript anonymous block:
DO BEGIN
CALL MYPROC(5, Y);
CREATE TABLE BLAH AS (SELECT * FROM :Y);
END;
We store the output to a table variable in the anonymous block, then create a physical table with data taken from the table variable. This even works with PAL! It's a lot of typing, though.
The limitation I've found is that the body of an anonymous block can't refer to local temporary tables created outside the anonymous block, so it's awkward to pass local temporary tables to the procedure this way. It's possible to do it anyway by passing the local temporary table as a parameter to the anonymous block itself, but that requires writing out the type of the local temporary table, and we were trying to avoid writing table types manually.
As far as I understand, you want to use your database tables as output parameter types.
In my default schema, I have a database table named CITY
I can create a stored procedure as follows using the table as output parameter type
CREATE PROCEDURE MyCityList (
OUT CITYLIST CITY
)
LANGUAGE SQLSCRIPT
AS
BEGIN
CITYLIST = SELECT * FROM CITY;
END;
After procedure is created, you can execute it as follows
do
begin
declare myList CITY;
call MyCityList(:myList);
select * from :myList;
end;
Here is the result where the output data is in a database table format, namely as CITY table
I hope this answers your question,
Update after first comment
If the scenario is the opposite as mentioned in the first comment, you can query system view PROCEDURE_PARAMETER_COLUMNS and create dynamic SQL statements that will generate tables with definitions in procedure table type parameters
Here is the SQL query
select
parameter_name,
'CREATE Column Table ' ||
procedure_name || '_'
|| parameter_name || ' ( ' ||
string_agg(
column_name || ' ' ||
data_type_name ||
case when data_type_name = 'INTEGER' then '' else
'(' || length || ')'
end
, ','
) || ' );'
from PROCEDURE_PARAMETER_COLUMNS
where
schema_name = 'A00077387'
group by procedure_name, parameter_name
You need to replace the WHERE clause according to your case.
Each line will have such an output
CREATE Column Table LISTCITIESBYCOUNTRYID_CITYLIST ( CITYID INTEGER,NAME NVARCHAR(40) );
The format for table name is concatenation of procedure name and parameter name
One last note, some data types integer, decimal, etc requires special code like excluding length or adding of scale , etc. Some are not handled in this SQL.
I'll try to enhance the query soon and publish an update

How to return a set of results from one Oracle subprogram to another?

This is a simplified version of what I'm trying to do, but let's say I want to select all employees whose manager is in a given department. I could have a SQL query like the following:
SELECT employees.id
FROM employees
WHERE employees.manager IN (SELECT managers.id
FROM managers
WHERE managers.dept = 12)
;
But let's say I want to abstract the manager subquery into a PL/SQL subprogram. How do I do that?
The stored procedures I've worked with (which are mostly written by other developers) tend to have out parameters that get mapped by PHP calling code into a PHP array. I don't really have any experience of calling one stored procedure from another.
What I'd like to do is to have something like this:
SELECT employees.id
FROM employees
WHERE employees.manager IN my_stored_procedure(12)
;
and then my_stored_procedure would output the set of manager IDs for the input parameter (which is 12 in this example).
It is not possible to do exactly as you have posted, but if the selection of managers are not straightforward, you could abstract it through a view or make use of a function that returns a table, like this:
SELECT employees.id
FROM employees
WHERE employees.manager IN (SELECT * from TABLE(get_managers_from_dept(12)));
In this link there is an example of that approach:
Function or Procedure for an IN clause
To Call A stored Proc from another Stored Proc you just need to call it from the Main Proc as mentioned below. This Main Proc can be called /initiated by a PHP Code.
PROCEDURE some_sp
AS
BEGIN
some_other_sp('parm1');
END;
Although less straight-forward, You can do accomplish it by using dynamic sql. This is the structure of your stored procedure. It returns a comma separated list of manager_ids for a given department.
CREATE OR REPLACE FUNCTION my_stored_procedure(
p_dept NUMBER)
RETURN VARCHAR2
IS
v_manager_list VARCHAR2(1000);
BEGIN
SELECT m.id INTO v_manager_list FROM managers m WHERE m.dept = p_dept;
RETURN '('||v_manager_list||')';
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN '(NULL)';
END;
/
Now you cannot use this to directly query as ...IN my_stored_procedure(12),
rather you must use a dynamic fetch into a collection.
SET SERVEROUTPUT ON;
DECLARE
TYPE v_emp_type
IS
TABLE OF employees.id%TYPE;
v_emp v_emp_type;
BEGIN
EXECUTE IMMEDIATE 'SELECT employees.id
FROM employees
WHERE employees.id IN '|| my_stored_procedure(100) BULK COLLECT INTO v_emp ;
FOR i IN v_emp.FIRST..v_emp.LAST
LOOP
DBMS_OUTPUT.PUT_LINE(v_emp(i));
END LOOP;
END;
/

Oracle Apex: ORA-24344 Success with compilation error

I am currently working on an oracle database in APEX.
First of all, I do not have access to any tools. I can only use what is provided in apex.oracle.com and I am writing the script in NotePad++ before uploading it over there. It's a school assignment, so I am not allowed to use any other tools, even if there is something that would make things easier.
I am creating a long script that creates a bunch of tables in a database, creates a bunch of records in every table, and creates all the constraints. This part works fine. Now i must create several functions and procedures in the same script, after the other stuff.
Every single time I try to create one, I get this: ORA-24344: success with compilation error
Also, the other instructions in the script after this error are not executed. Everything that was before the error works fine.
Here is one of the functions that create this error:
CREATE OR REPLACE FUNCTION SP_03Recherche (titre_art VARCHAR2, nom_aut VARCHAR2, type_art VARCHAR2)
RETURN CURSOR
IS
CURSOR articles (p_titre_art VARCHAR2, p_nom_aut VARCHAR2, p_type_art VARCHAR2) IS
SELECT * FROM BI_Articles INNER JOIN (BI_ArticlesAuteurs INNER JOIN BI_Auteurs ON BI_ArticlesAuteurs.AuteurID = BI_Auteurs.AuteurID) ON BI_Articles.ISBN = BI_ArticlesAuteurs.ISBN
WHERE (Titre LIKE p_titre_art) AND ((Nom LIKE p_nom_aut) OR (Prenom LIKE p_nom_aut)) AND TypeArticle LIKE type_art;
BEGIN
RETURN articles(titre_art, nom_aut, type_art);
END;
sys_refcursor is the data type that you seemingly want to return. That's a generic type for a weak ref cursor. My guess is that you want something like
CREATE OR REPLACE FUNCTION SP_03Recherche (
p_titre_art VARCHAR2,
p_nom_aut VARCHAR2,
p_type_art VARCHAR2
)
RETURN sys_refcursor;
IS
l_rc sys_refcursor;
BEGIN
OPEN l_rc
FOR select *
from bi_articles art
inner join BI_ArticlesAuteurs art_auth
on (art.isbn = art_auth.isbn)
inner join BI_Auteurs auth
on (art_auth.auteurID = auth.auteurID)
where titre LIKE p_titre_art
and (nom LIKE p_nom_auth or
prenum LIKE p_nom_auth)
and typearticle LIKE type_art;
RETURN l_rc;
END;
Now, a few suggestions
Doing a select * is almost always a bad idea. Particularly when you are joining multiple tables. Do you really, really want the structure of the result set to change every time someone adds an additional column to any of three tables? That seems unlikely.
Use aliases everywhere. Looking at the code, I have no idea what table titre comes from. Or nom or prenum or typearticle. Use aliases to identify which table a column comes from. That makes your code clearer and makes it more robust when additional tables or columns are added in the future which may have the same column names (multiple entities might have a nom column for example).