如何在函数的 UPDATE 或 SELECT 语句中使用动态列名?

Posted

技术标签:

【中文标题】如何在函数的 UPDATE 或 SELECT 语句中使用动态列名?【英文标题】:How to use dynamic column names in an UPDATE or SELECT statement in a function? 【发布时间】:2015-08-24 22:42:10 【问题描述】:

在 PostgreSQL 9.1 PL/pgSQL 中,给定一个查询:

select fk_list.relname from ...

其中relname 的类型为name(例如,“table_name”)。

如何获得可以直接在UPDATE 语句中使用的“relname”的适当值:

Update <relname> set ...

在 PL/pgSQL 脚本中?

quote_ident(r.relname) 用作:

Update quote_ident(r.relname) Set ...

失败:

“(”处或附近的语法错误 第 55 行:更新 quote_ident(r.relname) ....

我正在使用的完整代码:

CREATE FUNCTION merge_children_of_icd9 (ocicd9 text, 
                      ocdesc text, ncicd9 text, ncdesc text)
RETURNS void AS $BODY$
DECLARE
  r RECORD;
BEGIN
  FOR r IN
    WITH fk_actions ( code, action ) AS (
      VALUES ('a', 'error'), 
             ('r', 'restrict'), 
             ('c', 'cascade'),
             ('n', 'set null'),
             ('d', 'set default')
    ), fk_list AS (
      SELECT pg_constraint.oid AS fkoid, conrelid, confrelid::regclass AS parentid,
             conname, relname, nspname,
             fk_actions_update.action AS update_action,
             fk_actions_delete.action AS delete_action,
             conkey AS key_cols
      FROM pg_constraint
      JOIN pg_class ON conrelid = pg_class.oid
      JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid
      JOIN fk_actions AS fk_actions_update ON confupdtype = fk_actions_update.code
      JOIN fk_actions AS fk_actions_delete ON confdeltype = fk_actions_delete.code
      WHERE contype = 'f'
    ), fk_attributes AS (
      SELECT fkoid, conrelid, attname, attnum
      FROM fk_list
      JOIN pg_attribute ON conrelid = attrelid AND attnum = ANY(key_cols)
      ORDER BY fkoid, attnum
    ), fk_cols_list AS (
      SELECT fkoid, array_agg(attname) AS cols_list
      FROM fk_attributes
      GROUP BY fkoid
    )
    SELECT fk_list.fkoid, fk_list.conrelid, fk_list.parentid, fk_list.conname, 
           fk_list.relname, fk_cols_list.cols_list
    FROM fk_list
    JOIN fk_cols_list USING (fkoid)
    WHERE parentid = 'icd9'::regclass
  LOOP
    RAISE NOTICE 'now in loop. relname is %', quote_ident(r.relname);
    RAISE NOTICE 'cols_list[1] is %', quote_ident(r.cols_list[1]);
    RAISE NOTICE 'cols_list[2] is %', quote_ident(r.cols_list[2]);
    RAISE NOTICE 'now doing update';

    UPDATE quote_ident(r.relname) SET r.cols_list[1] = ncicd9, r.cols_list[2] = ncdesc
    WHERE r.cols_list[1] = ocicd9 AND r.cols_list[2] = ocdesc;

    RAISE NOTICE 'finished update'; 
  END LOOP;     
  RETURN;
END $BODY$ LANGUAGE plpgsql VOLATILE;   

-- select merge_children_of_icd9('', 'aodm type 2', '', 'aodm, type 2'); 

我确信这种事情经常发生,但我似乎无法使用 PostgreSQL 找到类似的东西。有没有更好的办法?

【问题讨论】:

向我们展示您的函数的完整代码。 @a_horse_with_no_name 好的。请参阅问题编辑。任何批评都更受欢迎:-) 谢谢。 【参考方案1】:

在 PL/pgSQL 中的 UPDATE 语句中,表名必须以文字形式给出。如果你想动态设置表名和列,你应该使用EXECUTE命令并将查询字符串粘贴在一起:

EXECUTE 'UPDATE ' || quote_ident(r.relname) ||
       ' SET ' || quote_ident(r.cols_list[1]) || ' = $1, ' || 
                  quote_ident(r.cols_list[2]) || ' = $2' ||
       ' WHERE ' || quote_ident(r.cols_list[1]) || ' = $3 AND ' ||
                    quote_ident(r.cols_list[2]) || ' = $4'
USING ncicd9, ncdesc, ocicd9, ocdesc;

USING 子句只能用于替换数据值,如上所示。

【讨论】:

【参考方案2】:

您需要带有EXECUTE 的动态SQL,就像已经提供的@Patrick。 但是,您的函数和 EXECUTE 部分都可以简单得多。特别是,使用format() 安全地连接更长的查询字符串(从 pg 9.1 开始可用):

CREATE OR REPLACE FUNCTION merge_children_of_icd9 (_ocicd9 text, _ocdesc text
                                                 , _ncicd9 text, _ncdesc text)
  RETURNS void AS
$func$
DECLARE
   _sql text;
BEGIN
   FOR _sql IN
      SELECT format('UPDATE %3$s SET %1$I = $3   , %2$I = $4
                     WHERE           %1$I = $1 AND %2$I = $2'
                   , x.cols[1], x.cols[2], x.conrelid::regclass::text)
      FROM  (
         SELECT c.conrelid, array_agg(a.attname ORDER BY a.attnum) AS cols
         FROM   pg_constraint c
         JOIN   pg_attribute  a ON a.attrelid = c.conrelid
                               AND a.attnum = ANY(c.conkey)
         WHERE  c.confrelid = 'icd9'::regclass
         AND    c.contype = 'f'
         GROUP  BY c.oid, c.conrelid
         ORDER  BY c.oid
         ) x
   LOOP
   -- RAISE NOTICE '%', _sql;
      EXECUTE _sql
      USING   _ocicd9, _ocdesc, _ncicd9, _ncdesc;
   END LOOP;
END
$func$  LANGUAGE plpgsql;

如果 FK 约束没有跨越至少两列,或者列的数据类型与 text 不兼容,则函数会出错。可能会也可能不会像预期的那样。

详细介绍如何安全传递标识符并执行动态 SQL:

Table name as a PostgreSQL function parameter Define table and column names as arguments in a plpgsql function?

【讨论】:

以上是关于如何在函数的 UPDATE 或 SELECT 语句中使用动态列名?的主要内容,如果未能解决你的问题,请参考以下文章

从 update 语句成功调用函数,但从 select 语句调用时出错

无效的 SQL语句;期待 'DELETE'、'INSERT'、'PROCEDURE'、'SELECT'、或 'UPDATE

在db2数据库9.7中,有时候执行一条update或select语句,要耗时很久,几个小

无效的SQL语句;预期的DELETE,INSERT,PROCEDURE,SELECT或UPDATE?

如何获取 SQL Server 中 INSERT、UPDATE、DELETE、SELECT 语句的速率?

无效的 SQL语句;期待 'DELETE'、'INSERT'、'PROCEDURE'、'SELECT'、或 'UPDATE