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

时间:2014-05-29 09:11:27

标签: 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我也需要指定字段,所以我不知道该怎么做。

1 个答案:

答案 0 :(得分:20)

这很难解决,因为SQL要求在通话时知道返回类型 此外,plpgsql函数需要具有明确定义的返回类型

如果您选择返回匿名记录,则会获得您定义的内容:匿名记录。 Postgres不知道里面是什么。因此,列定义列表 required 可以分解类型。

根据具体要求,有各种变通方法。如果您有任何方式知道在呼叫时的返回类型,我建议多态类型,如本答案最后一章(“各种完整的表类型”)所述:
Refactor a PL/pgSQL function to return the output of various SELECT queries

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

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

您的代码中有几个其他问题。见下面的注释。

概念证明

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;

电话必须在一次交易中。您可能必须开始显式交易,具体取决于您的客户。

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

<强> SQL Fiddle.

或者,您可以在会话期间让临时表生效。但是要注意在重复呼叫中命名冲突。

注释