Writing a function that reads through a specific table and fills another based on the table that got read - oracle

What am trying to do is read through a table that is called transit and based on that table, I want to fill ord & ord 2 and if the function went ahead and executed with no problems, it would return true
CREATE OR REPLACE FUNCTION new_func
RETURN boolean
IS
BEGIN
INSERT INTO ORD(ordernum,cnum,snum,rec,ship,typ)
SELECT ordernum,cnum,snum,rec,ship,typ
FROM TRANSIT;
INSERT INTO ORD2(ordernum,cnum,snum,rec,ship,typ)
SELECT ordernum,cnum,snum,rec,ship,typ
FROM TRANSIT
return true;
END;

Oracle provides a convenient statement for this type action: INSERT ALL. This allows inserting rows into several tables with just one statement: (see example fiddle here)
insert all
into ord(ordernum,cnum,snum,rec,ship,typ)
values(ordernum,cnum,snum,rec,ship,typ)
into ord2(ordernum,cnum,snum,rec,ship,typ)
values(ordernum,cnum,snum,rec,ship,typ)
select ordernum,cnum,snum,rec,ship,typ
from transit;
In the above every row would be copied into both tables. This however is not required. The fiddle referenced above splits the tables between even and odd ordernum, but the split could be any on expression which eventuates true or false.
Initially my reply was just the above. But reading the comments it became apparent that OP may benefit from seeing examples of why this should be a procedure and not a function. (As could whoever required a function.)
As a developer I automatically assume a function can be called directly form SQL. In this case that is not true. This function can only be called form a plsql block. There are (at least) 2 reasons for this:
There is no boolean data type in SQL. Boolean data type is
strictly a plsql data type.
A function called by SQL cannot perform DML.
The referenced fiddle above also contains a example of each of these and what happens when called when called via SQL and by plsql.

Related

What is the purpose of this package?

I am new to Oracle PL/SQL. I have found this package and it is called from a trigger. I just cannot figure exactly what this simple looking package code is doing.
It is called from a trigger as below:
IF INSERTING THEN
i := STATE_PKG_OVERRIDE_CN.AffectedRows.COUNT+1;
STATE_PKG_OVERRIDE_CN.AffectedRows(i).IDU := :new.IDU;
STATE_PKG_OVERRIDE_CN.AffectedRows(i).cn := :new.cn;
This is the package. Can somebody please explain the basics of what is it doing? does it return a value? change a value? what is AffectedRows RIDARRAY or EMPTY ?
create or replace PACKAGE STATE_PKG_OVERRIDE_CN
AS
TYPE rowid_cn IS RECORD
(
idu dirxml.USR.IDU%TYPE,
cn dirxml.USR.CN%TYPE
);
TYPE RIDARRAY IS TABLE OF rowid_cn INDEX BY BINARY_INTEGER;
AffectedRows RIDARRAY;
EMPTY RIDARRAY;
END;
I have googled EMPTY but found nothing, i believe it is creating a table of type record. The trigger is passing in a value of cn or IDU am i am familiar with these two values. But what is the package doing? or returning ? im confused.
Cheers
This is a bespoke package belonging to your organisation. (That's why Google wasn't helpful for you.) We cannot tell you for sure what it does or how it's used. But we can guess.
The package has no procedures or functions, it just defines array variables of a bespoke type, which can be used by other program units such as triggers. The trigger you posted assigns values to the array AffectedRows. Presumably this trigger fires FOR EACH ROW. Likely there is another trigger on the same table firing AFTER STATEMENT which reads that array and does some processing, then assigns AffectedRows := EMPTY to reset the array.
The purpose of this infrastructure is to pass state across trigger actions. A common reason for doing this is work around a mutating table exception. They are risky because state cannot be guaranteed; for instance if a insert fails before the AFTER STATEMENT trigger fires the AffectedRows array is not re-initialised, so subsequent processing will be incorrect (or will fail).
Since 11g Oracle provides compound triggers which remove the need for this sort of package. Find out more.
To investigate further, first you want to check USER_TRIGGERS to find other triggers on the table which owns the trigger you mentioned. If that doesn't help or you want to see whether other tables also use this package run this query:
select *
from user_dependencies
where referenced_type = 'PACKAGE'
and referenced_name = 'STATE_PKG_OVERRIDE_CN'
It begins with the package which
declares a type ROWID_CN as a record that contains two values: IDU and CN
syntax requires to create another type (RIDARRAY) which is based on previously declared ROWID_CN
affectedrows and empty are arrays whose type is RIDARRAY. Basically, you can imagine them as a table that has two columns, IDU and CN
Now, a trigger: piece of code you posted says that those 3 lines are executed when someone inserts a row into a table the trigger is based on. For example,
create or replace trigger trg_biu_emp
before insert or update on emp
for each row
declare
i number;
begin
if inserting then ...
Apparently, there's some code that is executed when updating, or even deleting rows.
Anyway:
i := ... line counts number of elements in affectedrows array (which is declared in the package) and adds 1 to that number. For example, if there were 3 elements, i would be 4.
...affectedrows(i).idu := :new.idu enters a new row into the array. Its ordinal number is i (4 in our example). As you're inserting a row into the table, trigger knows IDU column's :new value and puts it into the array. For example, if you used
insert into emp (idu, cn) values (100, 'A')
then affectedrows(4).idu = 100, while affectedrows(4).cn = 'A'
It is, probably, something similar with EMPTY array. Google can't return anything useful, it is just a custom-made array.

User defined table types in Oracle

First of all usually I am working with MSSQL. But I have a stored procedure in MSSQL, which I need to use in Oracle now and since I am absolutely new to Oracle I have no idea at all how to do it correct.
I needed to use user defined table types in my MS SQL stored procedure because I am using "logical" tables in my stored procedure, which I also need to pass them to a dynamic sql statement within this procedure (using column names of "physical" tables as variables/parameters).
I've started to add the oracle function in a package I made before for another function. It looks like
TYPE resultRec IS RECORD
(
[result columns]
);
TYPE resultTable IS TABLE OF resultRec;
Function MyFunctionName([A LOT PARAMETERS]) RETURN resultTable PIPELINED;
I also described the layout of the tables (the user defined table types in MSSQL), which I want to use within this function in this package header.
So far so good, but now I don't really know where I have to declare my table variables or user defined table types. I also tried to put them in the package header, but if I am trying to use these tables in the package body, where I am describing my function, Oracle tells met, that the table or view does not exist.
I also tried it to describe the tables within the package body or in the block of my function, which looks like that:
FUNCTION MyFunctionName
(
[MyParameters]
)
RETURN resultTable PIPELINED is rec resultrec;
TYPE tableVariableA IS TABLE OF tableRecA;
TYPE tableVariableB IS TABLE OF tableRecB;
BEGIN
INSERT INTO tableVariableA
SELECT ColumnA, ColumnB FROM physicalTable WHERE[...];
[A LOT MORE TO DO...]
END;
But in this case Oracle also tells me, that it doesn't know the table or view.
I also tried a few more things, but at the end I wasn't able to tell Oracle what table it should use...
I would appreciate every hint, which helps me to understand how oracle works in this case. Thanks a lot!
You can't insert into a collection (e.g. PL/SQL table). You can use the bulk collect syntax to populate the collection:
SELECT ColumnA, ColumnB
BULK COLLECT INTO tableVariableA
FROM physicalTable
WHERE [...];
However, you might want to check this is an appropriate approach, since SQL Server and Oracle differ quite a bit. You can't use PL/SQL tables in plain SQL (at least prior to 12c), even inside your procedure, so you might need a schema-level type rather than a PL/SQL type, but it depends what you will do next. You might not really want a collection at all. Trying to convert T-SQL straight to PL/SQL without understanding the differences could lead you down a wrong path - make sure you understand the actual requirement and then find the best Oracle mechanism for that.

PL/SQL Creating Associative Array With Employee Id as Key

I am trying to create a hash with employee_id (NUMBER(6,0)) as the key and salary (NUMBER(8,2)) as the value.
For that I have created a INDEX-OF table(associative array) in PL/SQL (Oracle 11g) using the following definition:
TYPE emp_title_hash IS TABLE OF employees.salary%type
INDEX BY employees.employee_id%type;
I am getting the following compilation error:
Error(22,28): PLS-00315: Implementation restriction: unsupported table index type
I am aware in this case that the only supported type for the index is STRING or PLS_INTEGER. This seems to really restrictive. Why exactly has this been imposed in Oracle ? Is there a work around to get the above done ?
Appreciate your comments / suggestions.
As someone already pointed out, you can use "index by pls_integer", since pls_integer can contain any number(6,0) value.
Surely it would be nice to be able to use any possible type to index a pl/sql associative array, but I have always managed a way of writing a function that builds a string that identifies the object instance I want to use as index value.
So, instead of writing:
type TMyAssociativeArray is table of MyDataType index by MyIndexType;
I write:
Type TMyAssociativeArray is table of MyDataType index by varchar2(30);
then I write a function that calculates a unique string from MyIndexType:
function GetHash(obj MyIndexType) return varchar2;
and, having written this function I can use it to simulate an associative array indexed by MyIndexType object:
so I can write
declare
arr TMyAssociativeArray;
obj TMyDataType;
idx TMyDataType;
begin
....
arr(GetHash(idx)) := obj;
end;
Here I am getting out of the strict question you asked, and giving you an advice about a possible other way of obtaining a quick customer->salary lookup cache... This is the final purpose of your associative-array question, as I can read from your comment, so maybe this could be useful:
If you are using associative arrays to build a fast look-up mechanism, and if you can use the oracle 11g R2 new features, a easier way of obtaining this caching is to rely on the native RESULT_CACHE feature which has been introduced both for queries (by using the RESULT_CACHE hint) and for pl/sql functions.
for pl/sql functions you can create a function whose result value is cached like this one:
create or replace function cached_employee_salary(employee number)
return number RESULT_CACHE is
result number;
begin
select salary
into result
from employees e
where e.code = employee;
return result;
end;
the RESULT_CACHE keyword instructs oracle to keep an in-memory cache of the result values and to reuse them on subsequent calls (when the function is called with the same parameters, of course).
this cache has these advantages, compared to the use of associative arrays:
it is shared among all sessions: cached data is not kept in the private memory allocated for each session, so it wastes less memory
oracle is smart enough to detect that the function calculates its results by accessing the employees table and automatically invalidates the cached results if the table data is being modified.
Of course I suggest you to run some tests to see if this optimization, in your case gives some tangible results. it mostly depends on how complex is the calculation you make in your function.
You can also rely on an analogous feature introduced for SQL queries, triggered by the /+RESULT_CACHE/ hint:
select /*+RESULT_CACHE*/
salary
from employees e
where e.code = employee;
return result
this hint instructs oracle to store and reuse (on subsequent executions) the result set of the query. it will be stored too in memory.
actually this hint has also the advantage that, being the hint -syntactically speaking- a special comment, this query will continue working without any modifications even on servers < 11gR2, whereas, for the function cache version, you should use some "conditional compilation" magic to make it compile also with previous server versions (for which it would be a normal function without any result caching)
I Hope this helps.

When does a deterministic function use the previous calculated value?

Consider a deterministic function like:
CREATE OR REPLACE FUNCTION SCHEMA.GET_NAME(ss_id nvarchar2
) RETURN nvarchar2 DETERMINISTIC IS
tmpVar nvarchar2(500);
BEGIN
select name into tmpvar from logistics.organization_items
where id = ss_id ;
return tmpvar ;
END ss_name;
Using Toad I called the SCHEMA.GET_NAME(1) and it returns A. I then changed the value from the table from A to B and recalling the SCHEMA.GET_NAME(1) returned B.
It is a good result. But I'm afraid of the value not being updated according to this page in the documentation, which said:
When Oracle Database encounters a deterministic function in one of these contexts, it attempts to use previously calculated results when possible rather than reexecuting the function. If you subsequently change the semantics of the function, you must manually rebuild all dependent function-based indexes and materialized views.
In what situations would the value of GET_NAME(1) return an old cached value (A instead of B)?
If you select from a table then the results of your function are not deterministic. A deterministic system is one that will always produce the same output, given the same initial conditions.
It is possible to alter the information in a table, therefore a function that selects from a table is not deterministic. To quote from the PL/SQL Language Reference:
Do not specify this clause to define a function that uses package variables or that accesses the database in any way that might affect the return result of the function. The results of doing so are not captured if the database chooses not to reexecute the function.
In other words, Oracle does not guarantee that the results of the function will be accurate (they just might be). If your table is static, and unlikely to ever change, then it should be okay but this is not something I'd ever like to rely on. To answer your question, do not assume that Oracle will return anything other than the cached value within the same transaction/session.
If you need to speed this up there are two ways. Firstly, check that you have an index on ID!
Simply JOIN to this table. If your function is only this then there is no need for the function to exist.
Use scalar sub-query caching (not necessarily possible but worth the try).
select ( select get_name(:id) from dual )
from your_table
Oracle will create an in-memory hash of the results of the function, like a result cache. If you're executing the same function multiple times then Oracle will hit the cache rather than the function.
Ben's answer sums it up nicely, and I would just like to add that the way you used DETERMINISTIC keyword inside your function is not right - keeping in view that you are reading the value from a table and then returning the same to the user.
A deterministic function should be used in cases, where you are evaluating an expression over a fixed input, for example, when you need to return a substring, or upper/lower case for the input string. Programatically, you know that for the same input the lowercase function will always return the same value, and so you would like to cache the result (using deterministic keyword).
When you read a value from a table, Oracle has no way to know that the value in the column has not changed, and so it prefers to rexecute the function and not depend on the cached result (which makes sense)
Can you add a timestamp parameter to your function? Then pass in sysdate to the function from wherever you're calling it.
This way, you're effectively caching the result and you avoid running the function over and over when it generally returns the same value within a given transaction.
The remark of Erez is the answer I was looking for.
Before executing the query or plsql-unit you can with this solution force to execute the function again after you reseted the ret-values of the function (e.g. changing a package var).
I use this for:
select ...
from big_table_vw;
where
create view big_table_vw
as
select ... (analytical functions)
from big_table
where last_mutated >= get_date();
In my case the big_table_vw contains window-functions that prevents Oracle to push the predicate into the view.
This is a late follow-up to a long-answered question, but I just wanted to add that Oracle does provide a caching mechanism for functions with mutable dependencies. RESULT_CACHE is an alternative to DETERMINISTIC that allows Oracle to abandon cached function results any time a referenced object is modified.
This way one can cache costly calculations against rarely-updated objects with confidence that cached results will not return incorrect results.
Here's an example using mythological monsters:
CREATE TABLE MONSTER (
MONSTER_NAME VARCHAR2(100) NOT NULL PRIMARY KEY
);
INSERT INTO MONSTER VALUES ('Chthulu');
INSERT INTO MONSTER VALUES ('Grendel');
INSERT INTO MONSTER VALUES ('Scylla');
INSERT INTO MONSTER VALUES ('Nue');
COMMIT;
CREATE OR REPLACE PACKAGE MONSTER_PKG
IS
FUNCTION IS_THIS_A_MONSTER(P_MONSTER_NAME IN VARCHAR2)
RETURN BOOLEAN RESULT_CACHE;
END MONSTER_PKG;
/
CREATE OR REPLACE PACKAGE BODY MONSTER_PKG
IS
FUNCTION IS_THIS_A_MONSTER(P_MONSTER_NAME IN VARCHAR2)
RETURN BOOLEAN
RESULT_CACHE RELIES_ON (MONSTER)
IS
V_MONSTER_COUNT NUMBER(1, 0) := 0;
BEGIN
SELECT COUNT(*)
INTO V_MONSTER_COUNT
FROM MONSTER
WHERE MONSTER_NAME = P_MONSTER_NAME;
RETURN (V_MONSTER_COUNT > 0);
END;
END MONSTER_PKG;
/
When a scenario like the below occurs, any existing cache is invalidated and a new cache can then be rebuilt.
BEGIN
DBMS_OUTPUT.PUT_LINE('Is Kraken initially a monster?');
IF MONSTER_PKG.IS_THIS_A_MONSTER('Kraken')
THEN
DBMS_OUTPUT.PUT_LINE('Kraken is initially a monster');
ELSE
DBMS_OUTPUT.PUT_LINE('Kraken is not initially a monster');
END IF;
INSERT INTO MONSTER VALUES ('Kraken');
COMMIT;
DBMS_OUTPUT.PUT_LINE('Is Kraken a monster after update?');
IF MONSTER_PKG.IS_THIS_A_MONSTER('Kraken')
THEN
DBMS_OUTPUT.PUT_LINE('Kraken is now a monster');
ELSE
DBMS_OUTPUT.PUT_LINE('Kraken is not now a monster');
END IF;
END;
/
Is Kraken initially a monster?
Kraken is not initially a monster
Is Kraken a monster after update?
Kraken is now a monster

Getting the return value of a PL/SQL function via Hibernate

I have a PL/SQL function in an Oracle database that I can't change. This function takes a parameter which identifies an entity, creates a copy of that entity and then returns the ID of the copy. This looks something like
FUNCTION copy_entity(id IN NUMBER) RETURN NUMBER
I need to call this function from Hibernate. I tried creating a named SQL query with something similar to
CALL copy_entity(:id)
as the query, but from this I can't seem to get the return value of the function. Hibernate's "return-scalar" and similar options require a column name to return and I don't have a column name. This lead me to
SELECT copy_entity(:id) AS newEntityId
with "return-scalar" using newEntityId as column name, but this also did not work since Oracle then throws an exception that I can't call INSERT (to save the copy) in a SELECT.
Is there any way to get the return value of such a PL/SQL function? The function is actually much more complex and still required in other parts of the app, so re-writing it is not really an option.
I hope/think you can use an anonymous PL/SQL block:
begin
:myresult = copy_entity(:id);
end;
Now you have 'column name' myresult with the result.
I've never used hibernate so I hope it works. I don't know how flexible Hibernate is.
I think you are stuck using straight JDBC. The Hibernate documentation has this in the limitations section for Oracle:
For Oracle the following rules apply:
A function must return a result set.
The first parameter of a procedure
must be an OUT that returns a result
set. This is done by using a
SYS_REFCURSOR type in Oracle 9 or 10.
In Oracle you need to define a REF
CURSOR type. See Oracle literature for
further information.
Since this function accepts a number and returns a number you are out of luck with Hibernate and would probably be better off making a simple JDBC CallableStatement.

Resources