How to check if a user has data in certain tables - postgresql

So the approach I'm taking is to create new boolean columns on the user table that if is set to true, then the table has data, if false the table is empty. Now I'm stuck because I don't know how to create the triggers, or more like the procedure that follows the trigger.
So my logic is...for each table have a trigger:
CREATE TRIGGER check_sales_trigger
AFTER INSERT OR DELETE
ON sales
FOR EACH ROW
EXECUTE PROCEDURE check_sales_table();
Then, create a procedure that updates the boolean column on the user table for each table. So basically I need help creating the procedure.
FYI, each client has his own db.

The functions (that's a plural) need to deal with a) new order for user, b) user id change for existing order and c) deleted order. Writing triggers isn't hard, it just needs some reading the manual. No ifs, no buts, no exceptions.
Since this is your first time, here's an example for the more complicated one (because it can lead to deadlocks if poorly written), to get you started:
create function check_sales_table__update() returns trigger as $$
begin
if new.user_id < old.user_id then
update users
set has_sales = true
where id = new.user_id;
update users
set has_sales = exists (select 1 from sales where user_id = old.user_id)
where id = old.user_id;
elsif old.user_id < new.user_id then
update users
set has_sales = exists (select 1 from sales where user_id = old.user_id)
where id = old.user_id;
update users
set has_sales = true
where id = new.user_id;
end if;
return null;
end;
$$ language plpgsql;
(The above assumes a not null field, of course.)

Related

How to implement a constraint in postgresql

In a Details table (Product ID, Receipt ID) only products with a stock greater than 0 can be added.
Warehouse (Product ID, Stock). How can I implement this constraint in Postgresql?
-- CREATE THE FUNCTION
CREATE FUNCTION trg_product_stock_check()
RETURNS trigger AS
$func$
BEGIN
if exists (select * from warehouse w where w.product_id = new.product_id and stock <= 0) then
raise NOTICE 'Product must have a stock of greater than 0';
return null;
end if;
return new;
END
$func$ LANGUAGE plpgsql;
-- CREATE THE TRIGGER
CREATE TRIGGER product_stock_check
BEFORE INSERT ON "orders"
FOR EACH ROW EXECUTE PROCEDURE trg_product_stock_check();
The solution is to create a trigger function that's set up on insert of the order's table. First create your function. You want to check your warehouse table for the product's stock quantity in the function. If the product's stock quantity is less than or equal to 0, return null with a log message. Otherwise, return the newly created row (eg: the new keyword in the function is the row being inserted). After you create the function, you can set the trigger up to run on the orders table before insert.
You can read more about creating a trigger/function here and here.

how to have a trigger work only for certain columns (they are chosen by their id) after updating

I have two tables.Tickets which has the columns: ticketid,startdate , enddate
and transactions which has the columns: transactionid, ticketid (fk to tickets), ticketcost.I want to create a trigger on tickets that makes a discount to ticketcost of transactions(of the table transactions) whenever the enddate of a ticket is updated.Multiple transactions might have the same ticket.
I was able to make a trigger that did what i described however not only at one ticket, the one that the date was changed, but every ticket of the tickets table.
first attempt:
create or replace function changeDate() returns trigger as $changeDate$
BEGIN
IF new.enddate != old.enddate THEN
update transactions
set ticketcost = ticketcost - ticketcost*0.1
from tickets
where tickets.ticketid= transactions.ticketid;
END IF;
return new;
END
$changeDate$ LANGUAGE plpgsql;
CREATE TRIGGER changeDate after UPDATE ON tickets
FOR EACH ROW EXECUTE FUNCTION changeDate();
This obviously failed because it is done for every row so every ticket is updated.
What i have now is this:
create or replace function changeDate() returns trigger as $changeDate$
Declare
arg1 integer;
BEGIN
IF new.enddate != old.enddate THEN
update transactions
set ticketcost = ticketcost - ticketcost*0.1
from tickets
where arg1 = transactions.ticketid;
END IF;
return new;
END
$changeDate$ LANGUAGE plpgsql;
CREATE TRIGGER changeDate after UPDATE ON tickets
FOR EACH ROW
WHEN (new.enddate != old.enddate)
EXECUTE FUNCTION changeDate(tickets.ticketid);
I have been trying to pass only the id of a ticket that has different new and old dates.The query works but nothing is changed.Basically i m trying to find a way to pass the id of the ticket that has had its enddate field changed.In the above example i m trying to pass it as a variable when the condition i described occurs.Any help would be appreciated as i cant really find a solution.
You can restrict the trigger to fire only when the date has been updated by specifying the column name and by ensuring the updated value is not the same as the old one:
CREATE TRIGGER changeDate after UPDATE OF enddate ON tickets
FOR EACH ROW
WHEN (OLD.enddateIS DISTINCT FROM NEW.enddate )
EXECUTE FUNCTION changeDate();
Inside the trigger function, you can refer to the NEW.ticketID directly
create or replace function changeDate() returns trigger as $changeDate$
BEGIN
update transactions
set ticketcost = ticketcost - ticketcost*0.1
where transactions.ticketid = NEW.ticketid;
return new;
END
$changeDate$ LANGUAGE plpgsql;
PS: since the function is not changing the date, a better name would be setDiscount

Using a for loop and if statement in postgres

How come this is not working? Basically, this proc will update columns in the main buyer table to check if the user has data in other tables.
DO language plpgsql $$
DECLARE
buyer integer;
BEGIN
FOR buyer IN SELECT id FROM buyers
LOOP
IF (SELECT count(*) FROM invoice WHERE buyer_id = buyer) > 0 THEN
UPDATE buyers SET has_invoice = true WHERE id = buyer;
ELSE
UPDATE buyers SET has_invoice = false WHERE id = buyer;
END IF;
END LOOP;
RETURN;
END;
$$;
It is unclear what is "not working". Either way, use this equivalent UPDATE statement instead:
UPDATE buyers b
SET has_invoice = EXISTS (SELECT 1 id FROM invoice WHERE buyer_id = b.id);
If you don't need redundant storage for performance, you can use a VIEW or generated column for the same purpose. Then the column has_invoice is calculated on the fly and always up to date. Instructions in this closely related answer:
Store common query as column?

Postgres concurrency issue

I wrote the following trigger to guarantee that the field 'filesequence' on the insert receives always the maximum value + 1, for one stakeholder.
CREATE OR REPLACE FUNCTION update_filesequence()
RETURNS TRIGGER AS '
DECLARE
lastSequence file.filesequence%TYPE;
BEGIN
IF (NEW.filesequence IS NULL) THEN
PERFORM ''SELECT id FROM stakeholder WHERE id = NEW.stakeholder FOR UPDATE'';
SELECT max(filesequence) INTO lastSequence FROM file WHERE stakeholder = NEW.stakeholder;
IF (lastSequence IS NULL) THEN
lastSequence = 0;
END IF;
lastSequence = lastSequence + 1;
NEW.filesequence = lastSequence;
END IF;
RETURN NEW;
END;
' LANGUAGE 'plpgsql';
CREATE TRIGGER file_update_filesequence BEFORE INSERT
ON file FOR EACH ROW EXECUTE PROCEDURE
update_filesequence();
But I have repeated 'filesequence' on the database:
select id, filesequence, stakeholder from file where stakeholder=5273;
id filesequence stakeholder
6773 5 5273
6774 5 5273
By my undertanding, the SELECT... FOR UPDATE would LOCK two transactions on the same stakeholder, and then the second one would read the new 'filesequence'. But it is not working.
I made some tests on PgAdmin, executing the following:
BEGIN;
select id from stakeholder where id = 5273 FOR UPDATE;
And it realy LOCKED other records being inserted to the same stakeholder. Then it seems that the LOCK is working.
But when I run the application with concurrent uploads, I see then repeating.
Someone could help me in finding what is the issue with my trigger?
Thanks,
Douglas.
Your idea is right. To get an autoincrement based on another field (let's say it designate to a group) you cannot use a sequence, then you have to lock the rows of that group before incrementing it.
The logic of your trigger function does that. But you have a misunderstood about the PERFORM operation. It supposed to be put instead of the SELECT keyword, so it does not receive an string as parameter. It means that when you do:
PERFORM 'SELECT id FROM stakeholder WHERE id = NEW.stakeholder FOR UPDATE';
The PL/pgSQL is actually executing:
SELECT 'SELECT id FROM stakeholder WHERE id = NEW.stakeholder FOR UPDATE';
And ignoring the result.
What you have to do on this line is:
PERFORM id FROM stakeholder WHERE id = NEW.stakeholder FOR UPDATE;
That is it, only change this line and you are done.

Using the now() function and executing triggers

I am trying to create a trigger function in PostgreSQL that should check records with the same id (i.e. comparison by id with existing records) before inserting or updating the records. If the function finds records that have the same id, then that entry is set to be the time_dead. Let me explain with this example:
INSERT INTO persons (id, time_create, time_dead, name)
VALUES (1, 'now();', ' ', 'james');
I want to have a table like this:
id time_create time-dead name
1 06:12 henry
2 07:12 muka
id 1 had a time_create 06.12 but the time_dead was NULL. This is the same as id 2 but next time I try to run the insert query with same id but different names I should get a table like this:
id time_create time-dead name
1 06:12 14:35 henry
2 07:12 muka
1 14:35 waks
henry and waks share the same id 1. After running an insert query henry's time_dead is equal to waks' time_create. If another entry was to made with id 1, lets say for james, the time entry for james will be equal to the time_dead for waks. And so on.
So far my function looks like this. But it's not working:
CREATE FUNCTION tr_function() RETURNS trigger AS '
BEGIN
IF tg_op = ''UPDATE'' THEN
UPDATE persons
SET time_dead = NEW.time_create
Where
id = NEW.id
AND time_dead IS NULL
;
END IF;
RETURN new;
END
' LANGUAGE plpgsql;
CREATE TRIGGER sofgr BEFORE INSERT OR UPDATE
ON persons FOR each ROW
EXECUTE PROCEDURE tr_function();
When I run this its say time_dead is not supposed to be null. Is there a way I can write a trigger function that will automatically enter the time upon inserting or updating but give me results like the above tables when I run a select query?
What am I doing wrong?
My two tables:
CREATE TABLE temporary_object
(
id integer NOT NULL,
time_create timestamp without time zone NOT NULL,
time_dead timestamp without time zone,
PRIMARY KEY (id, time_create)
);
CREATE TABLE persons
(
name text
)
INHERITS (temporary_object);
Trigger function
CREATE FUNCTION tr_function()
RETURNS trigger AS
$func$
BEGIN
UPDATE persons p
SET time_dead = NEW.time_create
WHERE p.id = NEW.id
AND p.time_dead IS NULL
AND p.name <> NEW.name;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
You were missing the INSERT case in your trigger function (IF tg_op = ''UPDATE''). But there is no need for checking TG_OP to begin with, since the trigger only fires on INSERT OR UPDATE - assuming you don't use the same function in other triggers. So I removed the cruft.
Note that you don't have to escape single quotes inside a dollar-quoted string.
Also added:
AND p.name <> NEW.name
... to prevent INSERT's from terminating themselves instantly (and causing an infinite recursion). This assumes that a row can never succeed another row with the same name.
Aside: The setup is still not bullet-proof. UPDATEs could mess with your system. I could keep updating the id or a row, thereby terminating other rows but not leaving a successor. Consider disallowing updates on id. Of course, that would make the trigger ON UPDATE pointless. I doubt you need that to begin with.
now() as DEFAULT
If you want to use now() as default for time_create just make it so. Read the manual about setting a column DEFAULT. Then skip time_create in INSERTs and it is filled automatically.
If you want to force it (prevent everyone from entering a different value) create a trigger ON INSERT or add the following at the top of your trigger:
IF TG_OP = 'INSERT' THEN
NEW.time_create := now(); -- type timestamp or timestamptz!
RETURN NEW;
END IF;
Assuming your missleadingly named column "time_create" is actually a timestamp type.
That would force the current timestamp for new rows.