Postgresql Procedure Error - Record "old" has no field - postgresql

I'm using postgresql procedures and here is my code;
CREATE OR REPLACE FUNCTION public.title_last_procedure()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
if (TG_OP = 'INSERT') then
update
"Titles"
set "entryCount" = "entryCount" + 1,
"lastInsertedDate" = date_trunc('day', (now() + interval '3 hours')),
"lastInsertedTime" = now(),
"entryTodayCount" =
(case
when old."lastInsertedDate" != new."lastInsertedDate" then 1 -- If date different.
else "lastInsertedDate" + 1 -- If date same.
end)
where new."TitleId" = id;
return new;
end if;
return null;
END;
$function$
;
I have two tables Entries and Titles, and I want, when I inserted to data in Entries, it increase lastInsertDate in Titles table. Everything works fine. But If title new, no any lastInsertDate so "old" gives error becouse no any data.
when old."lastInsertedDate" != new."lastInsertedDate" then 1
DatabaseError [SequelizeDatabaseError]: record "old" has no field "lastInsertedDate"
How can I set lastInsertedDate if record is new records without get error? Or how can i check if no old."lastInsertedDate" ?

Related

Postgresql trigger doesn't work correctly when it update two rows at the same time

I am having a problem with trigger on postgresql.
I have tables with tasks and orders. When tasks are updated not at the same time trigger is working correctly, but not when it is happened simultaneously (when two rows updated).
How trigger works.
Some user updates the task
If there all tasks are done, order will be updated, and will be set as completed.
But in this case, trigger won't be able to see if the all tasks are checked, because of the triggering at the same time.
Trigger definition and function.
CREATE TRIGGER "after_update_task_workshop_trigger" AFTER INSERT OR
UPDATE ON "system"."task_workshop"
FOR EACH ROW
EXECUTE PROCEDURE "system"."after_update_task_workshop"();
CREATE OR REPLACE FUNCTION "system"."after_update_task_workshop"()
RETURNS "pg_catalog"."trigger" AS $BODY$
DECLARE
ord "system".order;
count_in_run integer;
stage "system".stage_workshop;
stage_old "system".stage_workshop;
stage_cooked "system".stage_order;
BEGIN
IF (TG_OP = 'UPDATE') THEN
SELECT *
INTO stage
FROM "system"."stage_workshop"
WHERE keyword = NEW.stage_workshop AND
id_type = NEW.id_type_workshop;
SELECT *
INTO stage_old
FROM "system"."stage_workshop"
WHERE keyword = OLD.stage_workshop AND
id_type = OLD.id_type_workshop;
IF (stage_old.keyword IS NOT NULL AND stage_old.sort > stage.sort) THEN
RAISE 'Incorrect taks statuses sequence';
END IF;
END IF;
IF (TG_OP = 'UPDATE' AND OLD.stage_workshop = 'canceled') THEN
RAISE 'Can`t change status of canceled task';
END IF;
IF (NEW.id_product_order IS NOT NULL and NEW.stage_workshop = 'cooked') THEN
IF (EXISTS (SELECT 1 FROM "system".task_workshop WHERE id_order = NEW.id_order AND id_type_workshop = 3 AND stage_workshop NOT IN ('cooked', 'baking'))) THEN
RAISE 'Not all products have status baking';
ELSE
UPDATE "system".task_workshop SET stage_workshop = 'cooked' WHERE id_order = NEW.id_order AND id_type_workshop = 3 AND stage_workshop = 'baking';
END IF;
END IF;
SELECT * INTO ord FROM "system".order where id = NEW.id_order;
SELECT COUNT(*) INTO count_in_run FROM "system".task_workshop WHERE id_order = NEW.id_order AND stage_workshop != 'cooked';
SELECT * INTO stage_cooked FROM "system".stage_order where keyword = 'cooked';
IF (NEW.id_type_workshop != 3 AND NEW.stage_workshop = 'cooked' ) THEN
PERFORM "system".print_task(NEW.id);
END IF;
IF count_in_run = 0 and ord.stage_keyword != 'collected' and ord.stage_keyword != 'in_delivery' and ord.stage_keyword != 'passed' and ord.stage_keyword != 'canceled' and ord.stage_keyword != 'ended' THEN
UPDATE "system".order SET id_stage = stage_cooked.id WHERE id = NEW.id_order;
END IF;
INSERT INTO "system".status_task_workshop (stage_workshop, id_task_workshop, time, id_type_workshop) VALUES (NEW.stage_workshop, NEW.id, now(), NEW.id_type_workshop);
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100

How can I write a trigger that gets the last inserted row into the table?

I was to populate a field is_continued_post if some conditions are true about the previously inserted row into the table (it's the same user, and it's inserted_at is less than N mins from the new rows inserted_at).
When a new comment is inserted into the database. I want to get the last comment (with the same post_id) that was inserted, then check that the old rows user_id are the same as the new rows user_id, and that the old row was inserted less than 2 mins before the new row. If this is true, I want to flip a boolean on the new row to true before inserting it.
Is this possible with Postgresql triggers? Or is there a better way to do this?
This is what I've come up with so far:
CREATE OR REPLACE FUNCTION update_message_cont()
RETURNS trigger AS $$
BEGIN
old := (SELECT m0.user_id, m0.inserted_at FROM messages AS m0 WHERE (m0.post_id = NEW.post_id) ORDER BY m0.inserted_at DESC LIMIT 1);
NEW.is_continued := CASE
WHEN old is NULL THEN FALSE
WHEN old.user_id = NEW.user_id AND ((NEW.inserted_at - old.inserted_at) < 120) THEN TRUE
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Yes, that is possible, but only if you have a column in the table that allows you to identify the last inserted row. The order of insertion is not reflected in the table as such.
So introduce a column
inserted_at timestamp with time zone DEFAULT clock_timestamp() NOT NULL
An index on (post_id, inserted_at) will make the query fast.
The whole trigger could look like:
CREATE FUNCTION update_message_cont() RETURNS trigger AS
$$BEGIN
SELECT user_id IS NOT DISTINCT FROM NEW.user_id INTO NEW.is_continued
FROM messages
WHERE post_id = NEW.post_id
AND inserted_at > NEW.inserted_at - INTERVAL '120 seconds'
ORDER BY inserted_at DESC
LIMIT 1;
-- if no previous row was found:
IF NEW.is_continued IS NULL THEN
NEW.is_continued = FALSE;
END IF;
RETURN NEW;
END;$$ LANGUAGE plpgsql;
CREATE TRIGGER update_message_cont BEFORE INSERT ON messages
FOR EACH ROW EXECUTE PROCEDURE update_message_cont();

Postgresql same date record insert

I am using a Postgresql 9.5 database. A third party software application is also using this database. I have a Features table. I created an Events table to record Features events.
Features
------------
id name lon lat
1 x 0 10
2 y 15 20
When I create a record in the Features table, my trigger inserts a record into the Events table.
Events
id name date feature_id
1 insert 09.04.2018 14:22:23.065125 1
When I update Features name, lon and lat and save it, the software execution results in 3 update records at same time.
Events
id name date feature_id
1 insert 09.04.2018 14:22:23.065125 1
2 update 09.04.2018 18:15:41.099689 1
3 update 09.04.2018 18:15:41.099689 1
4 update 09.04.2018 18:15:41.099689 1
But this is 3 update is same values.
How can I restrict this in my trigger?
My trigger function:
CREATE FUNCTION event_fn() AS $BODY$ BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO events (event_name, event_date, feature_id) VALUES ('insert', now(), NEW.id);
RETURN NEW;
END IF;
IF TG_OP = 'UPDATE' THEN
INSERT INTO events (event_name, event_date, feature_id) VALUES ('update', now(), NEW.id);
RETURN NEW;
END IF;
IF TG_OP = 'DELETE' THEN
INSERT INTO events (event_name, event_date, feature_id) VALUES ('delete', now(), OLD.id);
RETURN OLD;
END IF;
END;
The best solution would be to opt out of the software that performs several updates instead of actually a single one. However, if you can not do this, you can add a trigger for the events table, e.g.:
create or replace function before_insert_on_events()
returns trigger language plpgsql as $$
begin
if exists (
select 1
from events e
where e.name = new.name
and e.date = new.date
and e.feature_id = new.feature_id)
then new = null;
end if;
return new;
end $$;
create trigger before_insert_on_events
before insert on events
for each row
execute procedure before_insert_on_events();

Run triggered postgresql function on hsqldb

I can't find solution about transfering my function.
Lets manage working function and trigger on postgresql as below:
CREATE FUNCTION func_check_minutes() RETURNS trigger AS
$$
BEGIN
IF (SELECT minutes + NEW.minutes FROM employees WHERE date = NEW.date) > 50
THEN RETURN NULL;
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
CREATE TRIGGER tr_check_minutes
BEFORE INSERT ON employees
FOR EACH ROW
EXECUTE PROCEDURE func_check_minutes();
Is it even possible to run this function on hslqdb?
Because when I try to run it (obviously without language command) there is an error:
DatabaseException: unexpected token: TRIGGER
I have syntax error, so I dont know if it's even possible. I was reading about functions and triggers in hsqldb from documentation, but did'nt notice any example about triggered functions in hsqldb.
With help from #fredt I created query:
<sql dbms="hsqldb">
DROP TRIGGER IF EXISTS tr_check_minutes
CREATE TRIGGER tr_check_minutes
BEFORE INSERT ON hours_worked
FOR EACH ROW
BEGIN ATOMIC
IF (SELECT sum(minutes) + NEW.minutes FROM hours_worked WHERE date = NEW.date) > 1440
THEN RETURN NULL;
END IF;
RETURN NEW;
END
</sql>
But it prints an error:
user lacks privilege or object not found: NEW.DATE
If you want the INSERT to fail when too many hours are worked, you can throw an exception:
CREATE TRIGGER tr_check_minutes
BEFORE INSERT ON hours_worked
REFERENCING NEW ROW AS NEW
FOR EACH ROW
BEGIN ATOMIC
IF (SELECT sum(minutes) + NEW.minutes FROM hours_worked WHERE date = NEW.date) > 1440
THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'too many hours';
END IF;
END

Is there a similar function in postgresql for mysql's SQL_CALC_FOUND_ROWS?

everybody using mysql knows:
SELECT SQL_CALC_FOUND_ROWS ..... FROM table WHERE ... LIMIT 5, 10;
and right after run this :
SELECT FOUND_ROWS();
how do i do this in postrgesql? so far, i found only ways where i have to send the query twice...
No, there is not (at least not as of July 2007). I'm afraid you'll have to resort to:
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT id, username, title, date FROM posts ORDER BY date DESC LIMIT 20;
SELECT count(id, username, title, date) AS total FROM posts;
END;
The isolation level needs to be SERIALIZABLE to ensure that the query does not see concurrent updates between the SELECT statements.
Another option you have, though, is to use a trigger to count rows as they're INSERTed or DELETEd. Suppose you have the following table:
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
poster TEXT,
title TEXT,
time TIMESTAMPTZ DEFAULT now()
);
INSERT INTO posts (poster, title) VALUES ('Alice', 'Post 1');
INSERT INTO posts (poster, title) VALUES ('Bob', 'Post 2');
INSERT INTO posts (poster, title) VALUES ('Charlie', 'Post 3');
Then, perform the following to create a table called post_count that contains a running count of the number of rows in posts:
-- Don't let any new posts be added while we're setting up the counter.
BEGIN;
LOCK TABLE posts;
-- Create and initialize our post_count table.
SELECT count(*) INTO TABLE post_count FROM posts;
-- Create the trigger function.
CREATE FUNCTION post_added_or_removed() RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'DELETE' THEN
UPDATE post_count SET count = count - 1;
ELSIF TG_OP = 'INSERT' THEN
UPDATE post_count SET count = count + 1;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- Call the trigger function any time a row is inserted.
CREATE TRIGGER post_added_or_removed_tgr
AFTER INSERT OR DELETE
ON posts
FOR EACH ROW
EXECUTE PROCEDURE post_added_or_removed();
COMMIT;
Note that this maintains a running count of all of the rows in posts. To keep a running count of certain rows, you'll have to tweak it:
SELECT count(*) INTO TABLE post_count FROM posts WHERE poster <> 'Bob';
CREATE FUNCTION post_added_or_removed() RETURNS TRIGGER AS $$
BEGIN
-- The IF statements are nested because OR does not short circuit.
IF TG_OP = 'DELETE' THEN
IF OLD.poster <> 'Bob' THEN
UPDATE post_count SET count = count - 1;
END IF;
ELSIF TG_OP = 'INSERT' THEN
IF NEW.poster <> 'Bob' THEN
UPDATE post_count SET count = count + 1;
END IF;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
There is a simple way, but keep in mind, that following COUNT(*) aggr function will be applied to all rows returned after where and before limit/offset (may be costy)
SELECT
id,
"count" (*) OVER () AS cnt
FROM
objects
WHERE
id > 2
OFFSET 50
LIMIT 5
No, PostgreSQL doesn't try to count all relevant results when you only need 10 results. You need a seperate COUNT to count all results.