SQL, update column in a specific row, instead of all rows - sql

The task is to update the specific row in the column klienta_nr, which is located in the table klientu_ieteikumi. If a specific row is deleted in klienti table. This code updates the whole column not the specific row. What is the problem?
CREATE FUNCTION "funkc"() RETURNS "opaque" AS '
DECLARE
BEGIN
IF (TG_OP = ''DELETE'') THEN
UPDATE klientu_ieteikumi SET klienta_nr = NULL ;
END IF;
RETURN NEW;
END;
' LANGUAGE 'plpgsql';
CREATE TRIGGER "triger"
AFTER DELETE ON "klienti"
FOR EACH ROW EXECUTE PROCEDURE funkc();
This one did what I wanted, thanks anyway everyone :)
CREATE FUNCTION "funkcija1"() RETURNS TRIGGER AS $$
BEGIN
UPDATE klientu_ieteikumi SET klienta_nr = NULL
FROM klienti WHERE old.klienta_nr = klientu_ieteikumi.klienta_nr;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER "trigeris"
AFTER DELETE ON "klienti"
FOR EACH ROW EXECUTE PROCEDURE funkcija1();

you need a WHERE clause to restrict the number of rows affected by the statement.
edit:
UPDATE klientu_ieteikumi
SET klienta_nr = NULL
WHERE klienta_ieteikumi.klienta_nr = klienti.klienta_nr

You presented "solution" cannot work and is wrong in a sneaky way.
CREATE FUNCTION funkcija1()
RETURNS TRIGGER AS
$func$
BEGIN
CREATE FUNCTION funkcija1()
RETURNS TRIGGER AS
$func$
BEGIN
UPDATE klientu_ieteikumi
SET klienta_nr = NULL
FROM klienti -- !!
WHERE klientu_ieteikumi.klienta_nr = OLD.klienta_nr;
RETURN NEW;
RETURN NULL;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER trigeris
AFTER DELETE ON klienti
FOR EACH ROW EXECUTE PROCEDURE funkcija1();
There is no NEW in an AFTER trigger. This wouldn't work at all and raise an exception immediately.
The unbound (and utterly pointless) table klienti in the FROM clause leads to a CROSS JOIN. I.e., instead of just one time, the UPDATE is executed as many times as there are rows in klienti. This would be a major drag on performance, and you might never find out, since there is no error message. Just a lot of wasted cycles and table bloat on your server.

Related

PostgreSQL Trigger update all row in the same table

I want calculate a field when update one or two fields but Trigger calculate all table's row with same value, not only the row updated. What is wrong ..? Thank you.
Create or Replace Function hospital_ocupation() returns Trigger
as
$$
Begin
update hospital set p_ocupation = (new.n_cases*100)/new.capacity
where old.n_case <> new.n_case;
return new;
End
$$
language plpgsql;
Create Trigger update_ocupation after update on hospital
for each row
WHEN (pg_trigger_depth() = 0)
execute procedure hospital_ocupation();
update hospital set capacity=500,n_cases=50
where id_hospital = 1
enter image description here
Don't use UPDATE, assign the calculated value to the new record in a BEFORE trigger:
create or replace function hospital_ocupation()
returns trigger
as
$$
begin
new.p_ocupation := (new.n_cases*100)/new.capacity;
return new;
End
$$
language plpgsql;
To make that work you need a BEFORE trigger:
create trigger update_ocupation
BEFORE update on hospital
for each row
WHEN (old.n_case <> new.n_case)
execute procedure hospital_ocupation();

PostgreSQL trigger after update only on a updated row

I have a small table for news. I want to make a trigger which sets the update date and update time in the row (only for the rows that were updated)
I tried making the following:
CREATE FUNCTION add_update_dates()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF (OLD.news_name IS DISTINCT FROM NEW.news_name OR
OLD.news_description IS DISTINCT FROM NEW.news_description OR
OLD.news_text IS DISTINCT FROM NEW.news_text) THEN
UPDATE news SET news_update_date = current_date, news_update_time = current_time;
END IF;
RETURN new;
END
$$;
CREATE TRIGGER update_news_dates
AFTER UPDATE ON news
FOR EACH ROW
EXECUTE PROCEDURE add_update_dates();
But the trigger updates each row in my table (even those that are not updated), when I want only the updated ones. What am I doing wrong?
Your update statement is updating all the rows in the table! It has no where clause.
Just use assignment:
CREATE FUNCTION add_update_dates()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF (OLD.news_name IS DISTINCT FROM NEW.news_name OR
OLD.news_description IS DISTINCT FROM NEW.news_description OR
OLD.news_text IS DISTINCT FROM NEW.news_text
) THEN
NEW.news_update_date := current_date;
NEW.news_update_time := current_time;
END IF;
RETURN new;
END;
$$;
As an aside, storing date/time in separate columns makes no sense to me.

Using the NEW variable in trigger functions

PostgreSQL 9.4
I'm working on the following trigger function:
CREATE OR REPLACE FUNCTION check_inserted_row() RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
--Here I need to transform the NEW trigger variable
--to an array of values or something else that can be iterated over
--and check each value in it
--How can I do that?
RETURN NEW;
END IF;
END $$
LANGUAGE plpgsql;
The issue is I don't know for sure what columns will be contained in NEW as that it might happen that some columns will be added later. To avoid rewriting the trigger any time it happens I'd like to iterate over all columns in NEW and perform required checking.
Is it possible?

How to make update on all records before insert new?

I want when i put new record to table, before insert i would like update old records as ghost (some like disable) and finally add this new one. So I prepared simple trigger function
CREATE OR REPLACE FUNCTION trg_ghost_game_seed_ins_bef()
RETURNS trigger AS
$$
BEGIN
UPDATE dice_game_seed SET ghost = true WHERE ghost = false;
RETURN NEW;
END
$$ LANGUAGE plpgsql;
CREATE TRIGGER ins_up_bef
BEFORE INSERT OR UPDATE ON dice_game_seed
FOR EACH ROW
EXECUTE PROCEDURE trg_ghost_game_seed_ins_bef();
When i tried insert new record i have info
SQL statement "UPDATE dice_game_seed SET ghost = true WHERE ghost = false"
PL/pgSQL function "trg_ghost_game_seed_ins_bef" line 3 at SQL statement
But whats wrong with line 3 ???
You can use the pg_trigger_depth() function to work around the infinite recursion:
create or replace function trg_ghost_game_seed_ins_bef()
returns trigger as
$$
begin
if (pg_trigger_depth() = 1) then
update dice_game_seed set ghost = true where ghost = false;
end if;
return null;
end
$$ language plpgsql;
create trigger ins_up_bef
before insert or update on dice_game_seed
for each statement
execute procedure trg_ghost_game_seed_ins_bef();
You can also use a statement trigger instead of a row trigger.
Example SQLFiddle

How can this approach to solving circular dependencies in triggers (PostgreSQL) be improved?

Let's say I have two tables, for the simplicity, let them be authors and pages. Listing only the interesting attributes like this:
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
active BOOLEAN NOT NULL DEFAULT true, -- set by the author himself
banned BOOLEAN NOT NULL DEFAULT false, -- set to true by the site administrator
visible BOOLEAN, -- set automatically in a trigger and used by the platform
-- when deciding whether to show this author or not.
-- Equals to (active and not banned).
visible_pages INT -- counter also updated automatically
);
CREATE TABLE pages (
id SERIAL PRIMARY KEY,
ref_author INT NOT NULL,
draft BOOLEAN DEFAULT true,
archived BOOLEAN DEFAULT false, -- not draft but not published anymore
visible BOOLEAN -- (authors.visible and not (draft or archived))
);
Now, when author publishes a page or archives it, the counter needs to be updated. When an author gets banned or suspends their account, the pages need to turn invisible. All that is done so that the frontend only cares for the visible flag and doesn't need to waste cycles to calculate it on the fly.
The straightforward (and somewhat naïve) way to make these triggers is as follows (the function names say it all, and trigger creation is omitted for brevity):
CREATE OR REPLACE FUNCTION before_update_authors() RETURNS TRIGGER AS $$
BEGIN
NEW.visible := (NEW.enabled AND NOT NEW.banned);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION after_update_authors() RETURNS TRIGGER AS $$
BEGIN
IF NEW.visible != OLD.visible THEN
UPDATE pages SET id = id WHERE ref_author = NEW.id; -- dummy call just to trigger the trigger
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION before_update_pages() RETURNS TRIGGER AS $$
DECLARE author_visible BOOLEAN;
BEGIN
SELECT visible INTO author_visible FROM authors WHERE id = NEW.ref_author;
NEW.visible := author_visible AND NOT (draft OR archived);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION after_update_pages() RETURNS TRIGGER AS $$
DECLARE counter INT;
BEGIN
IF OLD.visible <> NEW.visible THEN
IF NEW.visible THEN
counter := 1;
ELSE
counter := -1;
END IF;
UPDATE authors SET visible_pages = visible_pages + counter WHERE id = NEW.ref_author;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Looks straightforward, but in the best case it is slow, and in the worst case it would smash the stack and fail.
In MySQL we can get around this unfortunate situation by using #session_variables, so triggers can detect being called from triggers. PostgreSQL does not have such concept.
The best solution I have come up with so far is to use auxiliary columns and WHEN conditions. So my pages table grows an additional column, _updating_author of type BOOLEAN (default whatever but non-NULL).
Now the code for *_update_pages stays the same. The code to create trigger differs, though:
CREATE TRIGGER tg_before_update_pages BEFORE UPDATE ON pages WHEN (OLD._updating_author IS DISTINCT FROM NEW._updating_author) FOR EACH ROW EXECUTE PROCEDURE before_update_pages();
CREATE TRIGGER tg_after_update_pages AFTER UPDATE ON pages WHEN (OLD._updating_author IS DISTINCT FROM NEW._updating_author) FOR EACH ROW EXECUTE PROCEDURE after_update_pages();
And so is trigger function after_update_authors():
CREATE OR REPLACE FUNCTION after_update_authors() RETURNS TRIGGER AS $$
BEGIN
IF NEW.visible != OLD.visible THEN
UPDATE pages SET visible = (NEW.visible AND NOT (draft OR archived)), _updating_authors = NOT _updating_authors WHERE ref_author = NEW.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
In this case, pages visibility is being set in batch and very fast, because no trigger will be called for individual rows in pages.
However, the need for auxiliary columns bothers me a bit in this approach, and WHEN conditions seem a bit clunky. So is the need to state the logic of what pages.visible is twice.
Can you please suggest an alternative, maybe more elegant, approach to solving this and similar problems?