以列名为参数修改行的触发函数

Iva*_*van 9 postgresql trigger dynamic-sql plpgsql

我正在尝试在 Postgres 9.4 中编写一个触发器函数。像这样的东西(还没有工作):

  CREATE FUNCTION set_point_from_coords(source _regclass, target _regclass) 
    RETURNS trigger AS $func$
  BEGIN
    NEW.target := ST_SetSRID(ST_Point(NEW.source[1], NEW.source[2]), 4326);
    RETURN NEW;
  END;
  $func$ LANGUAGE plpgsql
Run Code Online (Sandbox Code Playgroud)

在这种情况下,target是一个类型的列geometry并且source是一个小数数组。

当一行插入coords数组时,我想将其转换为point. 如果我只是对列名进行硬编码,则上述方法会起作用,但我想使用相同的函数为不同的表和不同的列对执行此操作。而且我对它INSERT本身没有直接控制权。
这是我的一些实验:http : //sqlfiddle.com/#!15/ddddcd/1

找到了这篇相关的博客文章,我很难解析它。

如果这样更容易编码,我可以在插入/更新之后而不是之前运行它。

Erw*_*ter 17

问题

你必须选择所有可能的并发症聚集在一起的地方。

  • SQL(或 PL/pgSQL)不允许参数化标识符EXECUTE为此,您需要使用动态 SQL 。

  • 但是NEW触发器函数中的特殊 plpgsql 变量在使用EXECUTE.

  • 通过将列名作为参数传递给CREATE TRIGGER.

  • 并且使目标列动态化是不够的,您想从该行的另一个动态列中获取源值。

除非您对所有相关问题都了如指掌,否则我宁愿尝试更简单的方法。为每个触发器编写单独的触发器函数,并分配给目标列,无需动态 SQL。

解决方案

这就是说,它可以用做一个代码行-和解释很多行。对于问题中的原始示例,假设此表定义:

CREATE TABLE tbl (
  tbl_id serial PRIMARY KEY,
  geom geometry,
  coords double precision[]
);
Run Code Online (Sandbox Code Playgroud)

您需要hstore安装附加模块(每个数据库一次)才能工作。或者,您也可以将json_populate_record()/jsonb_populate_record()的未记录功能用于相同目的:

CREATE OR REPLACE FUNCTION trg_demo()
  RETURNS trigger AS 
$func$
BEGIN
   EXECUTE format('SELECT ($1 #= hstore(%L, ST_SetSRID(ST_Point($1.%2$I[1], $1.%2$I[2]), 4326)::text)).*'
                , TG_ARGV[0], TG_ARGV[1])  -- target (geom), source (coords)
   USING  NEW
   INTO   NEW;
   RETURN NEW;
END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

扳机:

CREATE TRIGGER demo
BEFORE INSERT OR UPDATE ON tbl
FOR EACH ROW EXECUTE PROCEDURE trg_demo('geom', 'coords');
Run Code Online (Sandbox Code Playgroud)

如果你不明白这里发生了什么,请考虑我上面的建议。

解释

我格式化了geometry 粗体的动态计算,以帮助您掌握这一点。与下面的简单案例进行比较。

db<> fiddle here - 使用point而不是geometry,因为未安装 PostGIS。
旧的sqlfiddle

这是一个更简单的版本,只分配空text值(这需要目标列geom是 type text)。再次加粗的简化部分:

   EXECUTE format('SELECT ($1 #= hstore(%L, $1.%I::text)).*'
                , TG_ARGV[0], TG_ARGV[1])  -- target (geom), source (coords)
   USING  NEW
   INTO   NEW;
Run Code Online (Sandbox Code Playgroud)

核心功能是hstore 运算符#=(根据文档):

record用匹配的值替换中的字段hstore

目标源都是新行的列,这使问题复杂化。如果源值是一个常量,我们可以简单地:

NEW := NEW #= hstore(TG_ARGV[0], 'POINT(123.0 456.0)');
Run Code Online (Sandbox Code Playgroud)

有关的:

但是我们需要动态 SQL 来解析列名并获取源值。

  • 用于format()安全地连接查询字符串。

  • TG_ARGV[0]并将TG_ARGV[1]前两个元素访问到(从 0 开始!)传递的参数文本数组CREATE TRIGGER

  • %I连接format()作为标识符传递给的参数(安全防止 SQL 注入)。

  • $1引用传递到值EXECUTEUSING的条款。

  • 我们需要::text在计算几何图形后进行转换,因为hstore需要text.

  • 我们需要NEW将分配的行分解回来,因为 plpgsql 将行分配为逐列的目标。