Question

I have this table

CREATE TABLE "UserCouponSentMail"
(
  "IdUser" integer NOT NULL,
  "IdCoupon" integer NOT NULL,
  "SendType" character varying(100),
  "Date" timestamp without time zone NOT NULL DEFAULT ('now'::text)::timestamp without time zone,
  CONSTRAINT "pk_UserCouponSentMail" PRIMARY KEY ("IdUser" , "IdCoupon" ),
  CONSTRAINT "pk_UserCouponSentMail_GroceryCoupon" FOREIGN KEY ("IdCoupon")
      REFERENCES "GroceryCoupon" ("IdGroceryCoupon") MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH ( OIDS=FALSE );

and this function

CREATE OR REPLACE FUNCTION "UserCouponSentMailInsertOrUpdate"(integer, integer, character varying, timestamp without time zone)
  RETURNS void AS
$BODY$
BEGIN
    IF (EXISTS(SELECT * FROM "UserCouponSentMail" WHERE "IdUser" = $1 AND "IdCoupon" = $2)) THEN
        UPDATE "UserCouponSentMail" SET
            "SendType" = $3,
            "Date" = $4
        WHERE
            "IdUser" = $1 AND "IdCoupon" = $2;
    ELSE
        INSERT INTO "UserCouponSentMail" VALUES ($1, $2, $3, $4);
    END IF;
    RETURN;
END
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

And I don't know why, but somehow I sometimes receive this error while running that function

Unique violation: 7 ERROR: duplicate key value violates unique constraint "pk_UserCouponSentMail" CONTEXT: SQL statement "INSERT INTO "UserCouponSentMail" VALUES ( $1 , $2 , $3 , $4 )" PL/pgSQL function "UserCouponSentMailInsertOrUpdate" line 9 at SQL statement

Any idea on how could this happen?

The possibility of two scripts running this function with the same parameters at the very same time is almost impossible.

I'm using PostgreSQL 8.4.11 on x86_64-redhat-linux-gnu, compiled by GCC gcc (GCC) 4.4.6 20110731 (Red Hat 4.4.6-3), 64-bit.

Thanks.

Was it helpful?

Solution

Concurrent executions with identical values for (IdUser,IdCoupon) seem like the only plausible explanation.

The question says it's almost impossible but multiple executions can't always be foresighted. For example, in the context of a web application, a double-post of a form with the exact same data may occur.

To avoid the error at the plpgsql level, you can use an exception block:

BEGIN
  IF (EXISTS...) THEN
     UPDATE the unique corresponding row
  ELSE
     INSERT new row
  END IF;
EXCEPTION WHEN unique_violation THEN
  UPDATE the unique row
END;

Also you may want to add some log in the exception block about the context and the time of the event in the hope of understanding why the concurrent executions happen.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top