使用函数的文本输出作为新查询

Use text output from a function as new query

从@Erwin Brandstetter 和@Craig Ringer 协助的 开始,我已将我的代码修改为如下所示。请注意,我的函数 myresult() 现在输出 text,而不是 table(事实上,正如在前一个案例中指出的那样,那里输出一个 table 对象是没有意义的,因为我们需要提前定义它的所有列,这基本上违背了整个目的):

CREATE OR REPLACE FUNCTION myresult(mytable text, myprefix text)
RETURNS text AS 
$func$
DECLARE
   myoneliner text;
BEGIN
   SELECT INTO myoneliner  
          'SELECT '
        || string_agg(quote_ident(column_name::text), ',' ORDER BY column_name)
        || ' FROM ' || quote_ident(mytable)
   FROM   information_schema.columns
   WHERE  table_name = mytable
   AND    column_name LIKE myprefix||'%'
   AND    table_schema = 'public';  -- schema name; might be another param

   RAISE NOTICE 'My additional text: %', myoneliner;
   RETURN myoneliner;
END
$func$ LANGUAGE plpgsql;

致电:

select myresult('dkj_p_k27ac','enri');   

运行上述过程后,我得到一个文本字符串,这基本上是一个查询。为简单起见,我接下来将其称为 'oneliner-output'。
'oneline-output' 看起来如下(我只是 copy/paste 它来自我在这里进入的一个输出单元格):

"SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac"

我现在更好地理解了构建一个既可以创建 'oneliner-output' 又可以执行它的函数的问题想法。我能够 copy/paste 'oneliner-output' 进入一个新的 Postgres 查询 window 并将其作为正常查询执行就好了,在我的数据输出 window 中接收所需的列和行.
但是,我想自动执行此步骤,以避免 copy/paste 步骤。 Postgres 中有没有办法使用我从 myresult() 函数收到的 text 输出('oneliner-output')并执行它?是否可以创建第二个函数来接收 myresult() 的输出并将其用于执行查询?

按照这些思路,虽然我知道以下脚本(在下面)有效并且实际上输出了所需的列和行:

-- DEALLOCATE stmt1; -- use this line after the first time 'stmt1' was created
prepare stmt1 as SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac;
execute stmt1;

尝试使用 refcursor

CREATE OR REPLACE FUNCTION show_mytable(ref refcursor) RETURNS refcursor AS $$
BEGIN
   OPEN ref FOR SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac;   -- Open a cursor 
   RETURN ref;    -- Return the cursor to the caller
END;
$$ LANGUAGE plpgsql;

致电:

BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy"; 

这个过程实际上有效并吐出所需的列和行,但我必须再次提供准确的 SELECT 声明。

我基本上希望能够提供它作为我的 myresult() 函数的输出。像这样:

CREATE OR REPLACE FUNCTION show_mytable(ref refcursor) RETURNS refcursor AS $$
BEGIN
   OPEN ref FOR myresult();   -- Open a cursor 
   RETURN ref;    -- Return the cursor to the caller
END;
$$ LANGUAGE plpgsql;

致电:

BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy"; 

PREPARE 的技巧不起作用,因为它不像 CREATE FUNCTION 那样采用*文本字符串*(值),而是有效的 语句(代码)。

要将 data 转换为 executable code 您需要使用动态 SQL,即 EXECUTE 在 plpgsql 函数或 DO 语句中。只要 return 类型不依赖于第一个函数 myresult() 的结果,这就没有问题。否则你又回到了我之前的回答中概述的 22:

关键部分是以某种方式声明return类型(在本例中为行类型)。您可以为此目的创建 TABLETEMP TABLETYPE。或者您可以使用准备好的语句或 refcursor。

准备语句的解决方案

你们一直很亲密。缺少的部分是使用 dynamic SQL.

准备生成的查询

动态准备语句的函数

创建此函数一次。它是您函数的优化和安全版本 myresult():

CREATE OR REPLACE FUNCTION f_prep_query (_tbl regclass, _prefix text)
  RETURNS void AS 
$func$
BEGIN
   IF EXISTS (SELECT 1 FROM pg_prepared_statements WHERE name = 'stmt_dyn') THEN
      DEALLOCATE stmt_dyn;
   END IF;                 -- you my or may not need this safety check 

   EXECUTE (
     SELECT 'PREPARE stmt_dyn AS SELECT '
         || string_agg(quote_ident(attname), ',' ORDER BY attname)
         || ' FROM ' || _tbl
      FROM   pg_catalog.pg_attribute
      WHERE  attrelid = _tbl
      AND    attname LIKE _prefix || '%'
      AND    attnum > 0
      AND    NOT attisdropped
     );
END
$func$  LANGUAGE plpgsql;

我使用 regclass 作为 table 名称参数 _tbl 以使其明确且安全地对抗 SQLi。详情:

  • Table name as a PostgreSQL function parameter

信息模式不包括系统目录的oid列,所以我切换到pg_catalog.pg_attribute而不是information_schema.columns。这也更快。这有利有弊:

  • How to check if a table exists in a given schema

如果名称为 stmt_dyn 的预处理语句已经存在,PREPARE 将引发异常。如果是acceptable,去掉系统视图上的勾选pg_prepared_statements和后面的DEALLOCATE.
可以使用更复杂的算法来管理每个会话的多个准备语句,或者将准备语句的名称作为附加参数,甚至使用查询字符串的 MD5 哈希作为名称,但这超出了本文的范围问题.

请注意,PREPARE 事务 的范围之外运行,一旦 PREPARE 成功,准备好的语句将在会话的生命周期内存在。如果包装事务中止,PREPARE 不受影响。 ROLLBACK 不能 删除准备好的语句。

动态查询执行

两次次查询,但只有一次次调用服务器。而且效率也很高。

SELECT f_prep_query('tbl'::regclass, 'pre'::text);
EXECUTE stmt_dyn;

对于大多数简单用例来说,比创建临时 table 或游标并从中选择/获取(这将是其他选项)更简单、更高效。

SQL Fiddle.

我想我也找到了解决方案,使用 refcursor
如果你能通过它,检查并告诉我你是否认为它是 'Kosher',我将非常高兴。坦率地说,我不太确定我在这里想到了什么,因为我对语法不太熟悉。但是我能够使用我在网上找到的不同示例来综合这一点。它似乎对我有用。如果您能为我和其他用户阐明这个解决方案,我将非常高兴 - 并告诉您您对此有何看法。

首先让我们创建构造动态 SELECT 语句的函数:

CREATE OR REPLACE FUNCTION myresult2()
  RETURNS text AS 
$func$
DECLARE
   myoneliner text;
   mytable    text := 'dkj_p_k27ac';
   myprefix   text := 'enri';
BEGIN
   SELECT INTO myoneliner  
          'SELECT '
        || string_agg(quote_ident(column_name::text), ',' ORDER BY column_name)
        || ' FROM ' || quote_ident(mytable)
   FROM   information_schema.columns
   WHERE  table_name = mytable
   AND    column_name LIKE myprefix||'%'
   AND    table_schema = 'public';  -- schema name; might be another param

   -- RAISE NOTICE 'My additional text: %', myoneliner; -- for debugging
   RETURN myoneliner;
END
$func$ LANGUAGE plpgsql;

现在,让我们创建第二个函数,它可以执行第一个函数的字符串 TEXT 输出 myresult2():

CREATE OR REPLACE FUNCTION show_mytable(ref refcursor)
  RETURNS refcursor AS
$func$
DECLARE
   mydynamicstatment text := myresult2();
BEGIN       
   OPEN ref FOR EXECUTE mydynamicstatment;
   RETURN ref;  -- return cursor to the caller
END;
$func$ LANGUAGE plpgsql;

通话:

BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy";