CREATE TRIGGER AND TRIGGER FUNCTION WITH RECURSIVE AND IF STATEMENT - postgresql

I would like to create a trigger called before an insert in my db.
The trigger function check one condition.
if the conditions is true :
CANNOT insert
else :
CAN insert
To check my condition I need RECURSIVITY, This is what I've done :
CREATE OR REPLACE FUNCTION trigger_check_relation()
RETURNS TRIGGER AS
$$BEGIN
WITH RECURSIVE parent_list AS (
SELECT relation.parent
FROM relation
WHERE relation.child = 9817
UNION
SELECT r.parent FROM relation r
JOIN parent_list on parent_list.parent = r.child
)
SELECT name FROM component WHERE _id in (SELECT parent FROM parent_list);
IF 9817 in (SELECT _id FROM component WHERE _id in (SELECT parent FROM parent_list))
THEN RETURN OLD;
ELSE
RETURN NEW;
END IF;
END;$$ LANGUAGE plpgsql;
I can create my trigger and my function but when I run it have :
ERROR: query has no destination for result data HINT: If you want to
discard the results of a SELECT, use PERFORM instead. CONTEXT:
PL/pgSQL function trigger_check_relation() line 3 at SQL statement SQL
state: 42601
Could you please help me to understand and fix this issue ?

You have two statements in the function: the first one is “WITH RECURSIVE parent_list … SELECT name FROM …” but it doesn't do anything with the result such as assigning it to a variable.
I think you may need to move the WITH clause into the condition:
IF 9817 in (
WITH RECURSIVE parent_list AS (
SELECT relation.parent
FROM relation
WHERE relation.child = 9817
UNION
SELECT r.parent FROM relation r
JOIN parent_list on parent_list.parent = r.child
)
SELECT _id FROM component
WHERE _id in (SELECT parent FROM parent_list)
)
…

Related

Duplication constraint in postgreSQL using (using GIST and or EXCLUDE)

How to write a constraint (table level unique, using GIST and or EXCLUDE ) to perform duplication record validation using the following rule:
Entered from_date and to_date values should not be equal or within the range, and
Employee_id should not be equal, and
After the validation, an error message should be return saying 'Duplicate Entry'.
This is in postgreSQL.
Note: I am new to postgreSQL (worked in MS SQL Server and MySQL).
Thanks in advance.
As stated by #Laurenz Albe, it sounds like impossible to do with a constraint. You can implement either a trigger function or a rule instead :
Trigger function :
CREATE OR REPLACE FUNCTION test_insert_table()
RETURNS trigger LANGUAGE plpgsql IMMUTABLE AS
$$
BEGIN
IF NEW.status = 'pending'
AND EXISTS
( SELECT 1
FROM your_table
WHERE Employee_id = NEW.Employee_id
AND range #> daterange(NEW.from_date, NEW.to_date)
)
THEN
RAISE EXCEPTION 'Duplicate Entry' ;
RETURN NULL ;
ELSE
RETURN NEW ;
END IF ;
END ;
$$
CREATE OR REPLACE TRIGGER test_insert_table
BEFORE INSERT ON your_table
FOR EACH ROW EXECUTE FUNCTION test_insert_table() ;
Rule :
CREATE OR REPLACE RULE test_insert AS
ON INSERT TO your_table
WHERE NEW.status = 'pending'
AND EXISTS
( SELECT 1
FROM your_table
WHERE Employee_id = NEW.Employee_id
AND range #> daterange(NEW.from_date, NEW.to_date)
)
DO INSTEAD NOTHING ;

Execute select statement conditionally

I'm using PostgreSQL 9.6 and I need to create a query that performs a select depending on the logic of an if
Basically I've tried:
DO $$
BEGIN
IF exists ( SELECT 1 FROM TABLE WHERE A = B ) THEN
SELECT *
FROM A
ELSE
SELECT *
FROM B
END IF
END $$
And that returns me an error:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM
instead.
CONTEXT: PL/pgSQL function inline_code_block line 15 at SQL statement
Then I switched "SELECT" for "PERFORM", but that don't actually execute the SELECT statement for me.
I read that I need to call a void function to perform a "dynamic" query, but I couldn't make that work either. I'm new to writing queries on PostgreSQL. Is there any better way of doing that?
DO statements do not take parameters nor return anything. See:
Returning values for Stored Procedures in PostgreSQL
You may want a function instead. Create once:
CREATE FUNCTION foo()
RETURNS SETOF A -- or B, all the same
LANGUAGE plpgsql AS
$func$
BEGIN
IF EXISTS (SELECT FROM ...) THEN -- some meaningful test
RETURN QUERY
SELECT *
FROM A;
ELSE
RETURN QUERY
SELECT *
FROM B;
END IF;
END
$func$
Call:
SELECT * FROM foo();
But the function has one declared return type. So both tables A and B must share the same columns (at least columns with compatible data types in the same order; names are no problem).
The same restriction applies to a plain SQL statement. SQL is strictly typed.
Anonymous code blocks just can't return anything - you would need a function instead.
But I think you don't need pl/pgsql to do what you want. Assuming that a and b have the same count of columns and datatypes, you can use union all and not exists:
select a.* from a where exists (select 1 from mytable where ...)
union all
select b.* from b where not exists (select 1 from mytable where ...)

Postgresql function table name parameter

I have n tables sharing a least one column name ("Date"), I want to create a function which can make a
select column1, column2, ..., columnx from myfunction('table_name','date_value')
First, tried with just table name parameter (excerpt from another post on this website) :
CREATE OR REPLACE FUNCTION test(_tbl regclass, OUT result integer) AS
$func$
BEGIN
EXECUTE format('SELECT (EXISTS (SELECT FROM %s))::int', _tbl)
INTO result;
END
$func$ LANGUAGE plpgsql;
and called with SELECT test('"vDxi"'); but the result is :
ERROR: syntax error at or near "FROM"
LINE 1: SELECT (EXISTS (SELECT FROM "vDxi"))::int
^
QUERY: SELECT (EXISTS (SELECT FROM "vDxi"))::int
CONTEXT: PL/pgSQL function test(regclass) line 3 at EXECUTE statement
SQL state: 42601
Since I'm a real newbie in PLSQL, I don't know where the error is (tried a SELECT * ... with no success).
And the calling query does not permit me to select column names, if I can make it working...

How can I check if an element in a table exists?

I'm writing a Postgres function which should delete from 3 tables successively.
The relation is delete from mobgroupdata -> mobilenums -> terminals and when I don't have an element in mobgroupdata, I want to delete from mobilenums and then from terminals. But what should be the condition. I've tried with
IF mRec.id != 0, but it didn't work, than I've tried with exists, it also didn't work. Also when I made my select statement from the DB and mobgroupdata's id doesn't exist, the code is breaking, but when I select element which consist in all tables it works. Does anybody know what should be the if statement to make it works?
CREATE OR REPLACE FUNCTION "Delete_From_Terminals_Casc_final12"(
"Id_list" bigint,
"Curuser_id" bigint)
RETURNS SETOF term_mgd_mobnums AS
$BODY$
declare
mRec "term_mgd_mobnums"%ROWTYPE;
BEGIN
for mRec in select mn."id_terminals", t.sn , t.imei ,t.les ,t.category ,t.model ,t.tswv ,t.status ,t.activation_date ,t.deactivation_date ,t.paytype ,t.ip_address ,t.pin1 ,t.pin2 ,t.puk1 ,t.puk2 ,t.notes ,t.units ,t.validtill, t.responsible_user,t.id_clients,t.currentuser, t.isn,
md.id_mobilenums, mn.current_status, mn.start_date ,mn.streason ,mn.unit ,mn.mobnumber ,mn.service ,mn.status as mn_status,mn.activator ,mn.responsible_department,mn.date_changed ,mn.reason ,mn.installed_on ,mn.usedby ,mn.regnumber ,mn.responsible_user as mn_responsible_user ,mn.description,
md.id,md.les1 ,md.les2,md.les3,md.les4,md.les5,md.member1 ,md.member2,md.member3,md.member4,md.member5,md.user1 ,md.user2,md.user3,md.user4,md.user5,md.pass1 ,md.pass2,md.pass3,md.pass4,md.pass5 from terminals t
inner join mobilenums mn on t."id" = mn."id_terminals"
inner join mobgroupdata md on md."id_mobilenums" = mn."id"
where mn."id_terminals" = $1
loop
IF exists THEN
PERFORM "Delete_From_Mobgroupdata2"(mRec.id,$2);
PERFORM "Delete_From_Mobilenums"(mRec.id_mobilenums::text,$2);
PERFORM "Delete_From_Terminals"(mRec.id_terminals::text,$2);
ELSE
PERFORM "Delete_From_Mobilenums"(mRec.id_mobilenums::text,$2);
PERFORM "Delete_From_Terminals"(mRec.id_terminals::text,$2);
END IF;
RETURN NEXT mRec;
end loop;
return;
end;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
ALTER FUNCTION "Delete_From_Terminals_Casc_final12"(bigint, bigint)
OWNER TO postgres;
Two problems with your code, if I am reading your question correctly:
You are using INNER JOIN to join to mobgroupdata. This will only retrieve results for rows which do exist in all of your tables. Use LEFT OUTER JOIN instead.
You tried mRec.id != 0, but you are looking for NULL, not 0. 0 and NULL are not the same thing in SQL. The condition you want is mRec.id IS NOT NULL.

Only perform update if column exists

Is it possible to execute an update conditionally if a column exists?
For instance, I may have a column in a table and if that column exists I want that update executed, otherwise, just skip it (or catch its exception).
You can do it inside a function. If you don't want to use the function later you can just drop it afterwards.
To know if a column exists in a certain table, you can try to fetch it using a select(or a perform, if you're gonna discard the result) in information_schema.columns.
The query bellow creates a function that searches for a column bar in a table foo, and if it finds it, updates its value. Later the function is run, then droped.
create function conditional_update() returns void as
$$
begin
perform column_name from information_schema.columns where table_name= 'foo' and column_name = 'bar';
if found then
update foo set bar = 12345;
end if;
end;
$$ language plpgsql;
select conditional_update();
drop function conditional_update();
With the following table as example :
CREATE TABLE mytable (
idx INT
,idy INT
);
insert into mytable values (1,2),(3,4),(5,6);
you can create a custom function like below to update:
create or replace function fn_upd_if_col_exists(_col text,_tbl text,_val int) returns void as
$$
begin
If exists (select 1
from information_schema.columns
where table_schema='public' and table_name=''||_tbl||'' and column_name=''||_col||'' ) then
execute format('update mytable set '||_col||'='||_val||'');
raise notice 'updated';
else
raise notice 'column %s doesn''t exists on table %s',_col,_tbl;
end if;
end;
$$
language plpgsql
and you can call this function like:
select fn_upd_if_col_exists1('idz','mytable',111) -- won't update raise "NOTICE: column idz deosnt exists on table mytables"
select fn_upd_if_col_exists1('idx','mytable',111) --will upadate column idx with value 1111 "NOTICE: updated"