从PL/pgSQL函数返回包含未知列的动态表

Egi*_*idi 13 postgresql polymorphism return-type dynamic-sql plpgsql

我需要创建一个函数,检查给定的表是否infowindow存在该字段.如果它存在,则函数必须返回select * from table但如果不存在,则必须返回一个附加id字段:

CREATE OR REPLACE FUNCTION getxo_ocx_cincu_preparar_infowindow(
                                              guretabla character varying)
  RETURNS TABLE AS
$BODY$ 
DECLARE
    tabla ALIAS FOR $1;

BEGIN

IF  EXISTS (SELECT 1
   FROM   pg_namespace n
   JOIN   pg_class     c ON c.relnamespace = n.oid
   JOIN   pg_attribute a ON a.attrelid = c.oid 
   WHERE  n.nspname = current_schema()  -- default to current schema
   AND    c.relname = tabla
   AND    a.attname = 'infowindow'
   AND    NOT a.attisdropped)
THEN
    RETURN QUERY EXECUTE 'SELECT * from ' ||tabla ;
ELSE
    RETURN QUERY EXECUTE 'SELECT *, ID:' || id::text ||' as infowindow
                                   from ' ||tabla ;
END IF;

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

如果我使用RETURNS SETOF RECORDS,当我选择函数时,我需要指定列,我不知道.如果我使用RETURNS TABLE我也需要指定字段,所以我不知道该怎么做.

Erw*_*ter 21

这很难解决,因为SQL要求在调用时知道返回类型.
此外,plpgsql函数需要具有良好定义的返回类型.

如果您选择返回匿名记录,则会得到您定义的内容:匿名记录.Postgres不知道里面是什么.因此,一个字段定义列表需要分解的类型.

根据具体要求,有各种变通方法.如果您有任何方式在调用时知道返回类型,我建议在本答案的最后一章("各种完整的表类型")中概述的多态类型:
重构PL/pgSQL函数以返回各种SELECT查询的输出

但这并不包括在函数内的运行时向返回类型添加另一列.那是不可能的.我会重新考虑整个方法.

至于你目前的做法,我能想到的最接近的将是一个临时表(或光标),您在查询第二个电话一内单个事务.

的代码还有其他一些问题.见下面的注释.

概念证明

CREATE OR REPLACE FUNCTION f_tbl_plus_infowindow (_tbl regclass) -- regclass!
  RETURNS void AS  -- no direct return type
$func$
DECLARE
   -- appending _tmp for temp table
   _tmp text := quote_ident(_tbl::text || '_tmp');
BEGIN

-- Create temp table only for duration of transaction
EXECUTE format(
   'CREATE TEMP TABLE %s ON COMMIT DROP AS TABLE %s LIMIT 0', _tmp, _tbl);

IF EXISTS (
   SELECT 1
   FROM   pg_attribute a
   WHERE  a.attrelid = _tbl
   AND    a.attname  = 'infowindow'
   AND    a.attisdropped = FALSE)
THEN
   EXECUTE format('INSERT INTO %s SELECT * FROM %s', _tmp, _tbl);
ELSE
  -- This is assuming a NOT NULL column named "id"!
   EXECUTE format($x$
      ALTER  TABLE %1$s ADD COLUMN infowindow text;
      INSERT INTO %1$s
      SELECT *, 'ID: ' || id::text
      FROM   %2$s $x$
     ,_tmp, _tbl);
END IF;

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

呼叫必须在单个事务中.您可能必须根据您的客户启动显式交易.

BEGIN;
SELECT f_tbl_plus_infowindow ('tbl');
SELECT * FROM tbl_tmp;  -- do something with the returned rows
ROLLBACK;               -- or COMMIT, does not matter here
Run Code Online (Sandbox Code Playgroud)

SQL小提琴.

或者,您可以在会话期间让临时表生效.但是要注意使用重复调用来命名冲突.

笔记

  • 使用参数名称而不是过时的ALIAS命令.

  • 要实际"默认"到当前架构,请使用我显示的更简单的查询.使用regclass自动完成技巧.细节:

    此外,这还可以避免语法错误以及原始代码中非标准(或恶意格式错误)表名的SQL注入.

  • 你的ELSE条款中的代码根本不起作用.

  • TABLE tbl;基本上是短暂的SELECT * FROM tbl;.

  • format()手册中的详细信息.