How to use trigger in Postgres after Update? - sql

Hi guys i need your help :D
I'm using the latest version of PostgreSQL
First of all, here is my database's tables:
CREATE TABLE colore (
idcolore INTEGER PRIMARY KEY,
nome VARCHAR(100),
note TEXT
);
CREATE TABLE Prodotto (
SKU varchar(50) PRIMARY KEY,
nome varchar(255) NOT NULL,
quantita INTEGER DEFAULT -1,
idColore INTEGER,
prezzo NUMERIC(10, 2),
FOREIGN KEY(idColore) REFERENCES Colore(idColore) ON UPDATE NO ACTION ON DELETE NO ACTION
);
CREATE TABLE Ordine (
idOrdine INTEGER PRIMARY KEY,
SKU varchar(50) NOT NULL,
quantita INTEGER NOT NULL,
CHECK (check_quantita(SKU, quantita)),
FOREIGN KEY(SKU) REFERENCES Prodotto(SKU) ON UPDATE NO ACTION ON DELETE NO ACTION
);
What I want is that when I insert a new Ordine, the quantita of the Prodotto references by SKU is the quantity available minus the quantity ordered.
For Example:
I have this Prodotto:
SKU : AAA
Nome: Prodotto1
Quantita: 11
And then I do the following:
INSERT INTO Ordine (idOrdine, SKU, quantita) VALUES (1, 'AAA', 10);
What I want is that after the last insert the quantity of the product AAA would be 1.
I've tried using this piece of code
CREATE OR REPLACE FUNCTION aggiorna_quantita() RETURNS trigger AS
$$
BEGIN
UPDATE Prodotto
SET quantita = (SELECT Quantita FROM Prodotto WHERE SKU = TG_ARGV[0]) - TV_ARGV[$1]
WHERE SKU = TV_ARGV[$0] ;
END
$$
LANGUAGE plpgsql;
CREATE TRIGGER trigger_aggiorna_quantita
AFTER INSERT ON Ordine
FOR EACH STATEMENT
EXECUTE PROCEDURE aggiorna_quantita(SKU, quantita);
But nothing happens :(
Thank you in advance and forgive me for my bad English :D

The arguments to a trigger can only be string literals. Simple names and numeric values are converted to strings at compile time. What you want cannot be done using these arguments. Luckily there is a much simpler method. Inside the trigger a variable called NEW is available which is the row that just got inserted.
Also you do not have to use a select to retrieve the current value of quantita.
Oh and don't use uppercase characters for object names in postgresql. It's handling of uppercase is very confusing because it converts them to lowercase unless you put the names between double quotes.
And you also want your trigger to be row level instead of statement level.
So your code would become:
CREATE OR REPLACE FUNCTION aggiorna_quantita() RETURNS trigger AS
$$
BEGIN
UPDATE prodotto
SET quantita = prodotto.quantita - NEW.quantita
WHERE sku = NEW.sku;
RETURN NEW;
END
$$
LANGUAGE plpgsql;
CREATE TRIGGER trigger_aggiorna_quantita
AFTER INSERT ON ordine
FOR EACH ROW
EXECUTE PROCEDURE aggiorna

Related

PostgreSQL Trigger takes 5 seconds

i have a MediaStore Database on Postgres where tried to make a trigger which Updates the average Rating of a Product if a new review is inserted.
The Problem is: If I insert a review now, it takes more than 5 seconds.
Im not really into Databases so i thought of asking you people here :)
The DDL of the two relevant tables are:
create table review
(
review_id bigint generated by default as identity primary key,
rating integer not null CHECK (rating BETWEEN 1 AND 5),
helpful integer not null CHECK (helpful >= 0),
reviewDate date,
benutzer varchar(255),
summary varchar(255),
comment text,
produkt_id bigint NOT NULL references produkt ON DELETE CASCADE
);
create table produkt
(
produkt_id bigint generated by default as identity primary key,
asin varchar(255) unique NOT NULL,
titel varchar(1000) NOT NULL,
rating double precision,
bild varchar(1000),
verkaufsrang integer
);
And the Trigger:
CREATE OR REPLACE FUNCTION update_rating()
RETURNS TRIGGER AS $$
BEGIN
UPDATE produkt
SET rating =
(SELECT AVG(rating) AS rating
FROM review
GROUP BY produkt_id
Having review.produkt_id = new.produkt_id)
WHERE produkt_id = new.produkt_id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER update_rating
AFTER INSERT ON review
FOR EACH ROW
EXECUTE PROCEDURE update_rating();
Does somebody have a solution which reduces the Time of the Insert?
You don't describe your indexes. An index on review (produkt_id, rating) could help a lot if you don't have one already. If you had columns in produkt for sum and count, then you could just compute the new average without needing to traverse the entire set in review for that produkt_id. You might have a problem with concurrency, but that could be a problem with your current one too.

Postgres string formatting for record ID

I am trying to create a table to keep an archive of dad jokes in Postgres. For the title record, I would like the value to by-default be the joke ID, but formatted in a way where if the id is 7, the record's title is Joke #7. Here is my query to create the table:
CREATE TABLE public.jokes (
id int NOT null primary KEY,
user_id int NOT NULL DEFAULT 1,
title varchar NULL DEFAULT FORMAT("Joke #%s", ), -- the title that I would like to be formatted
body varchar NOT NULL,
CONSTRAINT jokes_fk FOREIGN KEY (user_id) REFERENCES public."Users"(id)
);
You need to create a trigger function that will change the title if it is not set
create function change_title() returns trigger as $$
begin
if new.title is null then
new.title := format('joke #%s', new.id);
end if;
return new;
end; $$ language plpgsql;
create trigger change_title_jokes before insert on jokes for each row execute procedure change_title();
Demo in sqldaddy.io

Is it possible to create a cross relationship constraint in postgresql? [duplicate]

I would like to add a constraint that will check values from related table.
I have 3 tables:
CREATE TABLE somethink_usr_rel (
user_id BIGINT NOT NULL,
stomethink_id BIGINT NOT NULL
);
CREATE TABLE usr (
id BIGINT NOT NULL,
role_id BIGINT NOT NULL
);
CREATE TABLE role (
id BIGINT NOT NULL,
type BIGINT NOT NULL
);
(If you want me to put constraint with FK let me know.)
I want to add a constraint to somethink_usr_rel that checks type in role ("two tables away"), e.g.:
ALTER TABLE somethink_usr_rel
ADD CONSTRAINT CH_sm_usr_type_check
CHECK (usr.role.type = 'SOME_ENUM');
I tried to do this with JOINs but didn't succeed. Any idea how to achieve it?
CHECK constraints cannot currently reference other tables. The manual:
Currently, CHECK expressions cannot contain subqueries nor refer to
variables other than columns of the current row.
One way is to use a trigger like demonstrated by #Wolph.
A clean solution without triggers: add redundant columns and include them in FOREIGN KEY constraints, which are the first choice to enforce referential integrity. Related answer on dba.SE with detailed instructions:
Enforcing constraints “two tables away”
Another option would be to "fake" an IMMUTABLE function doing the check and use that in a CHECK constraint. Postgres will allow this, but be aware of possible caveats. Best make that a NOT VALID constraint. See:
Disable all constraints and table checks while restoring a dump
A CHECK constraint is not an option if you need joins. You can create a trigger which raises an error instead.
Have a look at this example: http://www.postgresql.org/docs/9.1/static/plpgsql-trigger.html#PLPGSQL-TRIGGER-EXAMPLE
CREATE TABLE emp (
empname text,
salary integer,
last_date timestamp,
last_user text
);
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% cannot have null salary', NEW.empname;
END IF;
-- Who works for us when she must pay for it?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
END IF;
-- Remember who changed the payroll when
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
...i did it so (nazwa=user name, firma = company name) :
CREATE TABLE users
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
nazwa character varying(20),
firma character varying(50)
);
CREATE TABLE test
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
firma character varying(50),
towar character varying(20),
nazwisko character varying(20)
);
ALTER TABLE public.test ENABLE ROW LEVEL SECURITY;
CREATE OR REPLACE FUNCTION whoIAM3() RETURNS varchar(50) as $$
declare
result varchar(50);
BEGIN
select into result users.firma from users where users.nazwa = current_user;
return result;
END;
$$ LANGUAGE plpgsql;
CREATE POLICY user_policy ON public.test
USING (firma = whoIAM3());
CREATE FUNCTION test_trigger_function()
RETURNS trigger AS $$
BEGIN
NEW.firma:=whoIam3();
return NEW;
END
$$ LANGUAGE 'plpgsql'
CREATE TRIGGER test_trigger_insert BEFORE INSERT ON test FOR EACH ROW EXECUTE PROCEDURE test_trigger_function();

Can I use an SQL statement in assignment inside a PL/pgSQL function?

I've these two tables (Encomenda and Informacaofaturacao) and I'm trying to create a trigger to insert a new line on Informacaofaturacao before insert on Encomenda and put the ID of new line of Informacaofaturacao on new line of Encomenda.
What am I doing wrong?
Thanks
CREATE TABLE Encomenda
(
EncomendaID SERIAL,
ClienteID integer NOT NULL,
MoradaFaturacaoID integer NOT NULL,
MoradaEnvioID integer NOT NULL,
InformacaofaturacaoID integer NULL,
Data timestamp NOT NULL,
Estado EstadoEncomenda NOT NULL DEFAULT 'Em processamento',
CONSTRAINT PK_Encomenda PRIMARY KEY (EncomendaID)
)
;
CREATE TABLE Informacaofaturacao
(
InformacaofaturacaoID SERIAL,
MetodopagamentoID integer NULL,
Portes real NULL,
Iva real NULL,
Total real NULL,
CONSTRAINT PK_Informacaofaturacao PRIMARY KEY (InformacaofaturacaoID)
)
;
CREATE OR REPLACE FUNCTION insert_encomenda()
RETURNS TRIGGER
AS $$
BEGIN
NEW.InformacaofaturacaoID := (INSERT INTO Informacaofaturacao RETURNING InformacaofaturacaoID);
RETURN NEW;
END $$ LANGUAGE plpgsql;
CREATE TRIGGER insert_encomenda_trigger
BEFORE INSERT OR UPDATE ON Encomenda
FOR EACH ROW
EXECUTE PROCEDURE insert_encomenda();
Postgres doesn't accept a data modifying SQL statement (INSERT, UPDATE or DELETE) in assignment. The documentation states:
... the expression in such a statement is evaluated by means of an SQL SELECT command sent to the main database engine.
You should use this form of executing a query with a single-row result instead.
Also, INSERT command must have VALUES part, it can be: DEFAULT VALUES, VALUES(...) or query see the syntax in the documentation.
CREATE OR REPLACE FUNCTION insert_encomenda()
RETURNS TRIGGER
AS $$
BEGIN
INSERT INTO Informacaofaturacao(InformacaofaturacaoID)
VALUES(DEFAULT)
RETURNING InformacaofaturacaoID
INTO NEW.InformacaofaturacaoID;
RETURN NEW;
END $$ LANGUAGE plpgsql;

Trigger for generating IDs

I would like to create trigger for generating ID in table:
CREATE TABLE client (
clientID INT PRIMARY KEY NOT NULL,
name VARCHAR(16) NOT NULL,
surname VARCHAR(16) NOT NULL,
personalID VARCHAR(10) NOT NULL,
CONSTRAINT verifyPersonalID CHECK ((personalID BETWEEN 1000000000 and 9999999999) and (MOD(personalID, 11) = 0))
);
I tried to write it like this but it keeps returning errors and I dont know why. Can you, please, give me an advice what Im doing wrong?
CREATE OR REPLACE TRIGGER clientID
AFTER INSERT
ON client
FOR EACH ROW
BEGIN
UPDATE client
SET client.clientID = klientSeq.nextval
WHERE :new.personalID = client.personalID;
END;
/
You want a before insert trigger:
CREATE OR REPLACE TRIGGER klientID
BEFORE INSERT
ON klient
FOR EACH ROW
BEGIN
SELECT klientSeq.nextval INTO :new.cisloKlienta FROM dual;
END;
Maybe instead of creating a trigger, you could make the default value of the primary key the next value of the sequence.