RESTRICT_REFERENCES and triggers - sql

I add a PRAGMA RESTRICT_REFERENCES to a procedure in a package (for example, RNPS). That procedure implementation inserts a row in a table.
That table has a before insert trigger. that trigger reads a variable from a package and puts it :new.my_column.
I can compile the package body without problems, even though it seems like it is actually reading values from a package variable.
When I execute the procedure, it actually works. But this is the development eviroment, where there are no multiple simultaneous connections usually. I'm afraid that this could fail in the production enviroment.
So, should I be worried, or will this actually work?
Example code:
CREATE TABLE MY_TABLE
(
ID VARCHAR2(20) NOT NULL
, USER_ID VARCHAR2(50)
, CONSTRAINT MY_TABLE_PK PRIMARY KEY
(
ID
)
ENABLE
);
CREATE OR REPLACE PACKAGE PUSER IS
PROCEDURE saveUser(
pUserId VARCHAR2
);
PRAGMA RESTRICT_REFERENCES (saveUser, WNDS, RNDS, RNPS);
FUNCTION getUser RETURN VARCHAR2;
PRAGMA RESTRICT_REFERENCES (getUser, WNDS, RNDS, WNPS);
END PUSER;
CREATE OR REPLACE PACKAGE BODY PUSER AS
userId VARCHAR2(50);
PROCEDURE saveUser(
pUserId VARCHAR2
) IS
BEGIN
userId := pUserId;
END saveUser;
FUNCTION getUser RETURN VARCHAR2 IS
BEGIN
RETURN userId;
END getUser;
END PUSER;
CREATE OR REPLACE PACKAGE MY_PACKAGE IS
PROCEDURE insertMyTable(
pId VARCHAR2
);
PRAGMA RESTRICT_REFERENCES (insertMyTable, RNPS);
END MY_PACKAGE;
CREATE OR REPLACE PACKAGE BODY MY_PACKAGE AS
PROCEDURE insertMyTable(
pId VARCHAR2
) IS
BEGIN
INSERT INTO MY_TABLE(id) VALUES(pId);
END insertMyTable;
END MY_PACKAGE;
CREATE OR REPLACE TRIGGER MY_TABLE_TRIGGER
BEFORE INSERT ON MY_TABLE FOR EACH ROW
DECLARE
BEGIN
:new.USER_ID := PUSER.getUser;
END MY_TABLE_TRIGGER;
Edit: I know that RESTRICT_REFERENCES is deprecated, but knowing this would still be useful for already existing code.

RNPS is useful only for functions as it tells the compiler that the return value of a function will not have changed between calls if in the meanwhile no package state (or database state for the other pragmas) has been changed. It allows for caching, but in a more implicit way than with RESULT_CACHE or DETERMINISTIC (which is now the preferred way to tell the optimizer what to expect).
Since you use it on a procedure, it does not really matter. You can safely proceed without changing package specifications. However, since you (or your successor) might ask yourselves the same question a year from now, you might as well change the packages right now while you're at it.

Related

Save and return multiple rows within function pl/sql oracle

declare
type t_trayIds is table of number(38,0) index by binary_integer;
v_trayIdsTable t_trayIds;
create or replace function F_getTrayIdByDiameter(v_diameterInCm tray.diameterincm%TYPE)
return t_trayIds
as
v_trayIdsTable t_trayIds := null;
begin
select t.trayid into v_trayIds from tray t
where t.diameterincm = v_diameterincm;
return v_trayIdsTable;
end;
So what I want is, to ask for all Tray IDs with a specific Diameter and store them in an Array or Table. In Java I used ArrayList. I want to return the Table in the end to pass the result onto another function. The above code doesn't seem to work. SQL Developer gives me a syntax error at the word create.
Can someone help?
Your code fails because you are mixing a declare section that must be followed by a begin section, with a "create or replace function" that is a standalone statement to create objects;
If you want to declare a PL/SQL table type and make it public,
you must put it in a package specification, so it can be visible by any function (I also declare here the function F_getTrayIdByDiameter, to make it visible):
CREATE OR REPLACE package utils is
type t_trayIds is table of number(38,0) index by binary_integer;
function F_getTrayIdByDiameter(v_diameterInCm tray.diameterincm%TYPE) return t_trayIds;
end utils;
/
besides, you can't use SELECT INTO syntax, because
select col into var
can be used only for single row, not for lists;
in PL/SQL, if you want to manage multiple rows, you have to use a cursor;
so, if you want to create your PL/SQL table, you can fetch your cursor and build your list (PL/SQL table);
so, your package body can be,
CREATE OR REPLACE package body utils is
function F_getTrayIdByDiameter(v_diameterInCm tray.diameterincm%TYPE) return t_trayIds is
v_trayIdsTable t_trayIds;
i number := 0;
cursor c is
select t.trayid from tray t
where t.diameterincm = v_diameterincm;
begin
for my_rec in c loop
v_trayIdsTable(i) := my_rec.trayid;
i := i + 1;
end loop;
return v_trayIdsTable;
end;
end utils;
/
Then, you can use your list in another function, or in an anonymous block, just for example:
declare
my_result utils.t_trayIds;
begin
my_result := utils.F_GETTRAYIDBYDIAMETER(20);
dbms_output.put_line(my_result(0));
end;
By starting with declare you are creating an anonymous block, which do not have return values. They just do stuff and quit. It sounds like you want to create a function instead.
First, to return a collection of trayids, you need to create a type to return. This has to be done at the schema level; it is an object in its own right. There are three kinds of collections in Oracle: nested tables, associative arrays ("index by" tables), and varrays. I pretty much never use varrays and I don't think you can use associative arrays like this (but I forget, this may have changed in recent versions of Oracle). So create your type:
create or replace type t_trayids as table of number;
Now create your function. The key here is you must use bulk collect to populate the array. This is vastly faster than creating a result set and looping over it.
create or replace function F_getTrayIdByDiameter(v_diameterInCm tray.diameterincm%TYPE)
return t_trayIds
as
v_trayIdsTable t_trayIds;
begin
select t.trayid bulk collect into v_trayIdsTable from tray t
where t.diameterincm = v_diameterincm;
return v_trayIdsTable;
end;
As Nicola points out, you can also create a package and declare the type inside the package specification. You can use associative arrays this way. Which approach depends on what you're trying to do.

How to pass arguments from a function to the creation of a TRIGGER?

I am trying to make a TRIGGER that responds on an update at a table (appointments) which then calls a procedure (proc1()). The procedure needs to get arguments in order to insert a new row -based on those arguments- on a different table (medical_folder). Procedures can't have arguments, but after a bit of search I found that you can use a method like the following to kinda force your way though:
Passing arguments to a trigger function
With the above as my base I made the following UDF:
CREATE OR REPLACE FUNCTION AppointmentUpdate(docAMKA bigint, patAMKA bigint, dateNtime timestamp, conclusion varchar(500),cure2 varchar(500), drug_id integer)
RETURNS void AS $$
DECLARE
patAMKAv2 text;
drug_idv3 text;
BEGIN
patAMKAv2 := cast(AppointmentUpdate.patAMKA as text);
drug_idv3 := cast(AppointmentUpdate.drug_id as text);
DROP TRIGGER IF EXISTS tr1 on appointments;
CREATE TRIGGER tr1 BEFORE UPDATE ON appointments
EXECUTE PROCEDURE proc1(patAMKAv2,cure2,drug_idv3);
UPDATE appointments
SET diagnosis = conclusion
WHERE patientamka = patAMKA
AND doctoramka = docAMKA
AND t = dateNtime;
END;
$$ LANGUAGE plpgsql;
My procedure is as follows:
CREATE OR REPLACE FUNCTION proc1()
RETURNS trigger AS $$
declare
newid integer;
BEGIN
newid =((select max(medical_folder.id) from medical_folder)+1);
INSERT INTO medical_folder AS Medf(id,patient,cure,drug_id)
VALUES(newid,cast(TG_ARGV[0] as bigint),TG_ARGV[1],cast(TG_ARGV[2] as integer));
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
If I run it as is I am getting an error in proc1() here cast(TG_ARGV[0] as bigint) and it seems that in the UDF instead of sending the value of the arguments it sends the arguments themselves(if for example i do this EXECUTE PROCEDURE proc1(324,cure2,234); INSTEAD OF THIS EXECUTE PROCEDURE proc1(patAMKAv2,cure2,drug_idv3); ). Is there any way that you can force it to get the values instead?
P.S.:I now that this can be done a lot easier without the use of a TRIGGER and just make a UDF that does everything itself but unfortunately I have to do it using a TRIGGER.
P.S.2: I tried using function_name.variable_name instead of just var_name also tried using $1, $2, $3, ... , $n.
From the CREATE TRIGGER docs:
arguments
An optional comma-separated list of arguments to be provided to the function when the trigger is executed. The arguments are literal string constants. Simple names and numeric constants can be written here, too, but they will all be converted to strings.
http://rextester.com/OCA59277
You might be able to achieve what you are trying with dynamic SQL though (see EXECUTE). But I believe that you overcomplicate things. What you want to do is simply to get rows or IDs which participated in an UPDATE statement. PostgreSQL's DML statements (INSERT, UPDATE & DELETE) has a RETURNING clause just for that. Also, you can actually write more DML (sub-)statements within a single statement with writeable CTEs. Something like this should suffice:
WITH upd AS (
UPDATE appointments
SET diagnosis = conclusion
WHERE patientamka = patAMKA
AND doctoramka = docAMKA
AND t = dateNtime
RETURNING *
)
INSERT INTO medical_folder(patient, cure, drug_id)
SELECT patAMKAv2, cure2, drug_idv3
FROM upd;
Note: while writing this I realized that you actually don't use any of the fields from the UPDATE, but using FROM upd will ensure that as much rows will be inserted into medical_folder just as much appointments got updated. Which is what your original trigger-based logic did.

Stored procedure in oracle sql

I'm trying to do a very simple stored procedure for a class project. I just want to use it to execute a simple insert statement.
What I have is:
CREATE OR REPLACE PROCEDURE insertSpa (p_spaID IN INTEGER, s_sdate IN VARCHAR2, p_address IN VARCHAR2)
AS
BEGIN
INSERT INTO spa( spaID,sdate,address)
VALUES (p_spaid, p_sdate, p_address);
END;
/
And then to call it, I've been using:
exec insertSpa( p_spaID => 9999, p_sDate => '12122012', p_address => '1234 Main St. 12345' );
When I try and run the first portion, I get an error that says "Procedure created with compilation errors." Here is what the spa relation is:
SPAID : NOT NULL NUMBER(38)
SDATE : VARCHAR2(8)
ADDRESS : VARCHAR2(100)
It appears that, among other things, you are trying to use SQL Server's T-SQL syntax in Oracle rather than PL/SQL. I'm guessing that spaid is intended to be an integer, sdate is intended to be an integer (though since it appears to represent a date, it really ought to be defined as a date), and address is intended to be a string. I'm also guessing that the spa table has three columns spaID, sDate, and address. If those guesses are accurate, something like
CREATE OR REPLACE PROCEDURE insertSpa( p_spaID IN INTEGER,
p_sdate IN INTEGER,
p_address IN VARCHAR2 )
AS
BEGIN
INSERT INTO spa( spaID, sDate, address )
VALUES( p_spaID, p_sdate, p_address );
END;
should compile. You would then call it
SQL> exec insertSpa( p_spaID => 9999, -
p_sDate => 12122012, -
p_address => '1234 Main St. 12345' );
The error that you are getting seems to imply that you never created the stored procedure. Did you actually run the CREATE PROCEDURE statement? It sounds like maybe you just created the script on your local client machine but then never executed it to create the procedure. You can run the CREATE PROCEDURE statement directly in SQL*Plus. Or you can save it to a file and execute the script in SQL*Plus
SQL> #<<name of .sql script>>
Presumably, from what you've shown, the procedure compiles OK, and it's the exec - which is just shorthand for an anonymous block - that says there is an error. It would have been helpful if you'd shown the error stack. (If the procedure, or any named blocked, said it had a compilation error, you could get the details with show errors, or by selecting from the user_errors view).
You created your procedure with the second parameter named s_sdate:
CREATE OR REPLACE PROCEDURE insertSpa (p_spaID IN INTEGER,
s_sdate IN VARCHAR2, p_address IN VARCHAR2)
... but when you call it you're saying the name is p_sDate:
exec insertSpa( p_spaID => 9999, p_sDate => '12122012', ...
The names have to match. Given the other names, presumably the procedure declaration should change to p_sDate. Actually, you have a mismatch between the procedure declaration and where you're using it in the procedure, so you'd get both errors - on compile and on exec - so you should definitely change it in the declaration. I missed that you'd said you had a procedure compilation, somehow.

How can I declare and initialize a package variable by SELECT

Can someone help me understand why I can declare a hardcoded date value in a package but not assign the value from a query? I've seen a number of examples (including the reference book) that show declarations of hardcoded values but I'm unable to locate examples of assigning values to variable through queries.
This is allowed:
create or replace package body PACKAGE_NAME AS
tDate DATE := '2012-05-30';
-- ...procedures follow
This is allowed:
create or replace package body PACKAGE_NAME AS
tDate DATE := sysdate;
This is not allowed:
create or replace package body PACKAGE_NAME AS
tDate DATE := select MAX(date_) from Table_Name;
I've tried a number of ways and I'm ok with it not working - I can use it as needed in the procedures themselves. But I'd like to know why I can't assign a value to tDate this way? The specific error is:
Encountered the symbol 'SELECT' when expecting ....
Adding that I can get variable values assigned through queries in a stored procedure but the same process does not seem to work for package body.
PROCEDURE Proc_Name IS
tDate Date;
BEGIN
SELECT MAX(date_) into tDate from Table_Name;
You need to create a package initialization block in your package body. This is a relatively arcane bit of PL/SQL package lore, but it's there and can be used. Here's an example:
CREATE OR REPLACE PACKAGE TEST_PKG IS
tDate DATE;
END TEST_PKG;
Here we've created a package spec which contains only a single DATE variable. Now we'll create a simple body which initializes that variable using a SELECT statement:
CREATE OR REPLACE PACKAGE BODY TEST_PKG IS
-- package-private variables go first (if any)
(...)
-- then the public functions/procedures
(...)
-- then a final BEGIN-END block which is the package initialization block
BEGIN
SELECT SYSDATE
INTO tDATE
FROM DUAL;
EXCEPTION -- you can have exception handlers in your initialization block
WHEN OTHERS THEN
NULL; -- pointless here, but this is just an example
RAISE;
END TEST_PKG;
Now if you execute the following:
begin
-- Test statements here
DBMS_OUTPUT.PUT_LINE('TEST_PKG.tDate = ' || TEST_PKG.tDate);
end;
it should print the current date.
Share and enjoy.
You need to use INTO when selecting directly into a variable.
select MAX(date_)
INTO tDate
from Table_Name;

Postgresql trigger function with parameters

I want to create a trigger on a table called takes in postgresql to update a value in another table called student
I'm trying to do it in the following way. But I'm getting an error that there is syntax error near "OLD". I don't understand whats wrong with this. This is my code:
CREATE OR REPLACE FUNCTION upd8_cred_func
(id1 VARCHAR, gr1 VARCHAR,id2 VARCHAR, gr2 VARCHAR)
RETURNS void AS $$
BEGIN
IF (id1=id2 and gr1 is null and gr2 is not null) THEN
update student set tot_cred = tot_cred + 6 where id = id1;
END IF;
RETURN;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER upd8_cred
AFTER UPDATE ON takes
FOR EACH ROW
EXECUTE PROCEDURE upd8_cred_func(OLD.id,OLD.grade,NEW.id,NEW.grade);
You do not need to pass the NEW and OLD as parameters to the trigger function. They are automagically available there:
http://www.postgresql.org/docs/9.1/interactive/trigger-definition.html :
The trigger function must be declared as a function taking no arguments and returning type trigger. (The trigger function receives its input through a specially-passed TriggerData structure, not in the form of ordinary function arguments.)
About the records passed to the trigger procedure, please see http://www.postgresql.org/docs/9.1/interactive/plpgsql-trigger.html :
When a PL/pgSQL function is called as a trigger, several special variables are created automatically in the top-level block. They are: [...] NEW, [...] OLD [...]
As SeldomNeedy pointed in the comment below, you can still pass and use parameters to the trigger function. You declare the function as taking no parameters, but when defining the trigger (by CREATE TRIGGER), you may add some.
They will be available for the trigger as TG_NARG (the number of such parameters), and TG_ARGV[] (an array of text values).
As Greg stated, trigger functions can take arguments, but the functions themselves cannot have declared parameters. Here's a simple example in plpgsql:
CREATE TABLE my_table ( ID SERIAL PRIMARY KEY ); -- onelined for compactness
CREATE OR REPLACE FUNCTION raise_a_notice() RETURNS TRIGGER AS
$$
DECLARE
arg TEXT;
BEGIN
FOREACH arg IN ARRAY TG_ARGV LOOP
RAISE NOTICE 'Why would you pass in ''%''?',arg;
END LOOP;
RETURN NEW; -- in plpgsql you must return OLD, NEW, or another record of table's type
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER no_inserts_without_notices BEFORE INSERT ON my_table
FOR EACH ROW EXECUTE PROCEDURE raise_a_notice('spoiled fish','stunned parrots');
INSERT INTO my_table DEFAULT VALUES;
-- the above kicks out the following:
--
-- NOTICE: Why would you pass in 'spoiled fish'?
-- NOTICE: Why would you pass in 'stunned parrots'?
--
There are a few other goodies such as TG_NARGS (to know how many args you got without looping through them) discussed in the docs. There's also information there about how to get the name of the triggering table in case you have mostly-but-not-quite-shared logic for one trigger-function that spans a number of tables.
The trigger function can have parameters, but, you can't have those parameters passed like a normal function (e.g. arguments in the function definition). You can get the same result... In python you get access to the OLD and NEW data as the answer above describes. For example, I can use TD['new']['column_name'] in python to reference the new data for column_name. You also have access to the special variable TD['args']. So, if you like:
create function te() returns trigger language plpython2u as $function$
plpy.log("argument passed 1:%s 2:%s" %(TD['args'][0], TD['args'][1], ))
$function$
create constraint trigger ta after update of ttable
for each for execute procedure te('myarg1','myarg2');
Granted, these arguments are static, but, they are useful when calling a common trigger function from multiple trigger declarations. I am pretty sure that the same variables are available for other stored procedure languages. (sorry if the code doesn't work verbatim, but, I do practice this technique, so I know you can pass arguments!).