PostgreSQL Trigger takes 5 seconds - sql

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.

Related

How to create date trigger?

How can I create a trigger to increase a date data from my table with each next row? I had an attempt, is below the table
What I want to do is to increase date from training_date_end by 1 week. But I don't know how to do it, just studying. Can anyone help?
CREATE TABLE training
(
coach_id int NOT NULL,
customer_id int NOT NULL,
training_date_start date NULL,
training_date_end date NULL,
training_place_id int NOT NULL,
CONSTRAINT training_pk PRIMARY KEY (training_place_id)
);
create or replace trigger lucky_week
before insert or update on training
for each row
begin
update training
set training_date_end = :new.training_date_end + 7
where training_date_end = :new.training_date_end;
end;
Most probably like this:
create or replace trigger lucky_week
before insert or update on training
for each row
begin
:new.training_date_end := :new.training_date_end + 7;
end;
Because, your trigger will suffer from the mutating table error (if you insert more than a single row), and - won't do anything in that case (because row you'd like to update doesn't exist yet).

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();

Postgresql SET DEFAULT value from another table SQL

I'm making a sql script so I have create tables, now I have a new table that have columns. One column has a FOREIGN KEY so I need this value to be SET DEFAULT at the value of the value of the original table. For example consider this two table
PERSON(Name,Surename,ID,Age);
EMPLOYER(Name,Surname,Sector,Age);
In Employer I need AGE to be setted on default on the AGE of Person, this only if PERSON have rows or just 1 row.
ID is Primary key for person and Surname,Sector for employer and AGE is FOREIGN KEY in Employer refferenced from Person
Example sql :
CREATE TABLE PERSON(
name VARCHAR(30) ,
surename VARCHAR(20),
ID VARCHAR(50) PRIMARY KEY,
Age INT NOT NULL,
);
CREATE TABLE EMPLOYER(
name VARCHAR(30) ,
Surename VARCHAR(20),
Sector VARCHAR(20),
Age INT NOT NULL,
PRIMARY KEY (Surename,Sector),
FOREIGN KEY (Age) REFERENCES Person(Age) //HERE SET DEFAULT Person(Age), how'??
);
Taking away the poor design choices of this exercise it is possible to assign the value of a column to that of another one using a trigger.
Rough working example below:
create table a (
cola int,
colb int) ;
create table b (
colc int,
cold int);
Create or replace function fn()
returns trigger
as $$ begin
if new.cold is null then
new.cold = (select colb from a where cola = new.colc);
end if;
return new;
end;
$$ language plpgsql;
CREATE TRIGGER
fn
BEFORE INSERT ON
b
FOR EACH ROW EXECUTE PROCEDURE
fn();
Use a trigger rather than a default. I have done things like this (useful occasionally for aggregated full text vectors among other things).
You cannot use a default here because you have no access to the current row data. Therefore there is nothing to look up if it is depending on your values currently being saved.
Instead you want to create a BEFORE trigger which sets the value if it is not set, and looks up data. Note that this has a different limitation because DEFAULT looks at the query (was a value specified) while a trigger looks at the value (i.e. what does your current row look like). Consequently a default can be avoided by explicitly passing in a NULL. But a trigger will populate that anyway.

MSSQL function in check constraint

I would like to create table with CHECK constraint, where CHECK calls an user defined scalar function. I have read on multiple sites that it is possible, also that it has bad performance. Even though I would like to do it.
I have this table
CREATE TABLE [book_history] (
id int NOT NULL IDENTITY PRIMARY KEY,
user_id int NOT NULL,
library_id int NOT NULL,
book_id int NOT NULL,
borrow_time datetime DEFAULT GETDATE(),
return_policy datetime DEFAULT DATEADD(DAY, 30, GETDATE()),
return_time datetime,
CHECK (dbo.fn_check_duplicate(user_id, library_id, book_id) = 0)
);
and function
DROP FUNCTION IF EXISTS fn_check_duplicate
GO
CREATE FUNCTION fn_check_duplicate (#user_id int, #library_id int, #book_id int)
RETURNS int
BEGIN
RETURN (SELECT COUNT(*) FROM [book_history] WHERE user_id = #user_id AND library_id = #library_id AND book_id = #book_id AND return_time IS NULL)
END
GO
When I try to insert new row into this book_history table (which is empty), I get an error saying The INSERT statement conflicted with the CHECK constraint "CK__book_history__267ABA7A". The conflict occurred in database "library", table "dbo.book_history".
COUNT is supposed to return int data type based on MSDN documentation.
I am owner of both, the table and the function.
Can anyone tell me what am I doing wrong?
Change it to check (dbo.fn_check_duplicate(user_id, library_id, book_id) = 1)
The check is going to look at the state of the table after the insert, so you want the count to be 1.
Test it on rextester: http://rextester.com/AWDNP40594 by uncommenting the second insert.
You can also replace this slow check constraint with a filtered unique index like so:
create unique nonclustered index uix_book_history_user_library_book
on dbo.book_history (user_id, library_id, book_id)
where return_time is null
This might be more of what you are trying to do, if each book_id is an individual book:
create unique nonclustered index uix_book_history_library_book
on dbo.book_history (library_id, book_id)
where return_time is null
Because this would allow a book to only be checked out by one user at a time.

How to use trigger in Postgres after Update?

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