Using a procedure to update Column 2 in a Table A, by checking if column 1 value is present in a column in Table B [PostgreSQL] - sql

I have two tables as follows:
Table A: Name, ID, value
Table B: ID, title
TableA.value is dependent on whether TableA.ID is present in TableB.ID.
I am trying to create a procedure and trigger so that whenever TableB is modified, the procedure is triggered to check if TableA.ID is in TableB.ID and sets TableA.value to 10.
I am using the following code and getting an error:
CREATE PROCEDURE update1
LANGUAGE SQL
AS $$
UPDATE tablA as a
SET a.value = 10
WHERE a.ID EXISTS ( SELECT b.ID
FROM tableB as b
WHERE a.ID = b.ID)
$$;
I am an SQL noob, and this is the first time I am trying to use a procedure.
UPDATE
I was able to create a procedure which does the job when I manually run it using CALL
However, it does not have a RETURNS TRIGGER block within it. Adding that returns the error Procedure cannot return triggers.
If I create a trigger as follows
CREATE TRIGGER b_trigger
AFTER UPDATE OR INSERT ON b.ID
FOR EACH STATEMENT
EXECUTE PROCEDURE update1();
However, this returns the following
ERROR: function columbia_deli.manager_discount must return type trigger
SQL state: 42P17

Try this using plpgsql Language:
Trigger Function:
CREATE OR REPLACE FUNCTION update1()
RETURNS trigger AS
$BODY$
begin
update "TableA" set value= 10 where id =new.id;
return NEW;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Then create trigger on TableB for after update event
CREATE TRIGGER trig_tableb
AFTER UPDATE
ON "TableB"
FOR EACH ROW
EXECUTE PROCEDURE update1();

Related

How to select all inserted rows to execute an insert trigger with a stored procedure in postgresql?

I'm trying to set an "after insert" trigger that executes a procedure. The procedure would take all inserted rows in table A, group them by a column and insert the result in a table B. I know about "new" variable but it gets inserted rows one by one. Is it possible to get all of them?
I think I can't use a for each row statement as I need to group rows depending on the "trackCode" variable, shared by different rows in tableA.
CREATE OR REPLACE PROCEDURE Public.my_procedure(**inserted rows in tableA?**)
LANGUAGE 'plpgsql'
AS $$
BEGIN
INSERT INTO Public."tableB" ("TrackCode", "count")
SELECT "TrackCode", count(*) as "count" FROM Public."tableA" --new inserted rows in this table
GROUP BY "vmsint"."TrackCode" ;
COMMIT;
END;
$$;
create trigger Public.my_trigger
after insert ON Public.tableA
execute procedure Public.my_procedure(**inserted rows in tableA?**)
Thank you!
You create a statement lever trigger, but do not attempt to pass parameters. Instead use the clause referencing new table as reference_table_name. In the trigger function you use the reference_table_name in place of the actual table name. Something like: (see demo)
create or replace function group_a_ais()
returns trigger
language 'plpgsql'
as $$
begin
insert into table_b(track_code, items)
select track_code, count(*)
from rows_inserted_to_a
group by track_code ;
return null;
end;
$$;
create trigger table_a_ais
after insert on table_a
referencing new table as rows_inserted_to_a
for each statement
execute function group_a_ais();
Do not attempt to commit in a trigger, it is a very bad id even if allowed. Suppose the insert to the main table is part of a larger transaction, which fails later in its process.
Be sure to refer to links provided by Adrian.

Trigger for conditional insert into table

I have subscription data that is being appended to a table in real-time (via kafka). i have set up a trigger such that once the data is added it is checked for consistency. If checks pass some of the data should be added to other tables (that have master information on the customer profile etc.). The checks function i wrote works fine but i keep getting errors on the function used in the trigger. The function for the trigger is:
CREATE OR REPLACE FUNCTION update_tables()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
BEGIN
CASE (SELECT check_incoming_data()) WHEN 0
THEN INSERT INTO
sub_master(sub_id, sub_date, customer_id, product_id)
VALUES(
(SELECT sub_id::int FROM sub_realtime WHERE CTID = (SELECT MAX(CTID) FROM sub_realtime)),
(SELECT sub_date::date FROM sub_realtime WHERE CTID = (SELECT MAX(CTID) FROM sub_realtime)),
(SELECT customer_id::int FROM sub_realtime WHERE CTID = (SELECT MAX(CTID) FROM sub_realtime)),
(SELECT product_id::int FROM sub_realtime WHERE CTID = (SELECT MAX(CTID) FROM sub_realtime))
);
RETURN sub_master;
END CASE;
RETURN sub_master;
END;
$$
The trigger is then:
CREATE TRIGGER incoming_data
AFTER INSERT
ON claims_realtime_3
FOR EACH ROW
EXECUTE PROCEDURE update_tables();
What I am saying is 'if checks pass then select data from the last added row and add them to the master table'. What is the best way to structure this query?
Thanks a lot!
The trigger functions are executed for each row and you must use a record type variable called "NEW" which is automatically created by the database in the trigger functions. "NEW" gets only inserted records. For example, I want to insert data to users_log table when inserting records to users table.
CREATE OR REPLACE FUNCTION users_insert()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
insert into users_log
(
username,
first_name,
last_name
)
select
new.username,
new.first_name,
new.last_name;
return new;
END;
$function$;
create trigger store_data_to_history_insert
before insert
on users for each row execute function users_insert();

trigger insert only if tupel satisfies condition

I want to create a trigger which checks before insert if the tupel which is supposed to be inserted holds a specific condition (which also depends on another table).
For example:
create trigger or replace check_tupel
before insert on A
for each row
execute
if exists (select x,y from B where B.x = A.x and B.y = A.y)
Oh I am using postgreSQL 13.
EDIT: Yes I know that I can do this without a trigger, but I am asking for a solution with a trigger for a reason.
I hope there is a way to do this... My other idea was to create a UDF which gets called before insert but I do not know how to check the condition in this UDF and only insert if the function returns true.
If you simply wanna automatically validate a record before inserting it on table A based on table B using a User Defined Function, you do not need a trigger at all. Consider adding a simple CHECK CONSTRAINT:
CREATE TABLE a (
x int,
y int,
CONSTRAINT exists_in_b CHECK (NOT myfunc(x,y))
);
Demo: db<>fiddle
CREATE TABLE b (x int,
y int);
INSERT INTO b VALUES (42,42);
CREATE OR REPLACE FUNCTION myfunc(x int, y int)
RETURNS BOOLEAN AS $BODY$
SELECT EXISTS (SELECT 1 FROM b WHERE b.y =$1 AND b.x=$2 )
$BODY$
LANGUAGE sql;
CREATE TABLE a (
x int,
y int,
CONSTRAINT exists_in_b CHECK (NOT myfunc(x,y)) -- here the magic happens
);
Now, if we try to insert a value that our function does not validate, it raises an exception:
INSERT INTO a VALUES (42,42);
ERROR: new row for relation "a" violates check constraint "exists_in_b"
DETAIL: Failing row contains (42, 42).
SQL state: 23514
EDIT (See comments): Solution using a trigger
CREATE OR REPLACE FUNCTION myfunc()
RETURNS TRIGGER AS $BODY$
BEGIN
IF EXISTS (SELECT 1 FROM b WHERE b.y =NEW.y AND b.x=NEW.x ) THEN
RAISE EXCEPTION 'tuple already exists in "b": % %', NEW.x,NEW.y;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER check_tupel
BEFORE INSERT OR UPDATE ON a
FOR EACH ROW EXECUTE PROCEDURE myfunc();
Demo: db<>fiddle
So you need a trigger solution, sounds like a homework problem. The question then becomes do you want to:
abort the entire operation
slightly squash the row but continue the remainder of the operation.
The following does the second: (See demo)
create or replace
function check_b_has_a()
returns trigger
language plpgsql
as $$
begin
if exists
( select null
from b
where (b.x,b.y) =
(new.x, new.y)
)
then
return null;
else
return new;
end if;
end;
$$;
create trigger a_bir
before insert
on a
for each row
execute function check_b_has_a();

Update another table through a trigger where new value is the result of a SELECT query

I have these tables:
Users
Skills (name - string, count - integer)
Has_skills (skill_id - skills.id, user_id users.id)
Has_skills is a many to many table between the first two through these FK:
user_id (users.id) and skill_id (skills.id).
What I want to do is update the count column inside skills when a new row is inserted into has_skills. I want to do this through an update trigger on table has_skills. The new value for count I will get through a select query:
SELECT COUNT(*) AS cnt FROM skills
JOIN has_skills hs ON skills.id = hs.skill_id
WHERE hs.skill_id = 1;
The ID above is hardcoded (1), but it works.
I also tested this code in isolation, and it works (although hardcoded, as well):
UPDATE skills
SET count = subquery.cnt
FROM (
SELECT COUNT(*) AS cnt FROM skills
JOIN has_skills hs ON skills.id = hs.skill_id
WHERE hs.skill_id = 1
) AS subquery
WHERE skills.id = 1;
RETURN NEW;
Alright, so here's probably where the problem is. Below is the trigger function and also the trigger itself.
Function:
CREATE OR REPLACE FUNCTION update_skill_count() RETURNS trigger AS
$func$
BEGIN
UPDATE skills
SET count = subquery.cnt
FROM (
SELECT COUNT(*) AS cnt FROM skills
JOIN has_skills hs ON skills.id = hs.skill_id
WHERE hs.skill_id = NEW.skill_id
) AS subquery
WHERE skills.id = NEW.skill_id;
RETURN NEW;
END;
$func$ LANGUAGE plpgsql;
Trigger:
CREATE TRIGGER on_has_skills_insert
AFTER INSERT ON has_skills
FOR EACH ROW
EXECUTE PROCEDURE update_skill_count();
I successfully create the function and trigger, but when I insert new data into has_skills, it doesn't change the count column inside skills. What could be the problem?
There's no need for a select in the trigger function at all. The key for the skill table is directly available in new.skill_id so just use it directly:
-- trigger function and trigger
create or replace function update_skill_count()
returns trigger
as $func$
begin
update skills sk
set count = count+1
where sk.skill_id = new.skill_id;
return new;
end;
$func$ language plpgsql;
create trigger on_has_skills_insert
after insert on has_skills
for each row
execute procedure update_skill_count();
I'm not much familiar with postgresql, but having understanding of Oracle and SQL Server, this looks to be a mutating trigger problem, which is: Trying to read from or write into the same table within a row level trigger on which the trigger is placed.
One of the ways to get rid of mutating trigger/table problem can be changing the row level trigger to a statement level trigger and changing the function accordingly. Here is a psudo code you can try out (not providing the exact tested code as I do not have Postgresql installed):
Function:
CREATE OR REPLACE FUNCTION update_skill_count() RETURNS trigger AS
$func$
BEGIN
UPDATE skills
SET count = subquery.cnt
FROM (
SELECT hs.skill_id, COUNT(*) AS cnt
FROM new_table hs
GROUP BY hs.skill_id
) AS subquery
WHERE skills.id = subquery.skill_id;
RETURN NULL;
END;
$func$ LANGUAGE plpgsql;
Trigger:
CREATE TRIGGER on_has_skills_insert
AFTER INSERT ON has_skills
REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE update_skill_count();

Creating trigger in PostgreSQL

I have 2 tables in PostgreSQL
Table A : https://2.pik.vn/201819cbcc97-8d59-4d3f-9e47-38b5e33d00df.jpg
Table B : https://2.pik.vn/20186c925ba2-7c9c-4253-ba28-497df1465b4f.jpg
I want to create a trigger in PostgeSQL so if I update value in column area in table A, the value in area_LUA will automatically change in table B.
area_LUA = total sum (area)*100 with condition A.parent_id = B.parent_id and CODE ='LUA'
Can someone guide me how to create a trigger like this? Thank you so much
The following trigger should get the job done.
Trigger area_lua will execute after each update on table A. Trigger function update_area_lua checks if the value of area changed, and accordingly updates column area_LUA in table B, with respect to the given parent_id.
CREATE OR REPLACE FUNCTION update_area_lua()
RETURNS trigger AS
$BODY$
BEGIN
IF NEW.area <> OLD.area THEN
UPDATE B
SET area_LUA = (
SELECT SUM(area) * 100
FROM A
WHERE parent_id = B.parent_id AND CODE ='LUA'
)
WHERE parent_id = NEW.parent_id;
END IF;
RETURN NEW;
END;
$BODY$
CREATE TRIGGER area_lua
AFTER UPDATE
ON A
FOR EACH ROW
EXECUTE PROCEDURE update_area_lua();