postgresql - 从 PL/pgSQL 函数返回具有未知列的动态表

标签 postgresql polymorphism plpgsql dynamic-sql return-type

我需要创建一个函数来检查给定表中是否存在 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;

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

最佳答案

这很难解决,因为 SQL 要求在调用时知道返回类型。
此外,plpgsql 函数需要有一个明确定义的返回类型

如果您选择返回匿名记录,您将得到您定义的:匿名记录。 Postgres 不知道里面有什么。因此,需要列定义列表来分解类型。

有多种解决方法,具体取决于具体要求。如果您有办法在调用时知道返回类型,我建议使用多态类型,如本答案最后一章(“各种完整的表类型”)中所述:

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

至于您当前的方法,我能想到的最接近的是一个临时表 ( or a cursor ),您可以在单个事务第二次调用中查询强>.

您的代码中还有一些其他问题。请参阅下面的注释。

概念验证

CREATE OR REPLACE FUNCTION f_tbl_plus_infowindow (_tbl regclass)  -- parameter name
  RETURNS void  -- no direct return type
  LANGUAGE plpgsql AS
$func$
DECLARE
   _tmp text := quote_ident(_tbl::text || '_tmp');  -- apending _tmp for temp table
BEGIN
   -- Create temp table only for duration of transaction
   EXECUTE format(
      'CREATE TEMP TABLE %s ON COMMIT DROP AS
       SELECT * FROM %s LIMIT 0'
      ,_tmp, _tbl
     );

   IF EXISTS (
      SELECT 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($$
         ALTER  TABLE %1$s ADD COLUMN infowindow text;
         INSERT INTO %1$s
         SELECT *, 'ID: ' || id::text
         FROM   %2$s$$
        ,_tmp, _tbl
        );
   END IF;
END
$func$;

调用必须在单个事务中。根据您的客户,您可能必须启动显式事务。

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

db<> fiddle here
<子> Old sqlfiddle

或者,您可以让临时表在 session 期间一直存在。不过,请注意重复调用的命名冲突。

注意事项

使用参数名称代替 outdated ALIAS command .

要实际“默认”到当前模式,请使用我显示的更简单的查询。使用 regclass自动完成技巧。详情:

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

您的 ELSE 中的代码子句根本不起作用。

TABLE tbl;基本上是 SELECT * FROM tbl; 的缩写.

Details on format() in the manual.

关于postgresql - 从 PL/pgSQL 函数返回具有未知列的动态表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23929707/

相关文章:

mysql - SQLITE 请求语法错误

C++接口(interface)设计问题

c++ - vptr——虚拟表

haskell - Haskell 中的类型级多态性

postgresql - 动态语句 PL/PGSQL 中的错误(函数和运算符最多可以采用一个集合参数)

sql - 在窗口函数中从 psql 返回行数组

postgresql - 在 EXISTS 条件下创建索引

database - 在 PL/pgSQL 中使用变量存储查询结果

postgresql - 从 Postgres 中提取 XML 元素值

sql - PostgreSQL 9.3 : Filter in Pivot table query