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();
Related
I'm trying to keey track of a clients database with which we sync. I need to record records_added (INSERTs) and records_updated (UPDATEs) to our table.
I'm using an UPSERT to handle the sync, and a trigger to update a table keeping track of insert/updates.
The issue is counting records that have are updated. I have 40+ columns to check, do I have to put all these in my check logic? Is there a more elegant way?
Section of code in question:
select
case
when old.uuid = new.uuid
and (
old.another_field != new.another_field,
old.and_another_field != new.and_another_field,
-- many more columns here << This is particularly painful
) then 1
else 0
end into update_count;
Reproducible example:
-- create tables
CREATE TABLE IF NOT EXISTS example (uuid serial primary key, another_field int, and_another_field int);
CREATE TABLE IF NOT EXISTS tracker_table (
records_added integer DEFAULT 0,
records_updated integer DEFAULT 0,
created_at date unique
);
-- create function
CREATE OR REPLACE FUNCTION update_records_inserted () RETURNS TRIGGER AS $body$
DECLARE update_count INT;
DECLARE insert_count INT;
BEGIN
-- ---------------- START OF BLOCK IN QUESTION -----------------
select
case
when old.uuid = new.uuid
and (
old.another_field != new.another_field
-- many more columns here
) then 1
else 0
end into update_count;
-- ------------------ END OF BLOCK IN QUESTION ------------------
-- count INSERTs
select
case
when old.uuid is null
and new.uuid is not null then 1
else 0
end into insert_count;
-- --log the counts
-- raise notice 'update %', update_count;
-- raise notice 'insert %', insert_count;
-- insert or update count to tracker table
insert into
tracker_table(
created_at,
records_added,
records_updated
)
VALUES
(CURRENT_DATE, insert_count, update_count) ON CONFLICT (created_at) DO
UPDATE
SET
records_added = tracker_table.records_added + insert_count,
records_updated = tracker_table.records_updated + update_count;
RETURN NEW;
END;
$body$ LANGUAGE plpgsql;
-- Trigger
DROP TRIGGER IF EXISTS example_trigger ON example;
CREATE TRIGGER example_trigger
AFTER
INSERT
OR
UPDATE
ON example FOR EACH ROW EXECUTE PROCEDURE update_records_inserted ();
-- A query to insert, then update when number of uses > 1
insert into example(whatever) values (2, 3) ON CONFLICT(uuid) DO UPDATE SET another_field=excluded.another_field+1;
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" ?
Help Needed. i have a table and the respective audit table in emp schema.I was not able to delete the entry from the source table when the trigger is enabled.
The table is mapped to a trigger as stated below.
Below is the generic function , which i have used to audit across all the tables.
Function:
============
CREATE OR REPLACE FUNCTION emp.fn_entry_audit()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
col_name text:='';
audit_table_name text := TG_TABLE_NAME || '_audit';
begin
if TG_OP = 'UPDATE' or TG_OP = 'INSERT' THEN
EXECUTE format('INSERT INTO emp.%1$I SELECT ($1).*,'''||TG_OP||'''',audit_table_name) using NEW;
else
EXECUTE format('INSERT INTO emp.%1$I SELECT ($1).*,'''||TG_OP||'''',audit_table_name) using old;
end if;
return new;
END $function$
Trigger creation
=================
create trigger trig_anish before insert or delete or update on emp.test_empname for each row execute procedure acaas.fn_entry_audit()
Table
======
create table emp.test_empname(id int4,fname varchar(300) not null,lname varchar(400),salary int4,last_modified_dt timestamp);
create table emp.test_empname_audit(id int4,fname varchar(300) not null,lname varchar(400),salary int4,last_modified_dt timestamp,modified_type varchar(10));
The difference between the two is modified_type column, which will mention whether the data is of insert, update or delete(TG_OP from above function).
now when i insert the values in emp.test_empname, it is getting inserted correctly in emp.test_empname_audit.
select * from emp.test_empname;
emp.test_empname:
==================
id fname lname salary last_modified_dt
===============================================================
1 stacker pentacost 1000 04-04-18
2 lyri pav 2000 04-04-18
3 TEST TEST1 1000 04-04-18
select * from emp.test_empname_audit;
id fname lname salary last_modified_dt modified_type
===============================================================
1 stacker pentacost 1000 04-04-18 INSERT
2 lyri pav 1000 04-04-18 INSERT
2 lyri pav 2000 04-04-18 UPDATE
3 TEST TEST1 1000 04-04-18 Delete
Now, the issue is whenever I perform delete on source table (test_empname), the query is executing fine, but it shows 0 rows affected.
when i query in the table select * from test_empname where id=3, it still exists.But you can see the entry in audit as delete.
I disabled the trigger and performed delete function ,it executes fine and the row gets affected.How is the trigger affecting my delete functionality.Please help!!
CREATE OR REPLACE FUNCTION acaas.fn_entry_audit1()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
col_name text:='';
audit_table_name text := TG_TABLE_NAME || '_audit';
begin
if TG_OP = 'UPDATE' or TG_OP = 'INSERT' THEN
EXECUTE format('INSERT INTO acaas.%1$I SELECT ($1).*,'''||TG_OP||'''',audit_table_name) using NEW;
return new;
else
EXECUTE format('INSERT INTO acaas.%1$I SELECT ($1).*,'''||TG_OP||'''',audit_table_name) using old;
return old;
end if;
END $function$
I have these tables:
CREATE EXTENSION citext;
CREATE EXTENSION "uuid-ossp";
CREATE TABLE cities
(
city_id serial PRIMARY KEY,
city_name citext NOT NULL UNIQUE
);
INSERT INTO cities(city_name) VALUES
('New York'), ('Paris'), ('Madrid');
CREATE TABLE etags
(
etag_name varchar(128) PRIMARY KEY,
etag_value uuid
);
INSERT INTO etags(etag_name, etag_value)
VALUES ('cities', uuid_generate_v4());
I want to update the cities etag when the cities table changes. If no rows are affected by the insert, update or delete statement, I'd like to avoid to change the cities etag, so I wrote the following statement level trigger:
CREATE OR REPLACE FUNCTION update_etag()
RETURNS trigger AS
$BODY$
DECLARE
record_count integer;
vetag_name varchar(128);
BEGIN
GET DIAGNOSTICS record_count = ROW_COUNT;
vetag_name := TG_ARGV[0];
RAISE NOTICE 'affected %:%', vetag_name, record_count;
IF record_count = 0 THEN
RETURN NULL;
END IF;
UPDATE etags SET etag_value = uuid_generate_v4()
WHERE etag_name = vetag_name;
RETURN null;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
CREATE TRIGGER update_cities_etag_trigger
AFTER INSERT OR UPDATE OR DELETE
ON cities
FOR EACH STATEMENT
EXECUTE PROCEDURE update_etag('cities');
However GET DIAGNOSTICS record_count = ROW_COUNT; doesn't work for me, as it always returns 0.
If I execute the following:
DELETE FROM cities;
The following is output:
NOTICE: affected cities:0 Query returned successfully: 3 rows
affected, 47 msec execution time.
Is there a way to figure out how many rows are affected by the statement that triggers the trigger in a PostgreSQL statement-level trigger?
Version 10
CREATE TRIGGER
...
[ REFERENCING { { OLD | NEW } TABLE [ AS ] transition_relation_name } [ ... ] ]
...
https://www.postgresql.org/docs/current/static/release-10.html
Add AFTER trigger transition tables to record changed rows (Kevin
Grittner, Thomas Munro)
Transition tables are accessible from triggers written in server-side
languages.
Example
Solves it:
CREATE OR REPLACE FUNCTION update_etag()
RETURNS trigger AS
$BODY$
DECLARE
record_count integer;
vetag_name varchar(128);
begin
IF (TG_OP = 'DELETE') or (TG_OP = 'UPDATE') THEN
select count(*) from oldtbl into record_count ;
ELSE
select count(*) from newtbl into record_count ;
END IF;
vetag_name := TG_ARGV[0];
RAISE NOTICE 'affected %:%:%', vetag_name,TG_OP, record_count;
IF record_count = 0 THEN
RETURN NULL;
END IF;
UPDATE etags SET etag_value = uuid_generate_v4()
WHERE etag_name = vetag_name;
RETURN null;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
CREATE TRIGGER update_ins_cities_etag_trigger
AFTER INSERT
ON cities
REFERENCING NEW TABLE AS newtbl
FOR EACH STATEMENT
EXECUTE PROCEDURE update_etag('cities');
CREATE TRIGGER update_upd_cities_etag_trigger
AFTER UPDATE
ON cities
REFERENCING OLD TABLE AS oldtbl
FOR EACH STATEMENT
EXECUTE PROCEDURE update_etag('cities');
CREATE TRIGGER update_del_cities_etag_trigger
AFTER DELETE
ON cities
REFERENCING OLD TABLE AS oldtbl
FOR EACH STATEMENT
EXECUTE PROCEDURE update_etag('cities');
so=# INSERT INTO cities(city_name) VALUES
so-# ('New York'), ('Paris'), ('Madrid');
NOTICE: affected cities:INSERT:3
INSERT 0 3
so=# select * from etags;
etag_name | etag_value
-----------+--------------------------------------
cities | dc7d1525-eea7-4822-b736-5141a20764f8
(1 row)
so=# insert into cities(city_name) values ('Budapest');
NOTICE: affected cities:INSERT:1
INSERT 0 1
so=# select * from etags;
etag_name | etag_value
-----------+--------------------------------------
cities | df835f44-dada-4a94-bb62-5890f2316103
(1 row)
so=# delete from cities where city_id > 42;
NOTICE: affected cities:DELETE:0
DELETE 0
so=# select * from etags;
etag_name | etag_value
-----------+--------------------------------------
cities | df835f44-dada-4a94-bb62-5890f2316103
(1 row)
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.