使用异常处理修复 PL/pgsql 中的外键约束 (PostgreSQL)

Using Exception handling to fix foreign key constraint in PL/pgsql (PostgreSQL)

正在尝试学习 pgSQL (PostgreSQL 9.1) 中的异常处理。以下 SP 失败

ERROR: insert or update on table "dx" violates foreign key constraint "fk_icd9"
SQL state: 23503
Detail: Key (cicd9, cdesc)=(244.9, testing1) is not present in table "icd9".

fk_icd9 从 table dx 定义为:

CONSTRAINT fk_icd9 FOREIGN KEY (cicd9, cdesc)
  REFERENCES icd9 (cicd9, cdesc) MATCH SIMPLE
  ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED

我对SP的尝试是:

CREATE OR REPLACE FUNCTION g_test() RETURNS void AS $$
DECLARE
    r View_dx%rowtype;
BEGIN
  r.cicd9 := '244.9';
  r.groupid := 'BBBB      CCCC        199971230';  
  r.tposted := '2013-08-30 17:45:45'::timestamp;
  r.cdesc := 'testing1';

  LOOP
    BEGIN
      UPDATE dx SET cdesc = r.cdesc
      WHERE cicd9 = r.cicd9 AND groupid = r.groupid AND tposted = r.tposted;
    EXCEPTION 
      WHEN others THEN
        INSERT INTO icd9(cicd9, cdesc) VALUES (r.cicd9, r.cdesc);
    END;
    IF FOUND THEN
      RETURN;
    END IF; 
  END LOOP;
END; $$ LANGUAGE plpgsql;

我正在尝试更新 table, dx,它在第二个 table, icd9 中具有外键约束。如果 dx table 的更新因为这个约束而失败,那么我想在父 icd9 table 中插入新记录,然后循环回到第一个 table,dx,进行更新.

我做错了什么?这是怎么做到的?

编辑 #1:将如下所示的代码编辑为:

 create or replace function g_savedx3() returns void as
$$
 DECLARE

_cicd9 character varying(8);
_groupid character varying(33);
_tposted timestamp without time zone;
_cdesc character varying(80); 


BEGIN
_cicd9 := '244.9';
_groupid := 'BBBBB        AAAAA        199998';  
_tposted := '2013-08-30 17:45:45'::timestamp;
_cdesc := 'testing109';

LOOP
    BEGIN
        RAISE NOTICE 'About to update ';

        UPDATE dx SET cdesc = _cdesc
            WHERE 
                cicd9 = _cicd9 and 
                groupid = _groupid and tposted = _tposted;

        RAISE NOTICE 'Updated in g_saveDx3';

        IF FOUND THEN
            RETURN;
        END IF;


        EXCEPTION 
            WHEN others THEN
                RAISE NOTICE 'In exception g_saveDx3, about to insert';

                INSERT INTO icd9(cicd9,cdesc) VALUES (_cicd9, _cdesc);

                RAISE NOTICE 'In exception inserted';
    END;
END LOOP;
END;
$$
LANGUAGE plpgsql;

select g_savedx3();

产生以下消息:

注意:即将更新 注意:更新于 g_saveDx3

错误:在 table "dx" 上插入或更新违反了外键约束 "fk_icd9" 详细信息:密钥 (cicd9, cdesc)=(244.9, testing109) 不存在于 table "icd9" 中。 ********** 错误 **********

错误:在 table "dx" 上插入或更新违反了外键约束 "fk_icd9" SQL 状态:23503 详细信息: table "icd9".

中不存在密钥 (cicd9, cdesc)=(244.9, testing109)

注意:我在 updates violating foreign constraints 上找到了 Tom Lane (2004)

的旧条目

Yes it is ... you're expecting the RI triggers to fire during the plpgsql function, but in fact they fire at completion of the outer statement that called the plpgsql function.

There's been some debate about whether this is really the most desirable behavior, but that's how it is at the moment.

如果还是这样,或许就说明了问题。任何想法如何修复我的代码? (应该有用??)谢谢。

*** 如下所述,我猜这是默认行为,在我的例子中,它导致在 plpgsql 函数完成后调用异常。可以通过以下方式更改此行为(PostgreSQL 9.1):

SET CONSTRAINTS ALL IMMEDIATE;

完成这项工作需要哪些条件

如果有关系,这里是ICD9的定义table:

 CREATE TABLE icd9
(
 recid serial NOT NULL,
 cicd9 character varying(8),
 cdesc character varying(80) NOT NULL,
 "timestamp" timestamp without time zone DEFAULT now(),
 modified timestamp without time zone DEFAULT now(),
 chronic boolean NOT NULL DEFAULT false,
 CONSTRAINT pk_icd9_recid PRIMARY KEY (recid),
 CONSTRAINT constraint_cdesc UNIQUE (cicd9, cdesc),
 CONSTRAINT desccheck CHECK (cdesc::text <> ''::text)
)
WITH (
 OIDS=FALSE
);

在你的循环中你有

IF FOUND THEN
  RETURN;
END IF;

这会在循环进入 INSERT 之后的下一次迭代之前结束函数,因为 FOUND 也是由该命令设置的。

你想要的是:

LOOP
  BEGIN
    UPDATE dx SET cdesc = r.cdesc
    WHERE cicd9 = r.cicd9 AND groupid = r.groupid AND tposted = r.tposted;
    IF FOUND THEN
      RETURN;
    END IF;
  EXCEPTION 
    WHEN others THEN
      INSERT INTO icd9(cicd9, cdesc) VALUES (r.cicd9, r.cdesc);
  END;
END LOOP;

切换到老式调试。这是我的代码,它确实插入了。

create or replace function f () returns void as

$$
DECLARE
newval integer :=3 ;
BEGIN
LOOP
BEGIN
    RAISE NOTICE 'About to update ';
    UPDATE B SET ID2 = newval;
    RAISE NOTICE 'Updated ';
    IF FOUND THEN
      RETURN;
    END IF;
EXCEPTION 
    WHEN others THEN
    RAISE NOTICE 'In exception , about to insert';
      INSERT INTO a VALUES (newval);
RAISE NOTICE 'In exception inserted';
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;

执行:

select f();
NOTICE:  About to update 
NOTICE:  In exception , about to insert
NOTICE:  In exception inserted
NOTICE:  About to update 
NOTICE:  Updated 

Table 定义:

test=# \d+ a
                           Table "w2gi.a"
 Column |  Type   | Modifiers | Storage | Stats target | Description 
--------+---------+-----------+---------+--------------+-------------
 id     | integer | not null  | plain   |              | 
Indexes:
    "a_pkey" PRIMARY KEY, btree (id)
Referenced by:
    TABLE "b" CONSTRAINT "b_id2_fkey" FOREIGN KEY (id2) REFERENCES a(id)
Has OIDs: no
test=# \d+ b
                           Table "w2gi.b"
 Column |  Type   | Modifiers | Storage | Stats target | Description 
--------+---------+-----------+---------+--------------+-------------
 id1    | integer |           | plain   |              | 
 id2    | integer |           | plain   |              | 
Foreign-key constraints:
    "b_id2_fkey" FOREIGN KEY (id2) REFERENCES a(id)
Has OIDs: no