PL/pgSQL:更新触发器中 N 列的一般方法?

diz*_*tar 3 postgresql triggers types dynamic-sql plpgsql

我正在尝试创建一个函数,该函数将采用通用表并将 N 列转换为大写。我没有找到此类问题的解决方案,但我能够提出以下建议:

create or replace function uc_on_insert()
returns trigger as
$$
declare
p_tbl varchar = TG_TABLE_NAME;
p_sch varchar = TG_TABLE_SCHEMA;
i varchar;
begin
    for i in 

    (select column_name
    from  INFORMATION_SCHEMA.COLUMNS
    where 1=1
    and table_name ilike p_tbl
    and table_schema ilike p_sch
    and data_type ='character varying')

    loop
           execute 'new.' || i || ' = upper(new.' || i || ');';
       return new;
    end loop;

end;
$$ language plpgsql;
Run Code Online (Sandbox Code Playgroud)

我目前收到此错误:

ERROR:  syntax error at or near "new"
LINE 1: new.c1 = upper(new.c1);
        ^
QUERY:  new.c1 = upper(new.c1);
Run Code Online (Sandbox Code Playgroud)

预期的输入是,在任何有此触发器的表上:

insert into table_one('a', 'b');

>> A, B
Run Code Online (Sandbox Code Playgroud)

如果我将此触发器放在另一个表上:

insert into table_two ('a', 3);
>> A, 3
Run Code Online (Sandbox Code Playgroud)

ETC。

Erw*_*ter 5

这是一个非常棘手的问题。

您的尝试注定会失败,因为当前行变量NEWEXECUTE. 即使是,NEW也是行类型(记录),而不是数组。与数组元素不同,行的字段不能通过数字索引引用。这会导致 SQL 中出现各种问题,因为(与数组相反)每个字段可以有不同的数据类型,而 SQL 期望事先知道要处理的数据类型。确实非常棘手。

仅针对选定类型的通用方法

幸运的是,我们之前处理过类似的问题:

您会在那里找到充足的解释。
适用于触发器函数,并且根据列的数据类型,它可能如下所示:

触发功能

CREATE OR REPLACE FUNCTION trg_uc_on_insert()
  RETURNS trigger AS
$func$
BEGIN

EXECUTE 'SELECT ' || array_to_string(ARRAY(
      SELECT CASE WHEN atttypid = 'varchar'::regtype
                    -- atttypid = ANY('{text, bpchar, varchar}'::regtype[])
                THEN 'upper(($1).' || quote_ident(attname)
                                   || ')::' || atttypid::regtype::text 
                ELSE '($1).' || quote_ident(attname)
             END AS fld
      FROM   pg_catalog.pg_attribute
      WHERE  attrelid = pg_typeof(NEW)::text::regclass
      AND    attnum > 0
      AND    attisdropped = FALSE
      ORDER  BY attnum
      ), ',')
USING  NEW
INTO   NEW;

RETURN NEW;

END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

如果您想将相同的规则也应用于其他基本字符类型,请使用带注释的替代方案。

扳机

CREATE TRIGGER trg_t_insbef
BEFORE INSERT
ON t              -- works for any table
FOR EACH ROW
EXECUTE PROCEDURE trg_uc_on_insert();
Run Code Online (Sandbox Code Playgroud)

SQL 小提琴。

简单、不具体的方法

只要您仅在表中使用简单类型并希望将所有字符数据大写,还有另一种粗暴、快速且简单的方法:将整行转换为text,将文本表示形式大写,然后转换回该行输入并更新NEW结果。有关表的行类型的详细信息

触发功能

CREATE OR REPLACE FUNCTION trg_uc_simple_on_insert()
  RETURNS trigger AS
$func$
BEGIN

EXECUTE 'SELECT ($1::' || pg_typeof(NEW) || ').*'
   USING upper(NEW::text)
   INTO NEW;

RETURN NEW;

END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

SQL 小提琴。

我们必须分解行类型,因为SELECT INTO将目标行类型的各个字段一一分配。我们不能一次分配整行。如果仔细观察,“通用”解决方案的作用是相同的,但不太明显。

虽然字符数据在文本表示中区分大小写,但其他基本数字或日期/时间数据类型则不然。所以这个简单的方法工作可靠。可能也适用于大多数其他类型。但我没有和别人测试过,肯定有例外。您必须验证您使用的数据类型。

此外,虽然代码比通用方法更短、更简单,但这不一定更快,特别是对于许多不受影响的列。不过,在简单的情况下它可能会快得多。