Postgres: how to add unique identifier to table - postgresql

I have the following table:
CREATE TABLE myid
(
nid bigserial NOT NULL,
myid character varying NOT NULL,
CONSTRAINT myid_pkey PRIMARY KEY (myid )
)
Now, I want to add records to this table with the following function:
CREATE FUNCTION getmyid(_myid character varying)
RETURNS bigint AS
$BODY$ --version 1.1 2015-03-04 08:16
DECLARE
p_nid bigint;
BEGIN
SELECT nid INTO p_nid FROM myid WHERE myid=_myid FOR UPDATE;
IF NOT FOUND THEN
INSERT INTO myid(myid) VALUES(_myid) RETURNING nid INTO p_nid;
END IF;
RETURN p_nid;
END;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Generally it works fine, but under high load, this function sometimes fails with "duplicate key value violates unique constraint "myid_pkey";
This function is called from trigger on insert on another table, and inserts are called within transaction. Isolation level is set for READ COMMITED, postgres 9.1 on Debian Wheezy.
What I'm doing wrong ?

I see following way how it happens.
Two processes(threads) call the function simultaneously with the same myid.
Both threads successfully execute SELECT nid INTO .. query, and see - there is no such myid in table now.
Both threads go into IF NOT FOUND THEN
Thread 1 executes INSERT INTO myid(myid) and commits transaction with no errors
Thread 2 executes INSERT INTO myid(myid) and fails, because same myid value already exists in table (PRIMARY KEY constraint).
Why Thread 2 sees other transaction committed data in own transaction ?
Because of 'non-repeatable read' phenomena, which is possible with READ COMMITTED isolation (http://www.postgresql.org/docs/9.2/static/transaction-iso.html).

Related

Auto increment depending on value of column in PostgreSQL

The question follows, can I auto increment (verification_number) depending on a value of a specific column (here business_uuid) so that verification_number increments by one (1) depending on that business_uuid's own highest number of verification_number?
Database looks as follows:
table: verification
verification_id = integer, sequence (Primary Key)
business_uuid = text
verification_number = integer
The verification_id is the Primary Key in this table and I want the verification_number to follow it's own auto increment depending on what it's highest value is filtered only for business_uuid.
The business_uuid is a unique identifier for each business.
Is it possible?
I think that this is a bad design, but if you really want that, you can achieve it as follows:
Create a unique constraint:
ALTER TABLE verification
ADD CONSTRAINT verification_uuid_nr_unique
UNIQUE (business_uuid, verification_number);
The index created by this will also make the following trigger function faster.
Create a BEFORE trigger to modify verification_number:
CREATE OR REPLACE FUNCTION veritrig() RETURNS trigger
LANGUAGE plpgsql AS
$$BEGIN
SELECT COALESCE(max(verification_number)+1, 1) INTO NEW.verification_number
FROM verification
WHERE business_uuid = NEW.business_uuid;
RETURN NEW;
END;$$;
CREATE TRIGGER veritrig
BEFORE INSERT OR UPDATE ON verification FOR EACH ROW
EXECUTE PROCEDURE veritrig();
Insert new values like this:
INSERT INTO verification (business_uuid) VALUES ('42');
Then verification_number will be set as you desire.
There is a problem with concurrency, however.
If several sessions try to insert or update the table concurrently, you will get an error like this:
ERROR: duplicate key value violates unique constraint "verification_uuid_nr_unique"
DETAIL: Key (business_uuid, verification_number)=(43, 1) already exists.
That is because the SELECT statements in concurrent modifications will only see the current (committed) table contents and may erroneously try to insert the same verification_number for a business_uuid.
There is no way to avoid that short of locking the table. But if you receive such an error, you can simply retry the transaction, and odds are that it will work the next time.
It is not clear if you want the verification_number to be saved to the table but it is possible to create it at query time:
select verification_id, business_uuid,
row_number() over(
partition by business_uuid
order by verification_id
) as verification_number
from verification
The problem with the above approach is that the verification_number will change if rows are deleted
Window functions
CREATE OR REPLACE FUNCTION public.creaid(
IN key_field text,
IN table_mane text,
OUT id integer)
RETURNS integer AS
$BODY$
DECLARE maxid INT;
BEGIN
EXECUTE 'SELECT max('||key_field||') FROM '||table_name INTO maxid;
IF maxid IS NULL THEN
id = 1;
ELSE
id = maxid + 1;
END IF;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Then, you can call that function as a default value of every key value of every table of your database, as follows:
DEFAULT creaid('key_field'::text, 'table_name'::text)

PostgreSQL trigger not firing. Can you see anything wrong?

Wrote this trigger 5-6 months ago, and it ran fine on the db I deployed it to. Just took it off the shelf, and pretty much dropped it on the same database which lives on a different box. Sucker isn't executing anymore.
Essentially, this trigger just copies the "before" row to an audit table. The audit table lives in a different db on the same server, and I link it back to the source database as a FOREIGN Table:
Audit db:
CREATE TABLE next_gen_permissions_change
(
id serial NOT NULL,
authorizable_type character varying(64),
authorizable_id integer,
grantee_id integer,
grantee_type character varying(255),
capability_id integer NOT NULL,
permission integer DEFAULT 0,
event_time timestamp without time zone,
event_type character varying(10)
)
GRANT ALL ON TABLE next_gen_permissions_change TO foo;
I create the foreign table in the "source" database:
CREATE EXTENSION postgres_fdw;
CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'localhost', dbname 'audit', port '5432');
CREATE USER MAPPING FOR foo SERVER myserver OPTIONS (user 'foo', password 'secret');
CREATE foreign TABLE next_gen_permissions_change
(
id serial NOT NULL,
authorizable_type character varying(64),
authorizable_id integer,
grantee_id integer,
grantee_type character varying(255),
capability_id integer NOT NULL,
permission integer DEFAULT 0,
event_time timestamp without time zone,
event_type character varying(10))
SERVER myserver;
GRANT ALL ON TABLE next_gen_permissions_change TO foo;
When I INSERT rows into source.next_gen_permissions_change, I see them show up in audit.next_gen_permissions_change...so I got that going for me.
Here's the function:
CREATE OR REPLACE FUNCTION audit_permissions()
RETURNS trigger AS
$BODY$
DECLARE
trigger_event_time timestamp;
BEGIN
trigger_event_time := now();
IF (TG_OP = 'INSERT') THEN
INSERT INTO next_gen_permissions_change(
id,
authorizable_type,
authorizable_id,
grantee_id,
grantee_type,
capability_id,
permission,
event_time,
event_type)
VALUES (NEW.id,
NEW.authorizable_type,
NEW.authorizable_id,
NEW.grantee_id,
NEW.grantee_type,
NEW.capability_id,
NEW.permission,
trigger_event_time,
'insert');
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
INSERT INTO next_gen_permissions_change(
id,
authorizable_type,
authorizable_id,
grantee_id,
grantee_type,
capability_id,
permission,
event_time,
event_type)
VALUES (OLD.id,
OLD.authorizable_type,
OLD.authorizable_id,
OLD.grantee_id,
OLD.grantee_type,
OLD.capability_id,
OLD.permission,
trigger_event_time,
'delete');
RETURN OLD;
END IF;
RETURN null;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
And now, I'm adding the function as a trigger:
CREATE TRIGGER next_gen_permissions_audit
AFTER INSERT OR DELETE
ON next_gen_permissions
FOR EACH ROW
EXECUTE PROCEDURE audit_permissions();
No errors, all seems well:
As you can see, I added RAISE NOTICE as well, so I could hopefully see this sucker executing in pgadmin.
Here are all of the properties of the trigger (parent window) and function (child window embedded inside the parent):
WHEN executing something like
UPDATE next_gen_permissions SET f1=f1
...I don't see anything in the messages.
Also,
EXPLAIN ANALYZE UPDATE next_gen_permissions SET f1=f1
doesn't return anything interesting:
Can anyone see what I'm doing wrong here? I'm not getting any error messages, and when I look at the logs, the UPDATE goes right through without ANY mention that the trigger is getting fired.
Thanks much.

PL/pgSQL query in PostgreSQL returns result for new, empty table

I am learning to use triggers in PostgreSQL but run into an issue with this code:
CREATE OR REPLACE FUNCTION checkAdressen() RETURNS TRIGGER AS $$
DECLARE
adrCnt int = 0;
BEGIN
SELECT INTO adrCnt count(*) FROM Adresse
WHERE gehoert_zu = NEW.kundenId;
IF adrCnt < 1 OR adrCnt > 3 THEN
RAISE EXCEPTION 'Customer must have 1 to 3 addresses.';
ELSE
RAISE EXCEPTION 'No exception';
END IF;
END;
$$ LANGUAGE plpgsql;
I create a trigger with this procedure after freshly creating all my tables so they are all empty. However the count(*) function in the above code returns 1.
When I run SELECT count(*) FROM adresse; outside of PL/pgSQL, I get 0.
I tried using the FOUND variable but it is always true.
Even more strangely, when I insert some values into my tables and then delete them again so that they are empty again, the code works as intended and count(*) returns 0.
Also if I leave out the WHERE gehoert_zu = NEW.kundenId, count(*) returns 0 which means I get more results with the WHERE clause than without.
--Edit:
Here is an example of how I use the procedure:
CREATE TABLE kunde (
kundenId int PRIMARY KEY
);
CREATE TABLE adresse (
id int PRIMARY KEY,
gehoert_zu int REFERENCES kunde
);
CREATE CONSTRAINT TRIGGER adressenKonsistenzTrigger AFTER INSERT ON Kunde
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE PROCEDURE checkAdressen();
INSERT INTO kunde VALUES (1);
INSERT INTO adresse VALUES (1,1);
It looks like I am getting the DEFERRABLE INITIALLY DEFERRED part wrong. I assumed the trigger would be executed after the first INSERT statement but it happens after the second one, although the inserts are not inside a BEGIN; - COMMIT; - Block.
According to the PostgreSQL Documentation inserts are commited automatically every time if not inside such a block and thus there shouldn't be an entry in adresse when the first INSERT statement is commited.
Can anyone point out my mistake?
--Edit:
The trigger and DEFERRABLE INITIALLY DEFERRED seem to be working all right.
My mistake was to assume that since I am not using a BEGIN-COMMIT-Block each insert would be executed in an own transaction with the trigger being executed afterwards every time.
However even without the BEGIN-COMMIT all inserts get bundled into one transaction and the trigger is executed afterwards.
Given this behaviour, what is the point in using BEGIN-COMMIT?
You need a transaction plus the "DEFERRABLE INITIALLY DEFERRED" because of the chicken and egg problem.
starting with two empty tables:
you cannot insert a single row into the person table, because the it needs at least one address.
you cannot insert a single row into the address table, because the FK constraint needs a corresponding row on the person table to exist
This is why you need to bundle the two inserts into one operation: the transaction. You need the BEGIN+ COMMIT, and the DEFERRABLE allows transient forbidden database states to exists: it causes the check to be evaluated at commit time.
This may seem a bit silly, but the answer is you need to stop deferring the trigger and run it BEFORE the insert. If you run it after the insert, of course there is data in the table.
As far as I can tell this is working as expected.
One further note, you probably dont mean:
RAISE EXCEPTION 'No Exception';
You probably want
RAISE INFO 'No Exception';
Then you can change your settings and run queries in transactions to test that the trigger does what you want it to do. As it is, every insert is going to fail and you have no way to move this into production without editing your procedure.

Capture columns in plpgsql during UPDATE

I am writing a trigger in plpgsql for Postgres 9.1. I need to be able to capture the column names that were issued in the SET clause of an UPDATE so I can record the specified action in an audit table. The examples in the Postgres documentation are simple and inadequate for my needs. I have searched the internet for days and I am unable to find any other examples that try to achieve what I want to do here.
I am on a tight schedule to resolve this soon. I don't know Tcl so pl/Tcl is out of the question for me at this point. pl/Perl may work but I don't know where to start with it. Also I wanted to find a way to accomplish this in pl/pgsql if at all possible for portability and maintenance. If someone can recommend a pl/Perl solution to this I would be grateful.
Here is the table structure of the target table that will be audited:
Note: There are many other columns in the record table but I have not listed them here in order to keep things simple. But the trigger should be able to record changes to any of the columns in the row.
CREATE TABLE record (
record_id integer NOT NULL PRIMARY KEY,
lastname text,
frstname text,
dob date,
created timestamp default NOW(),
created_by integer,
inactive boolean default false
);
create sequence record_record_id_seq;
alter table record alter record_id set default nextval('record_record_id_seq');
Here is my audit table:
CREATE TABLE record_audit (
id integer NOT NULL PRIMARY KEY,
operation char(1) NOT NULL, -- U, I or D
source_column text,
source_id integer,
old_value text,
new_value text,
created_date timestamp default now(),
created_by integer
);
create sequence record_audit_id_seq;
alter table record_audit alter id set default nextval('record_audit_id_seq');
My goal is to record INSERTS and UPDATES to the record table in the record_audit table that will detail not only what the target record_id was (source_id) that was updated and what column was updated (source_column), but also the old_value and the new_value of the column.
I understand that the column values will have to be CAST() to a type of text. I believe I can access the old_value and new_value by accessing NEW and OLD but I am having difficulty figuring out how to obtain the column names used in the SET clause of the UPDATE query. I need the trigger to add a new record to the record_audit table for every column specified in the SET clause. Note, there are not DELETE actions as records are simply UPDATED to inactive = 't' (and thus recorded in the audit table)
Here is my trigger so far (obviously incomplete). Please forgive me, I am learning pl/pgsql as I go.
-- Trigger function for record_audit table
CREATE OR REPLACE FUNCTION audit_record() RETURNS TRIGER AS $$
DECLARE
insert_table text;
ref_col text; --how to get the referenced column name??
BEGIN
--
-- Create a new row in record_audit depending on the operation (TG_OP)
--
IF (TG_OP = 'INSERT') THEN
-- old_value and new_value are meaningless for INSERTs. Just record the new ID.
INSERT INTO record_audit
(operation,source_id,created_by)
VALUES
('I', NEW.record_id, NEW.created_by);
ELSIF (TG_OP = 'UPDATE') THEN
FOR i in 1 .. TG_ARGV[0] LOOP
ref_col := TG_ARGV[i].column; -- I know .column doesn't exist but what to use?
INSERT INTO record_audit
(operation, source_column, source_id, old_value, new_value, created_by)
VALUES
('U', ref_col, NEW.record_id, OLD.ref_col, NEW.ref_col, NEW.created_by);
END LOOP;
END IF;
RETURN NULL; -- result is ignored anyway since this is an AFTER trigger
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER record_audit_trig
AFTER INSERT OR UPDATE on record
FOR EACH ROW EXECUTE PROCEDURE audit_record();
Thanks for reading this long and winding question!
you cannot to get this information - not in PL level - probably it is possible in C.
Good enough solution is based on changed fields in records NEW and OLD. You can get list of fields from system tables ~ are related to table that is joined to trigger.

how to get last Postresql serial ID that all rows before it are committed?

I am using Postgresql 9.0.5 and I have a cron job that periodically reads newly created rows from a table and accumulate its value into a summary table that has hourly data.
I need to get the latest ID (serial) that is committed and all rows before it are committed.
The currval function will not give a correct value in this case, because the transaction inserting currval may commit earlier than others. Using SELECT statement at a moment, I can see Id column is not continuous because some rows are still not committed.
Here is some sample code I have used to test:
--test race condition
create table mydata(id serial,val int);
--run in thread 1
create or replace function insert_delay() returns void as $$
begin
insert into mydata(val) values (1);
perform pg_sleep(60);
end;
$$ language 'plpgsql';
--run in thread 2
create or replace function insert_ok() returns void as $$
begin
insert into mydata(val) values (2);
end;
$$ language 'plpgsql';
--run in thread 3
mytest=# select * from mydata; --SHOULD HAVE SEEN id = 1 and 2;
id | val
----+-----
2 | 2
(1 row)
I even tried some statement like the one below;
select max(id) from mydata age(xmin) >= age(txid_snapshot_xmin(txid_current_snapshot())::text::xid);
But in production line (running high volume transactions), the returned max(id) will not move forwards (even all the busy transaction are finished). So this does not work either.
There isn't a really good way to do this directly. I think the best option really is to create a temporary table which truncates on transaction commit, and a trigger that inserts such into that table. Then you can look up the values from the temp table.