views:

217

answers:

2

Using Postgresql.

I try to use TRIGGER procedure to make some consistency check on INSERT.

The question is ......

whether "BEFORE INSERT FOR EACH ROW" can make sure each row to insert "checked" and "inserted" one after another? do I need extra lock on table to survive from concurrent insert?

check for new row1 -> insert row1 -> check for new row2 -> insert row2

--
--

-- unexpired product name is unique.
CREATE TABLE product (
  "name"    VARCHAR(100) NOT NULL,
  "expired" BOOLEAN      NOT NULL
);

CREATE OR REPLACE FUNCTION check_consistency() RETURNS TRIGGER AS $$
  BEGIN
    IF EXISTS (SELECT * FROM product WHERE name=NEW.name AND expired='false') THEN
      RAISE EXCEPTION 'duplicated!!!';              
    END IF;
    RETURN NEW;
  END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_check_consistency
BEFORE INSERT ON product
  FOR EACH ROW EXECUTE PROCEDURE check_consistency();

--
INSERT INTO product VALUES("prod1", true);
INSERT INTO product VALUES("prod1", false);
INSERT INTO product VALUES("prod1", false); // exception!

this is OK

 name | expired
 ==============
 p1   |  true
 p1   |  true
 p1   |  false

This is not OK

 name | expired
 ==============
 p1   |  true
 p1   |  false
 p1   |  false

or maybe I should ask, how can I use Trigger to implement "Primary" or "Unique" constraint-like SQL.

A: 

Why can't you use a unique key to enforce this?

StarShip3000
I want to apply the unique constraint only for some specific column value. Take the example from the code above. The same product names can exist many times if they are all expired, But the product with expired = false must have unique name.
elgcom
You can create the unique index as a partial index as Stephan Denne shows. See http://www.postgresql.org/docs/8.4/interactive/indexes-partial.html
StarShip3000
Thanks, partial index is exact the solution!!!But I still don't know if Trigger "FOR EACH ROW" can guarantee a sequential UPDATE or INSERT, so that concurrent table INSERT or UPDATE would not violate the constraint.
elgcom
+2  A: 

Your example can be done with a unique index:

CREATE UNIQUE INDEX uq_check_consistency ON product ( name ) WHERE NOT expired;

This will result in a statement within a second transaction that would that could inviolate the constraint, blocking till the first transaction commits or rolls back.

Edited to add:

To get similar (or more complex) transactionally safe behaviour with triggers, you can create a CONSTRAINT trigger, that is deferred till transaction commit time. These trigger functions need to be AFTER triggers, checking whether your constraint has been violated:

CREATE OR REPLACE FUNCTION after_check_consistency() RETURNS TRIGGER AS $$
  BEGIN
    IF (SELECT count(*) FROM product WHERE name=NEW.name AND expired='false') > 1 THEN
      RAISE EXCEPTION 'duplicated!!!';              
    END IF;
    RETURN NULL;
  END;
$$ LANGUAGE plpgsql;


CREATE CONSTRAINT TRIGGER trigger_check_consistency
AFTER INSERT OR UPDATE ON product
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW EXECUTE PROCEDURE after_check_consistency();
Stephen Denne