PostgreSQL意外触发行为

时间:2013-01-15 02:33:42

标签: sql postgresql postgresql-9.1

情况:

我有一个函数fn_SetFoo(),可以将记录插入表TableFoo

我还有一个触发器功能,在每次插入TableFoo后运行。它从新插入的行中获取新的主键TableFooID,并将其插入到第二个表TableFooBar中(使用外键约束)。

我创建了一个运行AFTER INSERT ON TableFoo FOR EACH ROW EXECUTE PROCEDURE fn_SetFooBar();

的触发器

如果我直接致电fn_SetFoo(),那么一切都会按预期进行。

但是,我有一个单独的函数fn_NormalizeFoo()来处理一些数据,然后为它处理的每个记录调用fn_SetFoo()

如果我拨打fn_NormalizeFoo(),则只会处理第一条记录并且该功能会停止。


问题:

fn_NormalizeFoo()fn_NormalizeFoo()调用时,当-------------------------------------------------------- -- Insert Into TableFoo -- -------------------------------------------------------- CREATE OR REPLACE FUNCTION "example"."fn_SetFoo" ( IN "Foo1" INTEGER, IN "Foo2" INTEGER, IN "Foo3" INTEGER ) RETURNS "void" AS $$ BEGIN INSERT INTO "example"."TableFoo"( "Foo1", "Foo2", "Foo3" ) VALUES ( $1, $2, $3 ); RETURN; EXCEPTION WHEN "unique_violation" THEN -- DO NOTHING END; $$ LANGUAGE plpgsql; -------------------------------------------------------- -- Insert Into TableFooBar -- -------------------------------------------------------- CREATE OR REPLACE FUNCTION "example"."fn_SetFooBar" ( IN "FooPK" INTEGER, IN "BarPK" INTEGER ) RETURNS "void" AS $$ BEGIN INSERT INTO "example"."TableFooBar"( "FooPK", "BarPK" ) VALUES ( $1, $2 ); RETURN; EXCEPTION WHEN "unique_violation" THEN -- DO NOTHING END; $$ LANGUAGE plpgsql; -------------------------------------------------------- -- Trigger Function -- -------------------------------------------------------- CREATE OR REPLACE FUNCTION "example"."tr_SetFooBar"() RETURNS TRIGGER AS $$ BEGIN PERFORM "example"."fn_SetFooBar"( "TableFoo"."FooPK", "TableBar"."BarPK" ) FROM "example"."TableFoo" JOIN "example"."TableBar" ON [SOMETHING TRUE] WHERE NEW.SOMECOLUMN = SOMETHING AND [MORE STUFF IS TRUE]; RETURN NULL; END; $$ LANGUAGE plpgsql; -------------------------------------------------------- -- Trigger -- -------------------------------------------------------- CREATE TRIGGER "SetFooBar" AFTER INSERT ON "example"."Foo" FOR EACH ROW EXECUTE PROCEDURE "example"."tr_SetFooBar"(); -------------------------------------------------------- -- Normalize Foo -- -------------------------------------------------------- CREATE OR REPLACE FUNCTION "example"."fn_NormaliseFoo" ( IN "Param1" VARCHAR, IN "Param2" VARCHAR, IN "Param3" VARCHAR ) RETURNS "void" AS $$ SELECT "example"."fn_SetFoo" ( "Foo1", "Foo2", "Foo3" ) FROM [TABLES] WHERE [STUFF IS TRUE] $$ LANGUAGE SQL; 的内容直接运行整个过程时,为什么该过程会在第一条记录后停止?


一些代码:

"example"."fn_NormaliseFoo"

正如您所看到的,这比我最初发布的要复杂一些。一般的想法是在添加每条记录时创建多对多的关系。

重申一下,在第一行之后运行{{1}}失败;但是,手动运行内容按预期工作。

1 个答案:

答案 0 :(得分:0)

fn_NormalizeFoo()被描述为:

  

一个单独的函数fn_NormalizeFoo()进行处理   一些数据,然后为它处理的每个记录调用fn_SetFoo()。

但是,为fn_NormaliseFoo显示的代码是在SQL语言中声明的,因此它不是程序性的,因此它无法执行所声称的操作。它可以只运行一个查询并返回其结果(如果没有结果,则返回任何结果)。此类代码必须与内联调用查询兼容。

所以第一个问题是fn_NormaliseFoo被声明为返回void,但它与它是SELECT的事实不兼容。通常情况下,sql语言的解释器甚至不应接受函数创建。 例如:

CREATE FUNCTION f(int) returns void as 'select $1;' language sql;

这失败了:

  

错误:声明返回void的函数返回类型不匹配   DETAIL:实际返回类型是整数。

第二个问题是fn_SetFoo可能只被调用一次,这是我从问题的“过程在第一个记录之后停止”中理解的。尽管调用在一个查询的选择列表中,其中一些连接表可能产生N行,但是没有理由在这里可以看到SQL引擎将其称为N次。更好的是它只调用一次,并将相同的结果影响到作为查询输出形成的每一行。

看起来有点像sql plpgsql。为了确保函数被调用N次,在过程代码中循环N次,这将保证有效。