PLPGSQL级联触发器?

时间:2012-03-07 11:53:06

标签: sql triggers plpgsql

我正在尝试创建一个触发器,这样当我添加新记录时,它会在同一个表中添加另一条记录。会话字段只会取1到4之间的值。因此,当我在会话中添加1时,我希望它添加另一条记录,但会话3被阻止。但问题是它会导致级联触发器并且它会一次又一次地插入,因为插入时会触发触发器。

我有一个简单的表格:

CREATE TABLE example
(
id      SERIAL PRIMARY KEY
,name   VARCHAR(100) NOT NULL
,session   INTEGER
,status VARCHAR(100)
);

我的触发功能是:

CREATE OR REPLACE FUNCTION add_block() RETURNS TRIGGER AS $$

BEGIN

INSERT INTO example VALUES (NEW.id + 1, NEW.name, NEW.session+2, 'blocked');

RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';

触发器是:

CREATE TRIGGER add_block
AFTER INSERT OR UPDATE
ON example
FOR EACH ROW
EXECUTE PROCEDURE add_block();

我收到错误:

SQL statement "INSERT INTO example VALUES ( $1 +1,  $2 ,  $3 + 2, $4)"
PL/pgSQL function "add_block" line 37 at SQL statement

这个错误重复了很多次,以至于我无法看到顶部。

我该如何解决这个问题?

编辑:

CREATE TABLE block_rules
(
id      SERIAL PRIMARY KEY
,session   INTEGER
,block_session   INTEGER
);

此表包含块规则。因此,如果使用会话1将新记录插入到EXAMPLE表中,则会通过在上面的相同(EXAMPLE)表中插入具有阻塞状态的新记录(而不是block_rules)来相应地阻止会话3。对于会话2也是如此,但它会阻止会话4。

block_rules表包含阻止会话的规则(或模式)。它持有

id | session | block_session
------------------------------
1  | 1       | 3
2  | 2       | 4
3  | 3       | 2

我如何把这个触发器的WHEN声明放在下面的Erwin Branstetter的答案中?

由于

3 个答案:

答案 0 :(得分:1)

好的,所以你不能只添加另一个列,如下所示:

ALTER TABLE example ADD COLUMN trig INTEGER DEFAULT 0;

CREATE OR REPLACE FUNCTION add_block() RETURNS TRIGGER AS $$
BEGIN
    IF NEW.trig = 0 THEN
        INSERT INTO example VALUES (NEXTVAL('example_id_seq'::regclass), NEW.name, NEW.session+2, 'blocked', 1);
    END IF;
    RETURN NULL;
 END;
 $$ LANGUAGE 'plpgsql';

它不是很好,但它有效: - )

答案 1 :(得分:1)

编辑问题的新答案

此触发器功能根据表block_rules中的信息添加被阻止的会话。

假设表格由id链接 - 问题中缺少信息。
我现在假设块规则是所有会话的一般规则,并且session链接。仅针对非阻塞会话调用触发器并插入匹配的阻止会话。

触发功能:

CREATE OR REPLACE FUNCTION add_block()
  RETURNS TRIGGER AS
$BODY$
BEGIN
    INSERT INTO example (name, session, status)
    VALUES (NEW.name
           ,(SELECT block_session
             FROM   block_rules
             WHERE  session = NEW.session)
           ,'blocked');

    RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;

触发:

CREATE TRIGGER add_block
AFTER INSERT -- OR UPDATE
ON example
FOR EACH ROW
WHEN (NEW.status IS DISTINCT FROM 'blocked')
EXECUTE PROCEDURE add_block();

回答原始问题

仍有改进的余地。考虑这个设置:

CREATE OR REPLACE FUNCTION add_block()
  RETURNS TRIGGER AS
$BODY$
BEGIN

INSERT INTO example (name, session, status)
VALUES (NEW.name, NEW.session + 2, 'blocked');

RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;


CREATE TRIGGER add_block
AFTER INSERT -- OR UPDATE
ON example
FOR EACH ROW
WHEN (NEW.session < 3)
-- WHEN (status IS DISTINCT FROM 'blocked') -- alternative guess at filter
EXECUTE PROCEDURE add_block();

主要观点:

  • 对于PostgreSQL 9.0或更高版本,您可以使用WHEN condition in the trigger definition。这将是最有效的。对于旧版本,您在触发器功能中使用相同的条件。

  • 如果您可以定义标准以识别自动插入的行,则无需添加列。你没告诉,所以我假设在我的例子中只有自动插入的行有session > 2。我为WHEN添加了另一个status = 'blocked'条件作为评论。

  • 您应该始终为INSERT提供列列表。如果不这样做,以后对表格的更改可能会产生意想不到的副作用!

  • 手动在触发器中插入NEW.id + 1。这不会递增序列,而下一个INSERT会因重复的密钥违规而失败。
    idserial列,所以不要做任何事情。序列中的默认nextval()会自动插入。

  • 您的说明仅提及INSERT,但您有一个触发器AFTER INSERT OR UPDATE。我删掉了UPDATE部分。

  • 不必引用关键字plpgsql

答案 2 :(得分:0)

CREATE OR REPLACE FUNCTION add_block() RETURNS TRIGGER AS $$
BEGIN
    SET SESSION session_replication_role = replica;
    INSERT INTO example VALUES (NEXTVAL('example_id_seq'::regclass), NEW.name, NEW.session+2, 'blocked');
    SET SESSION session_replication_role = origin;
    RETURN NULL;
 END;
 $$ LANGUAGE 'plpgsql';