如何使用动态SQL设置复合变量字段的值

Dav*_*dEG 12 postgresql types stored-procedures composite plpgsql

鉴于此类型:

-- Just for testing purposes:
CREATE TYPE testType as (name text)
Run Code Online (Sandbox Code Playgroud)

我可以使用此函数动态获取字段的值:

CREATE OR REPLACE FUNCTION get_field(object anyelement, field text) RETURNS text as
$BODY$
DECLARE
    value text;
BEGIN
    EXECUTE 'SELECT $1."' || field || '"'
      USING object
       INTO value;

    return value;
END;
$BODY$
LANGUAGE plpgsql
Run Code Online (Sandbox Code Playgroud)

呼叫get_field('(david)'::testType, 'name')按预期工作返回"大卫".

但是如何在复合类型中设置字段的值?我试过这些功能:

CREATE OR REPLACE FUNCTION set_field_try1(object anyelement, field text, value text)
RETURNS anyelement
as
$BODY$
DECLARE
    value text;
BEGIN
    EXECUTE '$1."' || field || '" := $2'
      USING object, value;

    return object;
END;
$BODY$
LANGUAGE plpgsql

CREATE OR REPLACE FUNCTION set_field_try2(object anyelement, field text, value text)
RETURNS anyelement
as
$BODY$
DECLARE
    value text;
BEGIN
    EXECUTE 'SELECT $1 INTO $2."' || field || '"'
      USING value, object;

    return object;
END;
$BODY$
LANGUAGE plpgsql

CREATE OR REPLACE FUNCTION set_field_try3(object anyelement, field text, value text)
RETURNS anyelement
as
$BODY$
DECLARE
    value text;
BEGIN
    EXECUTE 'BEGIN $1."' || field || '" := $2; SELECT $1; END;'
       INTO object
      USING value, object;

    return object;
END;
$BODY$
LANGUAGE plpgsql
Run Code Online (Sandbox Code Playgroud)

和一些变化.通话set_field_tryX无效.我总是得到"错误:语法错误在...附近".我怎么能做到这一点?

笔记:

  • 参数是anyelement,字段可以是复合类型中的任何字段.我不能只使用object.name.
  • 我关心SQL注入.任何建议都会受到赞赏,但这不是我的问题.

Erw*_*ter 14

更快 hstore

自Postgres 9.0以来,在您的数据库中安装了附加模块hstore,#=操作员可以使用非常简单快速的解决方案......

record用匹配的值替换[s]字段hstore.

要安装模块:

CREATE EXTENSION hstore;
Run Code Online (Sandbox Code Playgroud)

例子:

SELECT my_record #= '"field"=>"value"'::hstore;  -- with string literal
SELECT my_record #= hstore(field, value);        -- with values
Run Code Online (Sandbox Code Playgroud)

text显然,值必须投射到后面.

示例plpgsql函数以及更多详细信息:

几乎同样快 json

在Postgres中有类似的,但目前未记录的(如第9.5页)解决方案json(pg 9.3+)或jsonb(pg 9.4+),因此您不需要额外的模块.

有关详细信息,请参阅@ Geir的补充答案.

没有hstorejson

如果您使用的是旧版本或无法安装附加模块hstore或无法假设已安装,则以下是我之前发布的改进版本.但仍然比hstore运营商慢:

CREATE OR REPLACE FUNCTION f_setfield(INOUT _comp_val anyelement
                                          , _field text, _val text)
  RETURNS anyelement AS
$func$
BEGIN

EXECUTE 'SELECT ' || array_to_string(ARRAY(
      SELECT CASE WHEN attname = _field
                THEN '$2'
                ELSE '($1).' || quote_ident(attname)
             END AS fld
      FROM   pg_catalog.pg_attribute
      WHERE  attrelid = pg_typeof(_comp_val)::text::regclass
      AND    attnum > 0
      AND    attisdropped = FALSE
      ORDER  BY attnum
      ), ',')
USING  _comp_val, _val
INTO   _comp_val;

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

呼叫:

CREATE TEMP TABLE t( a int, b text);  -- Composite type for testing
SELECT f_setfield(NULL::t, 'a', '1');
Run Code Online (Sandbox Code Playgroud)

笔记

  • _val不需要将值显式转换为目标数据类型,动态查询中的字符串文字将自动强制转换,从而避免使用子查询pg_type.但我更进了一步:

  • quote_literal(_val)通过该USING子句替换为直接值插入.保存一个函数调用和两个强制转换,无论如何都更安全.text在现代PostgreSQL中自动强制转换为目标类型.(未使用9.1之前的版本进行测试.)

  • array_to_string(ARRAY())比...更快string_agg().

  • 不需要变量,没有DECLARE.分配更少.

  • 动态SQL中没有子查询.($1).field是比较快的.

  • pg_typeof(_comp_val)::text::regclass

    (SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
    有效的复合类型相同,只是更快.
    最后一个修改建立在pg_type.typnamepg_class.relname注册复合类型的关联始终相同的假设之上,双重转换可以替换子查询.我在一个大型数据库中运行此测试以进行验证,并且它按预期显示为空:

    SELECT *
    FROM   pg_catalog.pg_type t
    JOIN   pg_namespace  n ON n.oid = t.typnamespace
    WHERE  t.typrelid > 0  -- exclude non-composite types
    AND    t.typrelid IS DISTINCT FROM
          (quote_ident(n.nspname ) || '.' || quote_ident(typname))::regclass
    
    Run Code Online (Sandbox Code Playgroud)
  • INOUT参数的使用消除了对显式的需要RETURN.这只是一个符号捷径.帕维尔不喜欢它,他更喜欢明确的RETURN声明......

所有东西放在一起几乎是以前版本的两倍.


原创(过时)答案:

结果是版本快了〜2.25倍.但是如果没有Pavel的第二个版本,我可能无法做到.

此外,这个版本通过在单个查询中执行所有操作来避免大部分转换为文本和返回,因此它应该更不容易出错.
使用PostgreSQL 9.0和9.1进行测试.

CREATE FUNCTION f_setfield(_comp_val anyelement, _field text, _val text)
  RETURNS anyelement AS
$func$
DECLARE
   _list text;
BEGIN
_list := (
   SELECT string_agg(x.fld, ',')
   FROM  (
      SELECT CASE WHEN a.attname = $2
              THEN quote_literal($3) || '::'|| (SELECT quote_ident(typname)
                                                FROM   pg_catalog.pg_type
                                                WHERE  oid = a.atttypid)
              ELSE quote_ident(a.attname)
             END AS fld
      FROM   pg_catalog.pg_attribute a 
      WHERE  a.attrelid = (SELECT typrelid
                           FROM   pg_catalog.pg_type
                           WHERE  oid = pg_typeof($1)::oid)
      AND    a.attnum > 0
      AND    a.attisdropped = false
      ORDER  BY a.attnum
      ) x
   );

EXECUTE 'SELECT ' || _list || ' FROM  (SELECT $1.*) x'
USING  $1
INTO   $1;

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


Pav*_*ule 7

我写了第二版的setfield函数.它适用于postgres 9.1我没有在旧版本上测试它.这不是一个奇迹(从性能角度来看),但它更强大,比前一个快8倍.

CREATE OR REPLACE FUNCTION public.setfield2(anyelement, text, text)
 RETURNS anyelement
 LANGUAGE plpgsql
AS $function$
DECLARE 
  _name text;
  _values text[];
  _value text;
  _attnum int;
BEGIN
  FOR _name, _attnum
     IN SELECT a.attname, a.attnum
          FROM pg_catalog.pg_attribute a 
         WHERE a.attrelid = (SELECT typrelid
                               FROM pg_type
                              WHERE oid = pg_typeof($1)::oid)
           AND a.attnum > 0 
  LOOP
    IF _name = $2 THEN
      _value := $3;
    ELSE
      EXECUTE 'SELECT (($1).' || quote_ident(_name) || ')::text' INTO _value USING $1;
    END IF;
    _values[_attnum] :=  COALESCE('"' || replace(replace(_value, '"', '""'), '''', '''''') || '"', ''); 
  END LOOP;
  EXECUTE 'SELECT (' || quote_ident(pg_typeof($1)::text) || ' ''(' || array_to_string(_values,',') || ')'').*' INTO $1; 
  RETURN $1;
END;
$function$;
Run Code Online (Sandbox Code Playgroud)


Gei*_*tad 5

更新/警告: Erwin指出,这目前记载的,并且手册指出,不可能以这种方式更改记录。

请改用hstore或Pavel的解决方案

这个基于json的简单解决方案几乎与hstore一样快,并且仅需要Postgres 9.3或更高版本。如果您不能使用hstore扩展,那么这应该是一个不错的选择,并且性能差异可以忽略不计。基准:https : //stackoverflow.com/a/28673542/1914376

a)我们可以通过cast / concat内联完成。Json函数需要Postgres 9.3:

SELECT json_populate_record( 
     record
    , ('{"'||'key'||'":"'||'new-value'||'"}')::json
);
Run Code Online (Sandbox Code Playgroud)

b)或通过使用Postgres 9.4中的函数进行内联。

SELECT json_populate_record (
      record
     ,json_object(ARRAY['key', 'new-value'])
);
Run Code Online (Sandbox Code Playgroud)

注意:我选择json_object(ARRAY [key,value])是因为它比json_build_object(key,value)快一点:

要隐藏转换细节,您可以在函数中使用a),而开销很小。

CREATE FUNCTION x.setfield_json(in_element anyelement, key text, value text)
    RETURNS anyelement AS
$BODY$
    SELECT json_populate_record( in_element, ('{"'||key||'":"'||value||'"}')::json);
$BODY$ LANGUAGE sql;
Run Code Online (Sandbox Code Playgroud)