User-defined function with bind params - sql

I am using the Postgres upsert example. I can get it to work as shown in the example but I need to make the function call be dynamic. The function is
CREATE TABLE db (a INT PRIMARY KEY, b TEXT);
CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
LOOP
UPDATE db SET b = data WHERE a = key;
IF found THEN
RETURN;
END IF;
BEGIN
INSERT INTO db(a,b) VALUES (key, data);
RETURN;
EXCEPTION WHEN unique_violation THEN
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;
I can get it to work this way:
SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');
But I would like to do something like:
SELECT merge_db($1,$2);
Is this possible? I know I can do this by concatenating strings but I would like to prepare my statement and use bind params.

Not sure why I didn't think to try this before but here's the answer:
CREATE TABLE db (a INT PRIMARY KEY, b TEXT);
CREATE FUNCTION merge_db(INT, TEXT) RETURNS VOID AS
$$
BEGIN
LOOP
UPDATE db SET b = $2 WHERE a = $1;
IF found THEN
RETURN;
END IF;
BEGIN
INSERT INTO db(a,b) VALUES ($1, $2);
RETURN;
EXCEPTION WHEN unique_violation THEN
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;
With that you can use
SELECT merge_db($1, $2)

Related

Break update operation when function returns NULL value in PostgreSQL

Let' assume I have a table named mytable:
I have one function which returns text and sometime it can return NULL also. ( this is just demo function in real use case function is complex )
CREATE OR REPLACE FUNCTION parag_test (id text)
RETURNS text
LANGUAGE plpgsql
AS
$$
DECLARE
--- variables
BEGIN
IF(id= 'Ram') THEN
RETURN 'shyam';
ELSE
RETURN NULL;
END IF;
END
$$
I want to update mytable till the time when my function returns non NULL values. if it returns NULL value I want to break update operation that time.
if we use below update query it will not stops updating when function returns NULL
update mytable SET id = parag_test (id) ;
Table after triggering above query looks like :
But what my expectation of output is :
because when we try to update second row function parag_test will return NULL and I want to stop update operation there.
So is there any way in PostgreSQL for achieving that ?
If you do have a primary key (say row number) to your table, this approach could work. - https://dbfiddle.uk/?rdbms=postgres_14&fiddle=0350562961be16333f54ebbe0eb5d5cb
CREATE OR REPLACE FUNCTION parag_test()
RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
a int;
i varchar;
BEGIN
FOR a, i IN SELECT row_num, id FROM yourtable order by row_num asc
LOOP
IF(i = 'Ram') THEN
UPDATE yourtable set id = 'shyam' where row_num = a;
END IF;
IF (i is null) then
EXIT;
END IF;
END LOOP;
END;
$$

Can't execute or perform a funtion in postgresql

I have a syntax erro at or near ")" on executing sum function, perform doesn't work too!
That's my code:
CREATE OR REPLACE FUNCTION sum() RETURNS VOID AS $$
declare
ea bigint;
BEGIN
FOR ea in select ean from ws_products where order_code like 'BIL%'
LOOP
insert into ws_products_margins (type, amount)values ('PERSENTAGE', 30.00) returning id;
update ws_products set margin_id = id where ean = ea;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
execute sum();
You need to return that id into the variable you have declared as rightly mentioned in the comments.
Notice the variable name has been updated, Along with the usage of a 'record' variable.
Try-
CREATE OR REPLACE FUNCTION sum() RETURNS VOID AS $$
declare
ea_id bigint;
j record;
BEGIN
FOR j in select ean from ws_products where order_code like 'BIL%'
LOOP
insert into ws_products_margins (type, amount)values ('PERCENTAGE', 30.00) returning id into ea_id;
update ws_products set margin_id = ea_id where ean = j.ean;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;

Check attribute in trigger function (PostgreSQL)

I have a task in school which requires me to create a table and a trigger.
I don't really know how to explain it but how can I check if cID is inside the select statement within the trigger function ?
Basically my goal is to only allow cID values which are not inside of "SELECT * from Example2 natural join Example3". Can anyone help me with that? Thank you.
CREATE TABLE Example(
cID INTEGER REFERENCES Example2(attr),
level INTEGER CHECK (level BETWEEN 1 AND 10));
CREATE FUNCTION exp() RETURNS TRIGGER AS
$$
BEGIN
IF EXISTS (select * from Example2 natural join Example3) THEN
RAISE EXCEPTION '...';
END IF;
return null;
END; $$ language plpgsql;
CREATE CONSTRAINT TRIGGER trg
AFTER INSERT OR UPDATE ON Example
FOR EACH ROW
EXECUTE PROCEDURE exp();
CREATE FUNCTION exp() RETURNS TRIGGER AS
$$
BEGIN
IF EXISTS (select 1 from Example2 a where a.cID = new.cID ) THEN
RAISE EXCEPTION '...';
END IF;
return RETURN NEW;
END; $$ language plpgsql;

How can we use a constraint to ensure that no values of an array are NULL?

Let's say I have the following table:
CREATE TABLE test (
arr_column VARCHAR[] NOT NULL
)
This does not prevent values of the array being set to NULL when a row is inserted. So, I would like a constraint to enforce this rule. My attempt is as follows:
CREATE TABLE test (
arr_column VARCHAR[] NOT NULL
CHECK (NOT (ARRAY[NULL]::VARCHAR[] <# arr_column))
)
But unfortunately, this does not fail if I insert:
INSERT INTO test (ARRAY['some_string', NULL]::VARCHAR[])
One easy and straightforward way to do such a check is using triggers, but you can also simply create a function and use it at the CHECK clause as you've been doing so far:
CREATE OR REPLACE FUNCTION check_null_element(arr TEXT[])
RETURNS BOOLEAN AS $BODY$
DECLARE j INT;
BEGIN
FOR j IN 1 .. ARRAY_UPPER(arr, 1) LOOP
IF arr[j] IS NULL THEN
RETURN FALSE;
END IF;
END LOOP;
RETURN TRUE;
END;
$BODY$
LANGUAGE plpgsql;
So when creating your table you just need:
CREATE temp TABLE test (
arr_column VARCHAR[] NOT NULL
CHECK (check_null_element(arr_column))
);
Trying to insert an array with NULL values:
db=# INSERT INTO test VALUES (ARRAY['some_string', NULL]::VARCHAR[]);
FEHLER: neue Zeile für Relation »test« verletzt Check-Constraint »test_arr_column_check«
DETAIL: Fehlgeschlagene Zeile enthält ({some_string,NULL}).
And with a valid one ..
db=# INSERT INTO test VALUES (ARRAY['some_string', 'NOT NULL :-)']::VARCHAR[]);
INSERT 0 1
EDIT: Nice to have:
To avoid unwanted exceptions, you can additionally check if the parameter itself is NULL - redundant for this question, since it's been already checked with a NOT NULL constraint at the CREATE TABLE statement. This can be done by adding the following condition to the function: IF arr IS NULL THEN RETURN FALSE; END IF;
CREATE OR REPLACE FUNCTION check_null_element(arr TEXT[])
RETURNS BOOLEAN AS $BODY$
DECLARE j INT;
BEGIN
IF arr IS NULL THEN RETURN FALSE; END IF;
FOR j IN 1 .. ARRAY_UPPER(arr, 1) LOOP
IF arr[j] IS NULL THEN
RETURN FALSE;
END IF;
END LOOP;
RETURN TRUE;
END;
$BODY$
LANGUAGE plpgsql;
The following seems to do the trick, with very little code:
CHECK (array_position(arr_column, NULL) is NULL)

Rollback trigger on insertion conflict

I had this :
CREATE FUNCTION upsert_user(u_name text, u_fullname text, u_email text, u_suffix text) RETURNS integer
LANGUAGE plpgsql
AS $$
DECLARE
userid users.id_user%TYPE;
BEGIN
LOOP
-- first try to update
UPDATE users SET "fullname" = u_fullname, "email" = u_email, "suffix" = u_suffix WHERE "name" = u_name RETURNING "id_user" INTO userid;
-- check if the row is found
IF FOUND THEN
RETURN userid;
END IF;
-- not found so insert the row
BEGIN
INSERT INTO users ("name", "fullname", "email", "suffix") VALUES (u_name, u_fullname, u_email, u_suffix) RETURNING "id_user" INTO userid;
RETURN userid;
EXCEPTION WHEN unique_violation THEN
-- do nothing and loop
END;
END LOOP;
END;
$$;
CREATE TRIGGER link_entity
BEFORE INSERT
ON public.users
FOR EACH ROW
EXECUTE PROCEDURE public.link_entity();
CREATE FUNCTION link_entity() RETURNS trigger
LANGUAGE plpgsql
AS $$ DECLARE
entityid integer;
BEGIN
INSERT INTO privileges_entities (name) VALUES (NEW.name) RETURNING privileges_entities.id_entity INTO entityid;
IF NOT FOUND THEN
RETURN NULL;
END IF;
NEW.ref_entity := entityid;
RETURN NEW;
END;
$$;
After updated postgresql to version 9.5, I modified the function upsert_user to use the new instruction ON CONFLICT:
CREATE FUNCTION upsert_user(u_name text, u_fullname text, u_email text, u_suffix text) RETURNS integer
LANGUAGE sql
AS $$
INSERT INTO users (name, fullname, email, suffix)
VALUES (u_name, u_fullname, u_email, u_suffix)
ON CONFLICT (name) DO UPDATE SET name=EXCLUDED.name, fullname=EXCLUDED.fullname, email=EXCLUDED.email, suffix=EXCLUDED.suffix
RETURNING id_user;
$$;
The problem is that, now, new rows are inserted in the privileges_entities table even if insertion into the users table fails.
Is it possible to rollback the trigger if the insertion of the user leads to a conflict?
This is indeed a side-effect of using the new ON CONFLICT clause.
My solution here would be to add a check into the link_entity() function itself and prevent it from continuing if the user already exists. Like this:
CREATE FUNCTION link_entity() RETURNS trigger
LANGUAGE plpgsql
AS $$ DECLARE
entityid integer;
nameExists boolean;
BEGIN
EXECUTE format('SELECT EXISTS(SELECT 1 FROM %I.%I WHERE name = NEW.name)', TG_TABLE_SCHEMA, TG_TABLE_NAME) INTO nameExists;
IF nameExists THEN
RETURN NEW; -- just return, entity already linked
END IF;
INSERT INTO privileges_entities (name) VALUES (NEW.name) RETURNING privileges_entities.id_entity INTO entityid;
IF NOT FOUND THEN
RETURN NULL;
END IF;
NEW.ref_entity := entityid;
RETURN NEW;
END;
$$;