`

postgresql数据库中触发器的小结

阅读更多
转载自:http://blog.chinaunix.net/u2/72255/showart_2259523.html
postgresql数据库中触发器的小结
  最近因工作需要,需要在postgresql中写个触发器,经过几天的看手册, 查资料,写代码,对代码的测试,终于对触发器有了一定的了解, 为了防止自己忘记,同时希望能给需要的朋友一定的帮助,写下了这篇文章.(感悟: 看书到了一定程度,如果能写个功能类似但不完全一样的代码,那个时候自己的理解会加深很多....哈哈...).好了开始正题.....
  一个触发器是一种声明,告诉数据库应该在执行特定的操作的时候执行特定的函数。触发器可以定义在一个 INSERT, UPDATE, DELETE 命令之前或者之后执行,要么是对每行执行一次,要么是对每条 SQL 语句执行一次。如果发生触发器事件,那么将在合适的时刻调用触发器函数以处理该事件。触发器函数必须在创建触发器之前,作为一个没有参数并且返回 trigger 类型的函数定义。触发器函数通过特殊的 TriggerData 结构接收其输入,而不是用普通的函数参数方式.
    PostgreSQL 提供按行与按语句触发的触发器。按行触发的触发器函数为触发语句影响的每一行执行一次;按语句触发的触发器函数为每条触发语句执行一次,而不管影响的行数。特别是,一个影响零行的语句将仍然导致按语句触发的触发器执行。这两种类型的触发器有时候分别叫做行级触发器和语句级触发器。触发器还通常分成 before 触发器和 after 触发器。语句级别的"before"触发器通常在语句开始做任何事情之前触发,而语句级别的"after"触发器在语句结束时触发。行级别的"before"触发器在对特定行进行操作之前触发,而行级别的"after"触发器在语句结束的时候触发(但是在任何语句级别的"after"触发器之前)。
注意:
一.按语句触发的触发器应该总是返回 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 trigger manager");
}
/* 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_connect returned: %d", ret);
     return PointerGetDatum(rettuple);
}
/* check the permanent table name(perm_user) exists or 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 AS SELECT * FROM tbl_user;", 0);
     if (ret != SPI_OK_SELINTO ) {
         elog(INFO, "SPI_exec execute error: fail to create perm_user");
         SPI_finish();
         return PointerGetDatum(rettuple);
     }
     /* set attribute to perm_user */
     ret = SPI_exec("ALTER TABLE perm_user ADD PRIMARY KEY (user_name);", 0);
     if (ret != SPI_OK_SELINTO) {
         elog(INFO, "SPI_exec execute error: fail to add primary key to perm_user");
         SPI_finish();
         return PointerGetDatum(rettuple);
     }
     /* set attribute to perm_user*/
     ret = SPI_exec("ALTER TABLE perm_user ALTER user_passwd SET NOT NULL;", 0);
     if (ret != SPI_OK_SELINTO) {
         elog(INFO, "SPI_exec execute error: fail to set attribute to password.");
         SPI_finish();
         return PointerGetDatum(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 error tbl_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 AS SELECT * FROM tbl_member;", 0);
     if (ret != SPI_OK_SELINTO) {
         elog(INFO, "SPI_exec execute error");
         SPI_finish();
         return PointerGetDatum(rettuple);
     }
     /* set attribute to perm_member */
     ret = SPI_exec("ALTER TABLE perm_member ADD CONSTRAINT user_fk FOREIGN KEY (user_name) REFERENCES perm_user(user_name) ON DELETE CASCADE ON UPDATE CASCADE;", 0); 
     if (ret != SPI_OK_UTILITY) {
         elog(INFO, "SPI_exec execute error: fail to set attribute to user_name.");
         SPI_finish();
         return PointerGetDatum(rettuple);
     }
     /* set attribute to perm_member */
     ret = SPI_exec("ALTER TABLE perm_member ADD CONSTRAINT group_fk FOREIGN KEY (grp_name) REFERENCES perm_group(grp_name) ON DELETE CASCADE ON UPDATE CASCADE;", 0); 
     if (ret != SPI_OK_UTILITY) {
         elog(INFO, "SPI_exec execute 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 ADD PRIMARY KEY (user_name, grp_name);", 0); 
     if (ret != SPI_OK_UTILITY) {
         elog(INFO, "SPI_exec execute error: fail to add primary key to perm_member.");
         SPI_finish();
         return PointerGetDatum(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 config memory */
CREATE OR REPLACE FUNCTION pg_trigf() RETURNS trigger
       AS 'filename' 
     LANGUAGE C IMMUTABLE STRICT;

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语句就可以看到触发器的效果了.
哈哈... 大功终于告成......
我上面写的代码是测试过了的,可以使用.
如果你有什么问题,我很希望你能来和我讨论, 这样我们就可以共同进步.
如果你要转载我的这篇文章,麻烦你注明出处.(因为这个花了我不少的心血,为了写个触发器,我花了好几天啊.....).
分享到:
评论

相关推荐

    PostgreSQL数据库内核分析

    PostgreSQL数据库内核分析PostgreSQL数据库内核分析PostgreSQL数据库内核分析PostgreSQL数据库内核分析PostgreSQL数据库内核分析PostgreSQL数据库内核分析PostgreSQL数据库内核分析PostgreSQL数据库内核分析...

    PostgreSQL数据库内核分析.pdf

    PostgreSQL数据库内核分析.pdf

    Python编写PostgreSQL数据库结构比对程序源代码

    标题中的“Python编写PostgreSQL数据库结构比对程序源代码”意味着我们有一个用Python编写的工具,这个工具能够对比两个PostgreSQL数据库的结构,以检查它们是否一致。这在数据库迁移、升级或备份恢复等场景中非常...

    PostgreSQL数据库内核分析 清晰版

    PostgreSQL数据库内核分析 清晰版,完整415页

    PostgreSQL数据库内核分析 完整版

    《PostgreSQL数据库内核分析》彭智勇 完整版44M

    C# 操作PostgreSQL 数据库

    在这个例子中,`connectionString`包含了连接到PostgreSQL数据库所需的所有信息。你需要替换`your_server`, `your_port`, `your_database`, `your_user` 和 `your_password` 为实际的数据库参数。 一旦连接建立,...

    连接postgresql数据库需要的jar包

    在Java编程中,连接到PostgreSQL数据库通常需要特定的驱动程序,这个驱动程序通常是以JAR(Java Archive)文件的形式存在。"连接postgresql数据库需要的jar包"指的是用于建立Java应用程序与PostgreSQL数据库之间通信...

    asp连接postgresql数据库

    asp连接postgresql数据库 的源码

    如何恢复PostgreSQL数据库

    PostgreSQL 是一个功能强大且广泛使用的开源关系数据库管理系统,但是在实际应用中,数据库崩溃或无法启动的情况时有发生。这时,如何恢复 PostgreSQL 数据库变得非常重要。下面将详细介绍如何恢复 PostgreSQL ...

    postgresql数据库备份和恢复

    文档中提到的pg_dump是PostgreSQL提供的一个命令行工具,它专门用于导出数据库到一个SQL脚本文件中,或者归档文件(使用`-Fc`参数)。这个命令支持数据库的完全备份和部分备份。完全备份涉及整个数据库的数据和结构...

    nacos适配postgresql数据库

    1.nacos服务,适配postgresql数据库。 2.提供nacos,postgresql的创建nacos数据库脚本。 3.nacos/conf/nacos-pg.sql数据库脚本文件。 4.nacos版本1.4.2。

    postgresql10数据库生成文档工具

    在本工具中,可能涉及的是将PostgreSQL 10的数据库结构转换成易于阅读和共享的文档格式。 5. **一键生成**:一键生成功能意味着用户只需进行简单的设置和选择,即可自动生成数据库文档,节省了大量的手动编写时间,...

    Python-一个PostgreSQL数据库迁移工具

    Python中的PostgreSQL数据库迁移工具是开发者在管理数据库结构变更时的重要辅助工具。这些工具使得数据库的版本控制变得简单,允许开发者安全地更新数据库结构,同时保持数据的完整性。本篇文章将详细探讨“Python-...

    postgresql数据库定时备份脚本(linux)

    在Linux环境中,对PostgreSQL数据库进行定时备份是确保数据安全的重要步骤。PostgreSQL是一个功能强大的开源关系型数据库系统,广泛应用于各种规模的企业和项目。定时备份可以帮助我们在系统故障、误操作或其他不可...

    Matlab与PostgreSQL数据库的连接

    在 Matlab 中,使用 JDBC 连接 PostgreSQL 数据库需要下载对应的 JDBC 驱动程序,然后将其添加到 Matlab 的环境中。 知识点:PostgreSQL 的 JDBC 驱动程序 PostgreSQL 的 JDBC 驱动程序可以从官方网站下载,例如...

    使用Bucardo搭建PostgreSQL数据库双主同步.docx

    不过,Bucardo 中的同步都是异步的,它是通过触发器记录变化,甚至可以以 PostgreSQL 为源库,可以和 Oracle、MySQL、MongoDB 等很多数据库进行数据异步同步。 二、搭建前准备 在开始搭建 Bucardo 之前,需要满足...

    postgresQL数据库备份脚本

    postgresQL数据库备份脚本,添加到任务计划中就可以备份数据库文件了。

    PostgreSQL数据库对象名大小写敏感的解决方法

    尽管在MS SQL Server中双引号和单引号均可作为字符串常量的标识符,但在PostgreSQL中,双引号具有特殊意义——它们用于标识数据库对象名,而不能用于字符串常量。为了代码的一致性和避免潜在的混淆,始终推荐使用单...

    Window 下的PostgreSQL 数据库备份和恢复工具[GUI].

    Window 下的PostgreSQL 数据库备份和恢复工具[GUI],供大家一起共同分享使用。

Global site tag (gtag.js) - Google Analytics