postgreSQL触发器

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了postgreSQL触发器相关的知识,希望对你有一定的参考价值。

插入数据时候,数据写入到A表的同时,将数据入库到B一份,并判断是否存在重复(a,b,c三列的组合重复),如果有重复,将重复的老数据的time字段设置为当前时间。如果不存在,直接在当前时间对应的B表中插入一条time的数据

PostgreSQL 提供按行与按语句触发的触发器。按行触发的触发器函数为触发语句影响的每一行执行一次;按语句触发的触发器函数为每条触发语句执行一次,而不管影响的行数。特别是,一个影响零行的语句将仍然导致按语句触发的触发器执行。这两种类型的触发器有时候分别叫做行级触发器和语句级触发器。触发器还通常分成 before 触发器和 after 触发器。语句级别的"before"触发器通常在语句开始做任何事情之前触发,而语句级别的"after"触发器在语句结束时触发。行级别的"before"触发器在对特定行进行操作之前触发,而行级别的"after"触发器在语句结束的时候触发(但是在任何语句级别的"after"触发器之前)。
一个触发器是一种声明,告诉数据库应该在执行特定的操作的时候执行特定的函数。触发器可以定义在一个 INSERT, UPDATE, DELETE 命令之前或者之后执行,要么是对每行执行一次,要么是对每条 SQL 语句执行一次。如果发生触发器事件,那么将在合适的时刻调用触发器函数以处理该事件。触发器函数必须在创建触发器之前,作为一个没有参数并且返回 trigger 类型的函数定义。触发器函数通过特殊的 TriggerData 结构接收其输入,而不是用普通的函数参数方式.

注意:
一.按语句触发的触发器应该总是返回 NULL.
二.如果必要,按行触发的触发器函数可以给调用它的执行者返回一行数据(一个类型为 HeapTuple 的数值),那些在操作之前触发的触发器有以下选择
1. 它可以返回 NULL 以忽略对当前行的操作。这就指示执行器不要执行调用该触
发器的行级别操作(对特定行的插入或者更改)。
2.只用于 INSERT 和 UPDATE 行触发器:返回的行将成为被插入的行或者是成为
将要更新的行。这样就允许触发器函数修改将要被插入或者更新的行。
一个无意导致任何这类行为的在操作之前触发的行级触发器必须仔细返回那个被当作新行传进来的行。也就是说,对于 INSERT 和 UPDATE 触发器而言,是 NEW 行,对于 DELETE触发器而言,是 OLD 行。
三. 对于在操作之后触发的行级触发器,其返回值会被忽略,因此可以回NULL。

下面通过具体的例子来说明在postgresql中触发器的建立和使用(老规矩先写代码然后讲解)
#include <postgres.h>
#include <executor/spi.h>
#include <funcapi.h>
#include <commands/trigger.h>
#include <fmgr.h>
extern Datum pg_trigf(PG_FUNCTION_ARGS);
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
PG_FUNCTION_INFO_V1(pg_trigf);
Datum
pg_trigf(PG_FUNCTION_ARGS)

TriggerData *trigdata = (TriggerData *)fcinfo->context;
HeapTuple rettuple = NULL;
int ret;
int proc; /* to store the value of SPI_processed (actual row number)*/

/* to be sure this function will be called by trigger */
if (!(CALLED_AS_TRIGGER(fcinfo)))
elog(ERROR, "trigf: not called by triggermanager");

/* should be fired by statement */
if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
elog(ERROR, "cannot process row events");

/* should be fired before event */
if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
elog(ERROR, "must be fired before event");

/* connect spi manager */
if ((ret = SPI_connect()) < 0)
elog(INFO, "SPI_connect failed: SPI_connectreturned: %d", ret);
return PointerGetDatum(rettuple);

/* check the permanent table name(perm_user) existsor not*/
ret = SPI_exec("SELECT tablename FROM pg_tables WHERE tablename LIKE'perm!_user' ESCAPE '!';", 1);
proc = SPI_processed;
if (ret != SPI_OK_SELECT)
elog(INFO, "SPI_exec execute error: user table.");
SPI_finish();
return PointerGetDatum(rettuple);


/* create the permanent table(perm_user) if it does not exist */
if (proc < 1)
/* create permanent table: perm_user */
ret = SPI_exec("CREATE TABLE perm_user ASSELECT * FROM tbl_user;", 0);
if (ret != SPI_OK_SELINTO )
elog(INFO, "SPI_execexecute error: fail to create perm_user");
SPI_finish();
returnPointerGetDatum(rettuple);

/* set attribute to perm_user */
ret = SPI_exec("ALTER TABLE perm_user ADD PRIMARYKEY (user_name);", 0);
if (ret != SPI_OK_SELINTO)
elog(INFO, "SPI_execexecute error: fail to add primary key to perm_user");
SPI_finish();
returnPointerGetDatum(rettuple);

/* set attribute to perm_user*/
ret = SPI_exec("ALTER TABLE perm_user ALTERuser_passwd SET NOT NULL;", 0);
if (ret != SPI_OK_SELINTO)
elog(INFO, "SPI_execexecute error: fail to set attribute to password.");
SPI_finish();
returnPointerGetDatum(rettuple);


.....

/* check the permanent table name(perm_member) exists or not */
ret = SPI_exec("SELECT tablename FROM pg_tables WHERE tablename LIKE'perm!_member' ESCAPE '!';", 1);
proc = SPI_processed;
if (ret != SPI_OK_SELECT)
elog(INFO, "SPI_exec execute errortbl_member");
SPI_finish();
return PointerGetDatum(rettuple);

/* create the permanent table(perm_member) if it does not exist */
if (proc < 1)
/* create permanent table: perm_member */
ret = SPI_exec("CREATE TABLE perm_member ASSELECT * FROM tbl_member;", 0);
if (ret != SPI_OK_SELINTO)
elog(INFO, "SPI_execexecute error");
SPI_finish();
returnPointerGetDatum(rettuple);

/* set attribute to perm_member */
ret = SPI_exec("ALTER TABLE perm_member ADDCONSTRAINT user_fk FOREIGN KEY (user_name) REFERENCES perm_user(user_name) ONDELETE CASCADE ON UPDATE CASCADE;", 0);
if (ret != SPI_OK_UTILITY)
elog(INFO, "SPI_execexecute error: fail to set attribute to user_name.");
SPI_finish();
returnPointerGetDatum(rettuple);

/* set attribute to perm_member */
ret = SPI_exec("ALTER TABLE perm_member ADDCONSTRAINT group_fk FOREIGN KEY (grp_name) REFERENCES perm_group(grp_name) ONDELETE CASCADE ON UPDATE CASCADE;", 0);
if (ret != SPI_OK_UTILITY)
elog(INFO, "SPI_execexecute error: fail to set attribute to grp_name.");
SPI_finish();
return PointerGetDatum(rettuple);

/* add primary key to perm_member */
ret = SPI_exec("ALTER TABLE perm_member ADDPRIMARY KEY (user_name, grp_name);", 0);
if (ret != SPI_OK_UTILITY)
elog(INFO, "SPI_execexecute error: fail to add primary key to perm_member.");
SPI_finish();
returnPointerGetDatum(rettuple);


/*close connect with SPI manager */
SPI_finish();
/* return back must be NULL*/
return PointerGetDatum(rettuple);

这个函数写法与postgresql服务端函数的写法很相似, 但是不完全相同.具体需要注意的地方是:
1. 需要多添加头文件:#include <commands/trigger.h>
2. 这个函数的返回值一定是trigger类型的.
3. 函数的开始最好确认这个函数是供触发器调用的并且明确一下自己要写的触发器的类型是什么,然后做一下判断,以免别的语句也触发的触发器.

二. 接下来的事情是编译:
gcc -fpic -c trigger.c-I/usr/local/postgreSQL/include/postgresql/server
gcc -shared -o trigger.so trigger.o
如果不明白可以参考手册(说句题外话,手册的作用实在是太大了,在手册中也提供了一例子).

三. 在数据库中创建函数和触发器:
/* create a trigger used to write memory and configmemory */
CREATE OR REPLACE FUNCTION pg_trigf() RETURNS trigger
AS 'filename'
LANGUAGE C IMMUTABLESTRICT;

CREATE TRIGGER tbuser BEFORE INSERT OR UPDATE OR DELETE
ON tbl_user FOR EACH STATEMENT
EXECUTE PROCEDURE pg_trigf();
CREATE TRIGGER tbgroup BEFORE INSERT OR UPDATE OR DELETE
创建的触发器是语句触发器,这个和手册上的不一样, 手册上的是行触发器.
然后在数据库中使用SQL语句就可以看到触发器的效果了.
参考技术A 我怎么感觉用存储,比用触发器要好呀.
不过我给您一个使用触发器的例子,至于如何实现,相信您定能从中得到启发.
目标:
当表alphas插入新行时,更新titles的alpha_at为NOW()
当表alphas删除行时,更新titles的alpha_at为NULL

1、安装plpgsql语言到数据库

createlang plpgsql DATABASE

2、建立一个返回为trigger的过程

CREATE OR REPLACE FUNCTION after_alphas_id() RETURNS trigger AS $BODY$
BEGIN
IF( TG_OP='DELETE' ) THEN
UPDATE titles SET alpha_at=null WHERE id=OLD.title_id;
ELSE
UPDATE titles SET alpha_at=NOW() WHERE id=NEW.title_id;
END IF;
RETURN NULL;
END;

$BODY$
LANGUAGE 'plpgsql';

3、创建触发器

CREATE TRIGGER after_alphas_id
AFTER INSERT OR DELETE
ON alphas
FOR EACH ROW
EXECUTE PROCEDURE after_alphas_id();本回答被提问者采纳

PostgreSQL 触发器

PostgreSQL 触发器

PostgreSQL 触发器是数据库的回调函数,它会在指定的数据库事件发生时自动执行/调用。
下面是关于 PostgreSQL 触发器几个比较重要的点:

  1. PostgreSQL 触发器可以在下面几种情况下触发:

  2. 在执行操作之前(在检查约束并尝试插入、更新或删除之前)。

  3. 在执行操作之后(在检查约束并插入、更新或删除完成之后)。

  4. 更新操作(在对一个视图进行插入、更新、删除时)。

  5. 触发器的 FOR EACH ROW 属性是可选的,如果选中,当操作修改时每行调用一次;相反,选中 FOR EACH
    STATEMENT,不管修改了多少行,每个语句标记的触发器执行一次。

  6. WHEN 子句和触发器操作在引用 NEW.column-name
    和 OLD.column-name 表单插入、删除或更新时可以访问每一行元素。其中 column-name 是与触发器关联的表中的列的名称。

  7. 如果存在 WHEN 子句,PostgreSQL 语句只会执行 WHEN 子句成立的那一行,如果没有 WHEN 子句,PostgreSQL 语句会在每一行执行。

  8. BEFORE 或 AFTER 关键字决定何时执行触发器动作,决定是在关联行的插入、修改或删除之前或者之后执行触发器动作。

  9. 要修改的表必须存在于同一数据库中,作为触发器被附加的表或视图,且必须只使用 tablename,而不database.tablename。

  10. 当创建约束触发器时会指定约束选项。这与常规触发器相同,只是可以使用这种约束来调整触发器触发的时间。当约束触发器实现的约束被违反时,它将抛出异常。

语法

创建触发器时的基础语法如下:

CREATE  TRIGGER trigger_name [BEFORE|AFTER|INSTEAD OF] event_name
ON table_name[
 -- 触发器逻辑....];

在这里,event_name 可以是在所提到的表 table_name 上的 INSERT、DELETE 和 UPDATE 数据库操作。您可以在表名后选择指定 FOR EACH ROW。
以下是在 UPDATE 操作上在表的一个或多个指定列上创建触发器的语法:

CREATE  TRIGGER trigger_name [BEFORE|AFTER] UPDATE OF column_name
ON table_name[
 -- 触发器逻辑....];

实例

让我们假设一个情况,我们要为被插入到新创建的 COMPANY 表(如果已经存在,则删除重新创建)中的每一个记录保持审计试验:

highgo=# CREATE TABLE COMPANY(
   ID INT PRIMARY KEY     NOT NULL,
   NAME           TEXT    NOT NULL,
   AGE            INT     NOT NULL,
   ADDRESS        CHAR(50),
   SALARY         REAL);

为了保持审计试验,我们将创建一个名为 AUDIT 的新表。每当 COMPANY 表中有一个新的记录项时,日志消息将被插入其中:

highgo=# CREATE TABLE AUDIT(
   EMP_ID INT NOT NULL,
   ENTRY_DATE TEXT NOT NULL);

在这里,ID 是 AUDIT 记录的 ID,EMP_ID 是来自 COMPANY 表的 ID,DATE 将保持 COMPANY 中记录被创建时的时间戳。所以,现在让我们在 COMPANY 表上创建一个触发器,如下所示:

highgo=# CREATE TRIGGER example_trigger AFTER INSERT ON COMPANY FOR EACH ROW EXECUTE PROCEDURE auditlogfunc();

auditlogfunc() 是 PostgreSQL 一个程序,其定义如下:

CREATE OR REPLACE FUNCTION auditlogfunc() RETURNS TRIGGER AS $example_table$
   BEGIN
      INSERT INTO AUDIT(EMP_ID, ENTRY_DATE) VALUES (new.ID, current_timestamp);
      RETURN NEW;
   END;
$example_table$ LANGUAGE plpgsql;

现在,我们开始往 COMPANY 表中插入数据:

highgo=# INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)
VALUES (1, '张三', 32, '济南', 20000.00 )

这时,COMPANY 表中插入了一条记录:
同时, AUDIT 表中也插入了一条记录,因为我们在插入 COMPANY 表时创建了一个触发器。相似的,我们也可以根据需求在更新和删除时创建触发器:

emp_id |          entry_date--------+-------------------------------
      1 | 2021-11-23 15:49:59.968+05:30(1 row)

列出触发器
你可以把从 pg_trigger 表中把当前数据库所有触发器列举出来:

highgo=# SELECT * FROM pg_trigger;

如果,你想列举出特定表的触发器,语法如下:

highgo=# SELECT tgname FROM pg_trigger, pg_class WHERE tgrelid=pg_class.oid AND relname='company';

得到结果如下:

 tgname-----------------
 example_trigger
(1 row)

删除触发器

删除触发器基础语法如下:

drop trigger $trigger_name on $table_of_trigger_dependent;

删除本文上表 company 上的触发器 example_trigger 的指令为:

drop trigger example_trigger on company;

以上是关于postgreSQL触发器的主要内容,如果未能解决你的问题,请参考以下文章

Postgresql查询库结构及表属性SQL总结

PostgreSQL 日志触发器/函数查询数据

sql Lista触发了emmm banco de dados postgresql

postgresql的规则系统

sql 基于触发器的等效旧PostgreSQL时间旅行模块 - 请参阅http://blog.myitcv.org.uk/2014/02/25/row-level-version-control-wi

postgresql 上的更新类型