I have recently had some input from a colleague regarding committing in a stored function. Whether we use procedures or functions to execute offline / batch logic in an Oracle database is mostly a matter of taste in our application. In both cases, we return a code either as function result, or as procedure OUT parameter. We usually require those offline / batch routines to be called from PL/SQL, not from SQL:
-- good
declare
rc number(7);
begin
rc := our_function(1, 2, 3);
end;
-- less good
select our_function(1, 2, 3) from dual;
The reason why the latter is less good is because our_function may commit the transaction for performance reasons. This is ok for a batch routine.
The question is: Are there any best practices around this topic, or some special keywords that prevent such functions from being used in SQL statements on a compiler-level? Or should we avoid functions for batch operations and only use procedures?
You can use RESTRICT_REFERENCES to indicate that a function won't read/write package or database state.
CREATE PACKAGE t_pkg AS
FUNCTION showup (msg VARCHAR2) RETURN VARCHAR2;
PRAGMA RESTRICT_REFERENCES(showup, WNDS, RNDS);
END t_pkg;
/
-- create the package body
CREATE OR REPLACE PACKAGE BODY t_pkg AS
FUNCTION showup (msg VARCHAR2) RETURN VARCHAR2 IS
v_val varchar2(1);
BEGIN
select dummy into v_val from dual;
RETURN v_val;
END;
END t_pkg;
/
It used to be the case that SQL wouldn't allow you to call a function unless it made such a promise, but that restriction got dropped.
I'd prefer to make it a differentiator between a procedure and a function. It's worth bearing in mind that if a PL/SQL function raises a NO_DATA_FOUND exception, a calling SQL statement does not fail (as no data found isn't an SQL error). So I prefer to use procedures unless the object is specifically designed to be called from SQL.
Are there any best practices around this topic, or some special
keywords that prevent such functions from being used in SQL statements
on a compiler-level?
If you use a function that requires a transaction (and therefore a commit), AFAIK you will not be able to call it from a SELECT, unless the function uses an AUTONOMOUS TRANSACTION (otherwise you get a ORA-14551 cannot perform a DML operation inside a query).
See also: ORA-14551: cannot perform a DML operation inside a query
So, having a function that requires a transaction itself should prevent it from being called from a SELECT.
From my point of view there is no way to accomplish this.Although you can avoid runtime errors like "ORA-14551" by using PRAGMA RESTRICT_REFERENCES in "our_function(1, 2, 3)" to be sure that it is safe to use it the SQL query,but you can't prevent it from using in sql at the compiler-level
Related
I have a requirement to return a subset of rows from a table using FOR UPDATE SKIP LOCKED. Based on application parameters this subset may or may not be ordered by a column. I can't use ROWNUM since the numbers are assigned before SKIP LOCKED happens, so using cursors and FETCH ... LIMIT seems to be the way to go.
That works using an anonymous PL/SQL block, but I need to expose the data back to the java application. The most straightforward way would be to use a table function, so I can just do SELECT * FROM table(my_function(<params>)).
I tried a standard table function returning a collection first, but I got the error [902] ORA-00902: invalid datatype. This is roughly what I had in place:
Package specification:
CREATE OR REPLACE PACKAGE ACTIVITY_UTILS AS
TYPE ActivityList IS TABLE OF ACTIVITY_TABLE%ROWTYPE;
FUNCTION activity_batch(batch_size IN INTEGER, order_by_source IN VARCHAR2)
RETURN ActivityList;
END ACTIVITY_UTILS;
Package body:
CREATE OR REPLACE PACKAGE BODY ACTIVITY_UTILS AS
FUNCTION activity_batch(batch_size IN INTEGER, order_by_source IN VARCHAR2)
RETURN ActivityList
IS
batch ActivityList := ActivityList();
selectStatement VARCHAR2(200);
TYPE CursorType IS REF CURSOR;
activitiesCursor CursorType;
BEGIN
IF UPPER(order_by_source) = 'TRUE' THEN
selectStatement := 'SELECT * FROM ACTIVITY_TABLE ORDER BY source FOR UPDATE SKIP LOCKED';
ELSE
selectStatement := 'SELECT * FROM ACTIVITY_TABLE FOR UPDATE SKIP LOCKED';
OPEN activitiesCursor FOR selectStatement;
FETCH activitiesCursor BULK COLLECT INTO batch LIMIT batch_size;
CLOSE activitiesCursor;
RETURN batch;
END activity_batch;
While debugging the ORA-00902 error I ran into this question:
Return collection from packaged function for use in select
My (limited) understanding was I was trying to use a PL/SQL type on plain SQL, which is not allowed. I tried using a pipelined table function, as mentioned in the answer, but then I got the error ORA-14551: cannot perform a DML operation inside a query.
This seemed odd, is SELECT ... FOR UPDATE considered DML? At any rate, I noticed I could workaround this by using pragma autonomous_transaction, but that defeats the purpose of having FOR UPDATE SKIP LOCKED.
My question is, is this requirement achievable at all using functions, or would I have to use a procedure with an OUT parameter?
Option 1: create a function (or procedure) that returns a cursor and let your java application fetch it normally.
Option 2: Use Implicit statement results: in this case your java application can run something like call proc() where proc returns implicit statement results.
PS. It's not a good idea to hide DML under SQL select...
Is there any benefit to including the "return" keyword?
Several websites show examples of pipelined functions in Oracle using the "return" keyword after they have done all of the pipeline statements.
Functions not including return will work just fine, such as this:
CREATE OR REPLACE
FUNCTION return_test
RETURN qt_01_t PIPELINED IS
BEGIN
PIPE ROW(qt_01_row_t('pipe this'));
END;
VS.
CREATE OR REPLACE
FUNCTION return_test
RETURN qt_01_t PIPELINED IS
BEGIN
PIPE ROW(qt_01_row_t('pipe this'));
RETURN;
END;
Oracle documentation for pipelined functions
Ask Tom question on pipelined functions
According to the Oracle 11g documentation and the Oracle 12c documentation:
A pipelined table function must have a RETURN statement that does not
return a value. The RETURN statement transfers the control back to the
consumer and ensures that the next fetch gets a NO_DATA_FOUND
exception.
(this section is identical to the 10g documentation your link points to)
So although the parser might accept a pipelined function without a RETURN statement, I guess you should always add it to be on the safe side.
I am new to DB2. I want to execute an anonymous black in toad.
BEGIN ATOMIC
DECLARE TEMP_SCHEMA VARCHAR(12) ;
SET TEMP_SCHEMA = 'SCHEMA1';
SELECT * FROM TEMP_SCHEMA.TABLE_NAME
WHERE 1=1
WITH UR;
END;
I am getting following error:
20159: [IBM][DB2/AIX64] SQL20159W The isolation clause is ignored because of the statement context.
Can you please help.
According to the documentation at https://www.ibm.com/support/knowledgecenter/en/SSMKHH_10.0.0/com.ibm.etools.mft.doc/ak04940_.htm?view=embed , "If ATOMIC is specified, only one instance of a message flow (that is, one thread) is allowed to execute the statements of a specific BEGIN ATOMIC... END statement (identified by its schema and label), at any one time. If no label is present, the behavior is as if a zero length label had been specified.
The BEGIN ATOMIC construct is useful when a number of changes need to be made to a shared variable and it is important to prevent other instances seeing the intermediate states of the data."
Using ATOMIC in a stored procedure means that your code will execute as a singleton, providing maximal isolation. That would be in direct conflict with your "WITH UR" isolation option. Even though you are using the ATOMIC keyword in a script, not in a stored procedure, DB2 still treats it as a single thread, so it will complain if you include query hints that attempt to lower the isolation level.
After removing the ATOMIC keyword, you are getting the token error because your SELECT * FROM TEMP_SCHEMA.TABLE_NAME
WHERE 1=1 statement is attempting to return a result set to Toad from inside a BEGIN block. Unfortunately, this is not possible in DB2. As soon as you have any procedural code, which forces you to utilize a BEGIN block, DB2 steadfastly refuses to return data to the client. The only way that I found to return results from inside a BEGIN block is to place the BEGIN block in a stored procedure, and then use a CURSOR to return the result set to Toad, e.g.,
BEGIN
DECLARE C1 CURSOR WITH RETURN WITH HOLD FOR SELECT * FROM .EMPLOYEE;
OPEN C1;
END;
Note that you must enclose the CURSOR code in an inner BEGIN block, which DB2 requires when sending a result set back to the client WITH HOLD.
If you want to return the value of a variable to Toad from your prototype stored procedure, you can use this approach:
BEGIN
DECLARE C1 CURSOR WITH RETURN WITH HOLD FOR
SELECT * FROM TABLE(SELECT * FROM (VALUES(<variable goes here>))
AS TEMP(<descriptive name for the variable goes here>)) AS TEMP1;
OPEN C1;
END;
To summarize, in order to use the Anonymous Block as a prototyping vehicle in a development tool such as Toad, you have to wrap it in a stored procedure if you want to return any results, and you must use a CURSOR embedded in an inner BEGIN block to do so. Its unfortunate that DB2 is so much more cumbersome than MS SQL Server in this regard.
I need a generalized method to get list of runtime parameters (values) when I call a procedure. I need something similar to the $$PLSQL_UNIT that returns the name of the running procedure.
(plsql Oracle 10g)
E.g. look at this sample procedure:
(it simply prints its own name and parameters )
CREATE OR REPLACE PROCEDURE MY_PROC(ow in varchar2, tn IN varchar2)
IS
BEGIN
dbms_output.put_line('proc_name: '||$$PLSQL_UNIT||' parameters: '|| ow||' '||tn );
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERRORE: ' ||SQLERRM);
END MY_PROC;
/
Running procedure produces the following output:
SQL>
1 BEGIN
2 IBAD_OWN.MY_PROC('first_par', 'second_par');
3 END;
4 /
proc_name: MY_PROC parameters: first_par second_par
PL/SQL procedure successfully completed.
I'm not satisfy because I can't copy and paste in all my procedures because I have to hard code each procedure to set their right parameter variables.
Thanks in advance for the help.
It isn't possible to dynamically retrieve the values of parameters passed to a procedure in Oracle PL/SQL. The language simply isn't designed to handle this kind of operation.
Incidentally, in a procedure that is located within a package, $$PLSQL_UNIT will only return the package's name. I find it's better to define a consistently-named constant within each procedure that contains the procedure's name.
When I wanted the same functionality as yours I didn't find any good built-in solution.
What I did is: wrote DB-level trigger which modifies original body of function/procedure/package.
This trigger adds immediatly after "begin" dynamically generated piece of code from "user_arguments".
Plus, after that I include into this trigger the code, that logs calls of procs when exception occures.
Plus, you can trace procs calls, and many more interisting things.
But this solution works fine only for preproduction because performance decreases dramatically.
PS. Sorry for my bad English.
I have a stored procedure that I have defined in Oracle. In that procedure I need to return a recordset. To do this, I am using the SYS_REFCURSOR, which works great inside of Oracle (and with cx_Oracle, for that matter). In my application I am using SqlAlchemy scoped sessions, to support multi-threading.
How can I use the scoped session to return the REF CURSOR? The only way I have been able to get this to work is by declaring an out cursor with the cursor that is active in the session and then executing the stored procedure, like below:
sql = """
BEGIN
example('%s', '%s', '%s', :cur);
END;
""" % (cid, show, type)
conn = sa_tool.session.connection()
in_cur = conn._Connection__connection.cursor()
out_cur = conn._Connection__connection.cursor()
in_cur.execute(sql, cur=out_cur)
results = out_cur.fetchall()
Ideally, I would like to avoid using the connection object in this way, and execute the procedure while letting SqlAlchemy manage the cursors. If that is not possible, is there a reason that the fetch would take so long?
Thanks
I saw this question which was not answered and I decided to jump in. First of all, let me just say that for your scenario there is no better option than SYS_REFCURSOR. Of course, you have alternatives.
An Oracle Cursor is a memory area location where an instruction to execute a SQL statement is stored. A Ref cursor is just a pointer to the cursor location. SYS_REFCURSOR is an specific oracle defined typed of ref cursor. So when you return a SYS_REFCURSOR variable to a client, you are returning a pointer towards the memory location where the instruction to execute the SQL resides. Your client can now execute the instruction using the FETCH operation and get the rows. So this is the best possible way to return a result set to the client.
As an alternative, you might use a PIPELINED FUNCTION, but I can assure you that you won't get any better performance. AskTom has a great explanation about this comparison in this article
https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:9532870400346178516
Another scenario is whether you want to analyse where the time is consumed, either in the EXECUTE phase or in the FETCH one. If you have a huge time in FETCH, perhaps you might consider transfer the data in another way to the client. If you have a problem in EXECUTION, then you need to make a tuning exercise over your procedure.