Oracle SQL: create procedure that takes a table as input - sql

I would like to create a procedure that takes a table (T in the example) as an input.
However I don't want to provide the table name as a char and do something 'dynamic' like
begin
execute immediate ('delete from '||T||' where ...');
end;
because the actual task I am dealing with involves several inputs to the function and a long query to execute which has a lot of apices... the string to execute becomes really messy and difficult to debug.
I would like to have something simple as the following (which works):
CREATE or replace PROCEDURE fun (x NUMBER) AS
BEGIN
DELETE FROM MyTable WHERE MyTable.var= fun.x;
END;
/
However giving the table as an input to the procedure (I need to do it because otherwise, in the real task, I would need to change the "T" in a lot of occurrences within the procedure) does not work:
CREATE or replace PROCEDURE fun (x NUMBER, T table) AS
BEGIN
DELETE FROM T WHERE T.var= fun.x;
END;
/
Errors for PROCEDURE FUN
----------------------------
L:1 C:32 PLS-00103: Encountered the symbol "TABLE" when expecting one of the following:
in out <an identifier> <a double-quoted delimited-identifier>
... long double ref char time timestamp interval date binary
national character nchar
The symbol "<an identifier> was inserted before "TABLE" to continue.
1 statement failed.
Is it possible to have something close to what I want?

I have devised a tornaround which is surely not elegant but, according to the comments, appears to be the only way to avoid large pieces of dynamic "execute immediate" code.
CREATE or replace PROCEDURE fun (T_in char, T_out char, x number)
as
BEGIN
/* I create a temporary replica with a view */
execute immediate 'create or replace view tempview as select * from '||T_in;
/* DO STUFF, for example: */
delete from tempview where ente_segn = fun.x;
/* Save the results in a new table */
execute immediate 'create or replace view '||T_out||' as select * from tempview';
END;
/

Related

query has no destination for result data in a function that has a set of instructions in postgresql

I am trying to automate a set of sentences that I execute several times a day. For this I want to put them in a postgres function and just call the function to execute the sentences consecutively. If everything runs OK then in the end return the SUCCESS value. The following function replicates my idea and the error I am getting when executing the function:
CREATE OR REPLACE FUNCTION createTable() RETURNS int AS $$
BEGIN
DROP TABLE IF EXISTS MY_TABLE;
CREATE TABLE MY_TABLE
(
ID integer
)
WITH (
OIDS=FALSE
);
insert into MY_TABLE values(1);
select * from MY_TABLE;
RETURN 'SUCCESS';
END;
$$ LANGUAGE plpgsql;
Invocation:
select * from createTable();
With my ignorance of postgresql I would expect to obtain the SUCCESS value as a return (If everything runs without errors). But the returned message causes me confusion, isn't it the same as a function in any other programming language? When executing the function I get the following message:
query has no destination for result data Hint: If you want to
discard the results of a SELECT, use PERFORM instead.
query has no destination for result data Hint: If you want to discard the results of a SELECT, use PERFORM instead.
You are getting this error because you do not assign the results to any variable in the function. In a function, you would typically do something like this instead:
select * into var1 from MY_TABLE;
Therefore, your function would look something like this:
CREATE OR REPLACE FUNCTION createTable() RETURNS int AS $$
DECLARE
var1 my_table%ROWTYPE;
BEGIN
DROP TABLE IF EXISTS MY_TABLE;
CREATE TABLE MY_TABLE
(
ID integer
)
WITH (
OIDS=FALSE
);
insert into MY_TABLE values(1);
select * into var1 from MY_TABLE;
<do something with var1>
RETURN 'SUCCESS';
END;
$$ LANGUAGE plpgsql;
Otherwise, if you don't put the results into a variable, then you're likely hoping to achieve some side effect (like advancing a sequence or firing a trigger somehow). In that case, plpgsql expects you to use PERFORM instead of SELECT
Also, BTW your function RETURNS int but at the bottom of your definition you RETURN 'SUCCESS'. SUCCESS is a text type, not an int, so you will eventually get this error once you get past that first error message -- be sure to change it as necessary.

PostgreSQL variable in select and delete statements

The Problem: I have many delete lines in a PostgreSQL script where I am deleting data related to the same item in the database. Example:
delete from <table> where <column>=180;
delete from <anothertable> where <column>=180;
...
delete from <table> where <column>=180;
commit work;
There are about 15 delete statements deleting data that references <column>=180.
I have tried to replace the 180 with a variable so that I only have to change the variable, instead of all the lines in the code (like any good programmer would do). I can't seem to figure out how to do it, and it's not working.
NOTE: I am very much a SQL novice (I rarely use it), so I know there's probably a better way to do this, but please enlighten me on how I can fix this problem.
I have used these answers to try and fix it with no luck: first second third. I've even gone to the official PostgreSQL documentation, with no luck.
This is what I'm trying (these lines are just for testing and not in the actual script):
DO $$
DECLARE
variable INTEGER:
BEGIN
variable := 101;
SELECT * FROM <table> WHERE <column> = variable;
END $$;
I've also tried just delcaring it like this:
DECLARE variable INTEGER := 101;
Whenever I run the script after replacing one of the numbers with a variable this is the error I get:
SQL Error [42601]: ERROR: query has no destination for result data
Hint: If you want to discard the results of a SELECT, use PERFORM instead.
Where: PL/pgSQL function inline_code_block line 6 at SQL statement
Can someone tell me where I'm going wrong? It would be nice to only have to change the number in the variable, instead of in all the lines in the script, and I just can't seem to figure it out.
As #Vao Tsun said, you must define a destination to your SELECT statement. Use PERFORM otherwise:
--Test data
CREATE TEMP TABLE my_table (id, description) AS
VALUES (1, 'test 1'), (2, 'test 2'), (101, 'test 101');
--Example procedure
CREATE OR REPLACE FUNCTION my_procedure(my_arg my_table) RETURNS VOID AS $$
BEGIN
RAISE INFO 'Procedure: %,%', my_arg.id, my_arg.description;
END
$$ LANGUAGE plpgsql;
DO $$
DECLARE
variable INTEGER;
my_record my_table%rowtype;
BEGIN
variable := 101;
--Use your SELECT inside a LOOP to work with result
FOR my_record IN SELECT * FROM my_table WHERE id = variable LOOP
RAISE INFO 'Loop: %,%', my_record.id, my_record.description;
END LOOP;
--Use SELECT to populate a variable.
--In this case you MUST define a destination to your result data
SELECT * INTO STRICT my_record FROM my_table WHERE id = variable;
RAISE INFO 'Select: %,%', my_record.id, my_record.description;
--Use PERFORM instead of SELECT if you want to discard result data
--It's often used to call a procedure
PERFORM my_procedure(t) FROM my_table AS t WHERE id = variable;
END $$;
--DROP FUNCTION my_procedure(my_table);

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.

Best way to create a stored procedure that takes about 30 columns as arguments ? in postgres

What would be the best way to create a stored procedure to apply changes to the database that has about 30 arguments (columns) to be changed?
Right now I'm playing on creating a huge insert query inside the stored procedure that list all the arguments that are passed by the user. Is that the only way or is there a better way for stored procedures this big?
Example:
CREATE OR REPLACE FUNCTION "applyntunesettings"(val1,val2....val30)
RETURNS void AS
BEGIN
INSERT INTO calibrationstable (col1,col2........col20) Values (val1,val2.....val20);
INSERT INTO devicestable (col1,col2,.....col10) values (val21,val22,....val30);
END
Sometimes, row type variables can be convenient. Assuming the tables are "calibrations" and "devices" we can write:
CREATE OR REPLACE FUNCTION applyntunesettings (
_device_name TEXT
, _some_value TEXT
, _other_value TEXT
-- and so on
) RETURNS void AS $$
DECLARE
_calibration calibrations;
_device devices;
BEGIN
_device.name := _device_name;
INSERT INTO devices SELECT _device.*;
_calibration.some_value := _some_value;
_calibration.other_value := _other_value;
INSERT INTO calibrations SELECT _calibration.*;
END $$ LANGUAGE plpgsql;

PostgreSQL unexpected trigger behavior

The Situation:
I have a function fn_SetFoo() that inserts records into table TableFoo.
I also have a trigger function that runs after each insert into TableFoo. It takes the new primary key TableFooID from the newly inserted row and inserts it into a second table TableFooBar (with a foreign key constraint).
I created a trigger that runs AFTER INSERT ON TableFoo FOR EACH ROW EXECUTE PROCEDURE fn_SetFooBar();
If I call fn_SetFoo() directly then everything works as expected.
However, I have a separate function fn_NormalizeFoo() that processes some data and then calls fn_SetFoo() for each record it has processed.
If I call fn_NormalizeFoo() then the only the first record is processed and the function stops.
The Question:
Why would the process stop after the first record when called from fn_NormalizeFoo() when the entire process runs when the contents of fn_NormalizeFoo() is run directly?
Some Code:
--------------------------------------------------------
-- Insert Into TableFoo --
--------------------------------------------------------
CREATE OR REPLACE FUNCTION "example"."fn_SetFoo" (
IN "Foo1" INTEGER,
IN "Foo2" INTEGER,
IN "Foo3" INTEGER
) RETURNS "void" AS
$$
BEGIN
INSERT INTO
"example"."TableFoo"(
"Foo1",
"Foo2",
"Foo3"
)
VALUES
(
$1,
$2,
$3
);
RETURN;
EXCEPTION WHEN "unique_violation" THEN
-- DO NOTHING
END;
$$
LANGUAGE plpgsql;
--------------------------------------------------------
-- Insert Into TableFooBar --
--------------------------------------------------------
CREATE OR REPLACE FUNCTION "example"."fn_SetFooBar" (
IN "FooPK" INTEGER,
IN "BarPK" INTEGER
) RETURNS "void" AS
$$
BEGIN
INSERT INTO
"example"."TableFooBar"(
"FooPK",
"BarPK"
)
VALUES
(
$1,
$2
);
RETURN;
EXCEPTION WHEN "unique_violation" THEN
-- DO NOTHING
END;
$$
LANGUAGE plpgsql;
--------------------------------------------------------
-- Trigger Function --
--------------------------------------------------------
CREATE OR REPLACE FUNCTION "example"."tr_SetFooBar"() RETURNS TRIGGER AS
$$
BEGIN
PERFORM
"example"."fn_SetFooBar"(
"TableFoo"."FooPK",
"TableBar"."BarPK"
)
FROM
"example"."TableFoo" JOIN
"example"."TableBar" ON [SOMETHING TRUE]
WHERE
NEW.SOMECOLUMN = SOMETHING AND
[MORE STUFF IS TRUE];
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
--------------------------------------------------------
-- Trigger --
--------------------------------------------------------
CREATE TRIGGER "SetFooBar" AFTER INSERT ON "example"."Foo" FOR EACH ROW EXECUTE PROCEDURE "example"."tr_SetFooBar"();
--------------------------------------------------------
-- Normalize Foo --
--------------------------------------------------------
CREATE OR REPLACE FUNCTION "example"."fn_NormaliseFoo" (
IN "Param1" VARCHAR,
IN "Param2" VARCHAR,
IN "Param3" VARCHAR
) RETURNS "void" AS
$$
SELECT
"example"."fn_SetFoo" (
"Foo1",
"Foo2",
"Foo3"
)
FROM
[TABLES]
WHERE
[STUFF IS TRUE]
$$
LANGUAGE SQL;
As you can see, this is a bit more complex then I originally posted. The general idea is to create a many to many relationship as each record is added.
Just to reiterate, running "example"."fn_NormaliseFoo" fails after the first row; however, manually running the contents works as expected.
fn_NormalizeFoo() is described as:
a separate function fn_NormalizeFoo() that processes
some data and then calls fn_SetFoo() for each record it has processed.
However, the code shown for fn_NormaliseFoo is declared in the SQL language so it's not procedural, and therefore it can't do what is claimed. It can just run one query and returns its results (or nothing if no results). Such code has to be compatible with inlining into the calling query.
So the first problem is that fn_NormaliseFoo is declared as returning void but it's not compatible with the fact that it's a SELECT. Normally the function creation shouldn't even be accepted by the interpreter of the sql language.
Example:
CREATE FUNCTION f(int) returns void as 'select $1;' language sql;
This fails with:
ERROR: return type mismatch in function declared to return void
DETAIL: Actual return type is integer.
A second problem would be that fn_SetFoo is likely to be called only once, which is what I understand from the question's "the process stop after the first record". Despite the call being in the select list of a query with some joined tables that presumably produce N rows, there's no reason that can be seen here for the SQL engine to call it N times. Better for it call it only once and affect the same result to every row formed as the output of the query.
It looks a bit like you're using sql in spite of plpgsql. To make sure that a function is call N times, loop N times in procedural code and that will be guaranteed to work.