I'm trying to create an abstraction layer to mimic DB2 functions in Oracle 11g. The goal is to have the customer who is current running DB2 queries repoint to an Oracle abstraction layer without having to change their syntax.
I created the following function under the ABC schema:
create or replace function ABC.timestamp(p_date in date) return date is
begin
return p_date;
end;
When I try to use it in a SQL statement, I get an error:
select timestamp(current_date) from dual;
ORS-06553: PLS-222: no function with name 'TIMESTAMP' exists in this scope
If I call out the schema explicitly it works:
select ABC.timestamp(current_date) from dual;
I also have a logon trigger that explicitly sets the session's current_schema to ABC. The only complication I can see is that TIMESTAMP is a keyword in Oracle, but it allows me to create this function with that name.
Any way to get this to work?
I don't think that you can workaround it "as is". But, if you use double quotes around function name (which is yet another stupid thing some people do - and, look at me, I'll do it now), it'll work without owner's name:
SQL> create or replace function "timestamp" (p_date in date) return date is
2 begin
3 return p_date;
4 end;
5 /
Function created.
SQL> select "timestamp"(current_Date) from dual;
"TIMESTA
--------
11.01.18
SQL>
However, it means that you'll ALWAYS have to use double quotes when referencing that function.
If I were you, I'd change function name. As you can see, the fact that you can do something doesn't mean that you should do it.
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...
PostgreSQL supports a RETURNING clause, for instance as in
UPDATE some_table SET x = 'whatever' WHERE conditions RETURNING x, y, z
and MSSQL supports a variant of that syntax with the OUTPUT clause.
However Oracle "RETURNING INTO" seems intended to placing values in variables, from within the context of a stored procedure.
Is there a way to have an SQL equivalent to the one above that would work in Oracle, without involving a stored procedure ?
Note: I am looking for a pure-SQL solution if there exists one, not one that is language-specific, or would require special handling in the code. The actual SQL is dynamic, the code that makes the call is database-agnostic, with only the SQL being adapted.
Oracle does not directly support using the DML returning clause in a SELECT statement, but you can kind of fake that behavior by using a WITH function. Although the below code uses PL/SQL, the statement is still a pure SQL statement and can run anywhere a regular SELECT statement can run.
SQL> create table some_table(x varchar2(100), y number);
Table created.
SQL> insert into some_table values('something', 1);
1 row created.
SQL> commit;
Commit complete.
SQL> with function update_and_return return number is
2 v_y number;
3 --Necessary to avoid: ORA-14551: cannot perform a DML operation inside a query
4 pragma autonomous_transaction;
5 begin
6 update some_table set x = 'whatever' returning y into v_y;
7 --Necessary to avoid: ORA-06519: active autonomous transaction detected and rolled back
8 commit;
9 return v_y;
10 end;
11 select update_and_return from dual;
12 /
UPDATE_AND_RETURN
-----------------
1
Unfortunately there are major limitations with this approach that may make it impractical for non-trivial cases:
The DML must be committed within the statement.
The WITH function syntax requires both client and server versions of 12.1 and above.
Returning multiple columns or rows will require more advanced features. Multiple rows will require the function to return a collection, and the SELECT portion of the statement will have to use the TABLE function. Multiple columns will require a new type for each different result set. If you're lucky, you can use one of the built-in types, like SYS.ODCIVARCHAR2LIST. Otherwise you may need to create your own custom types.
You can do it in SQL, not need to pl/sql, and this depends on your tool and/or language. Here's an example in sqlplus:
SQL> create table t0 as select * from dual;
Table created.
SQL> var a varchar2(2)
SQL> update t0 set dummy='v' returning dummy into :a;
1 row updated.
SQL> print a
A
--------------------------------
v
I have been racking my brain for some time but I am unable to find a way to retrieve the Operator definition in either Toad or SQL Developer.
We have a user defined operator in our support Application Database and I can see it in all_objects dictionary but I am not able to retrieve its definition.
Is there a way to do that?
I have already been through the oracle documentation but I can't seem to find anything about operators, except how to create one.
Oracle Link 1
Oracle Link 2
Simply use dbms_metadata package and specifically get_ddl() function to extract DDL for an operator as you would use it to extract DDL for tables, views, and other database objects:
Here is an example:
create or replace function multf(
arg1 in number,
arg2 in number
) return number
is
begin
return arg1 * arg2;
end;
/
create or replace operator mult
binding (number, number)
return number
using multf;
/
select dbms_metadata.get_ddl('OPERATOR', 'MULT')
from dual
Result of the query:
OP_DDL
--------------------------------------------------------
CREATE OR REPLACE OPERATOR "NK"."MULT" BINDING
(NUMBER, NUMBER) RETURN NUMBER
USING "MULTF"
First, I want to make it clear that the question is not about the materialized views feature.
Suppose, I have a table function that returns a pre-defined set of columns.
When a function call is submitted as
SELECT col1, col2, col3
FROM TABLE(my_tfn(:p1))
WHERE col4 = 'X';
I can evaluate the parameter and choose what queries to execute.
I can either open one of the pre-defined cursors, or I can assemble my query dynamically.
What if instead of evaluating the parameter I want to evaluate the text of the requesting query?
For example, if my function returns 20 columns but the query is only requesting 4,
I can assign NULLs to remaining 16 clumns of the return type, and execute fewer joins.
Or I can push the filter down to my dynamic query.
Is there a way to make this happen?
More generally, is there a way to look at the requesting query before exuting the function?
There is no robust way to identify the SQL that called a PL/SQL object.
Below is a not-so-robust way to identify the calling SQL. I've used code like this before, but only in special circumstances where I knew that the PL/SQL would never run concurrently.
This seems like it should be so simple. The data dictionary tracks all sessions and running SQL. You can find the current session with sys_context('userenv', 'sid'), match that to GV$SESSION, and then get either SQL_ID and PREV_SQL_ID. But neither of those contain the calling SQL. There's even a CURRENT_SQL in SYS_CONTEXT, but it's only for fine-grained auditing.
Instead, the calling SQL must be found by a string search. Using a unique name for the PL/SQL object will help filter out unrelated statements. To prevent re-running for old statements, the SQL must be individually purged from the shared pool as soon as it is found. This could lead to race conditions so this approach will only work if it's never called concurrently.
--Create simple test type for function.
create or replace type clob_table is table of clob;
--Table function that returns the SQL that called it.
--This requires elevated privileges to run.
--To simplify the code, run this as SYS:
-- "grant execute on sys.dbms_shared_pool to your_user;"
--(If you don't want to do that, convert this to invoker's rights and use dynamic SQL.)
create or replace function my_tfn return clob_table is
v_my_type clob_table;
type string_table is table of varchar2(4000);
v_addresses string_table;
v_hash_values string_table;
begin
--Get calling SQL based on the SQL text.
select sql_fulltext, address, hash_value
bulk collect into v_my_type, v_addresses, v_hash_values
from gv$sql
--Make sure there is something unique in the query.
where sql_fulltext like '%my_tfn%'
--But don't include this query!
--(Normally creating a quine is a challenge, but in V$SQL it's more of
-- a challenge to avoid quines.)
and sql_fulltext not like '%quine%';
--Flush the SQL statements immediately, so they won't show up in next run.
for i in 1 .. v_addresses.count loop
sys.dbms_shared_pool.purge(v_addresses(i)||', '||v_hash_values(i), 'C');
end loop;
--Return the SQL statement(s).
return v_my_type;
end;
/
Now queries like these will return themselves, demonstrating that the PL/SQL code was reading the SQL that called it:
SELECT * FROM TABLE(my_tfn) where 1=1;
SELECT * FROM TABLE(my_tfn) where 2=2;
But even if you go through all this trouble - what are you going to do with the results? Parsing SQL is insanely difficult unless you can ensure that everyone always follows strict syntax rules.
PROCEDURE ORG_spGetType
(
v_TypeId IN NUMBER DEFAULT NULL
)
AS
BEGIN
SELECT *
FROM ORG_Type
WHERE TypeId = v_TypeId ;
END;
While Running above proceedure in oracle10g usinq eclipse platform sql editor getting error like "ORA-0675:package or function ORG_SPGETTYPE is in an invalid state"
That's not much of a surprise; that procedure is invalid. When compiling a procedure or any other PL/SQL block you should check whether it's compiled correctly.
If you're compiling on the command line, i.e. in SQL*Plus you can use the SHOW command and use the ERRORS variable, which:
displays the line and column number of the error (LINE/COL) as well as the error itself (ERROR).
After you've compiled the procedure type the following:
show errors
Alternatively, you can use the USER_ERRORS system view which will show you a list of all errors, which the schema you're in can see.
select *
from user_errors
where name = 'ORG_SPGETTYPE'
The actual reason for your error is that you cannot simply SELECT in PL/SQL. If you do you're not giving PL/SQL the ability to use the results of your SELECT statement in any way and so it won't allow you to do so. If you're selecting something you need to do something with it; what that something is is up to you.
There are numerous ways of doing this but let's say the table ORG_TYPE is unique on the column TYPEID and you want to return one column as an OUT parameter. Your procedure would then look like this:
create or replace procedure org_spgettype (
P_typeid in org_type.type_id%type
, P_some_column out org_type.some_column%type
) is
begin
select some_column into P_some_column
from org_type
where typeid = P_typeid;
end;
/
show errors
Please note a number of things:
There doesn't seem to be any need for the DEFAULT NULL if this is your primary key; it can't be NULL so you don't want to allow this possibility.
I've declared your parameters as the type of the column; this enables you to change the column without having to recode everything.
There is so much more to explain about all of this (exceptions for a start) so I would highly recommend taking some basic tutorials first or asking a colleague/friend for help.
You would have a 'compiled with warnings' message when creating the procedure. You can query the user_errors view to see what problems are reported for your peocedure. You will see something like 'PLS-00428: an INTO clause is expected in this SELECT statement', because you are not selecting into something.
The documentation shows how to do that. You need to declare variables to select into. In this case you could declare a rowtype variable:
CREATE OR REPLACE PROCEDURE ORG_spGetType (v_TypeId IN NUMBER DEFAULT NULL) AS
l_org_type org_type%rowtype;
BEGIN
SELECT *
INTO l_org_type
FROM ORG_Type
WHERE TypeId = v_TypeId ;
-- do something with l_org_type
END;
/
Note that this can get a no_data_found exception if there are no matching rows, or too_many_rows if there are multiple matches for the passed ID.
But it isn't clear what you're planning to do with the retrieved data. This currently does nothing at all with it, it's just selected and then forgotten. The name of the procedure suggests you want to return all or part of the data from the table to the caller. If it's only one field value then this should probably be a function rather than a procedure. Or you could add out parameters to put the values into (as Ben shows), or return a refcursor. Depends what you need.
The default null, however, maybe suggests you sometimes expect more than one result, maybe the whole table if no value is passed - though your where clause won't find anything if v_typeid is null.