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

时间:2015-01-07 17:07:43

标签: postgresql prepared-statement plpgsql dynamic-sql ref-cursor

在@Erwin Brandstetter和@Craig Ringer的协助下继续a previous case,我修改了我的代码,如下所示。请注意,我的函数myresult()现在输出text,而不是(事实上,正如在前一种情况中指出的那样,输出表格没有意义对象,因为我们需要提前定义它的所有列,这基本上违背了整个目的):

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'看起来如下(我只是从我进入这里的一个输出单元复制/粘贴它):

"SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac"
  • 请注意,声明两边的双引号是myresult()输出的一部分。我没有添加它们。

我现在明白了这个有问题的想法,即构建一个既可以创建' oneliner-output'并执行它。我能够复制/粘贴' oneliner-output'进入一个新的Postgres查询窗口并将其作为普通查询执行就好了,在我的数据输出窗口中接收所需的列和行。
但是,我希望自动执行此步骤,以避免复制/粘贴步骤。 Postgres中有没有办法使用text函数收到的myresult()输出(' 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;
  • 我在想,在做了正确的调整后,下面的脚本可能会起作用吗?不知道怎么回事。

    prepare stmt1 as THE_OUTPUT_OF_myresult();
    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"; 

2 个答案:

答案 0 :(得分:1)

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

要将数据转换为可执行的代码,您需要使用动态SQL,即plpgsql函数或EXECUTE语句中的DO。只要返回类型不依赖于第一个函数myresult()的结果,这就没有问题。另外,你回到了我之前的答案中所描述的22:

关键部分是以某种方式声明返回类型(在这种情况下为行类型)。您可以为此目的创建TABLETEMP TABLETYPE。或者您可以使用预准备语句或refcursor。

准备好的声明

的解决方案

你一直很亲密。这个难题的缺失部分是使用动态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作为表名参数_tbl,使其对SQLi具有明确性和安全性。详细说明:

信息架构不包括系统目录的oid列,因此我切换到pg_catalog.pg_attribute而不是information_schema.columns。那也更快。这有利有弊:

如果已存在名为stmt_dyn的预准备语句,PREPARE将引发异常。如果这是可接受的,请取消对系统视图pg_prepared_statements和以下DEALLOCATE的检查 可以使用更复杂的算法来管理每个会话的多个预准备语句,或者将预准备语句的名称作为附加参数,甚至使用查询字符串的MD5哈希作为名称,但这超出了本问题的范围。

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

动态查询执行

两个查询,但只有一个调用服务器。而且非常有效。

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

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

SQL Fiddle.

答案 1 :(得分:1)

我想我也找到了一个解决方案,使用refcursor 如果你可以通过它,我会很高兴,检查并告诉我你是否认为它是犹太洁食的'。坦率地说,我不太清楚我在这里提出了什么,因为我对语法并不熟悉。但是我能够使用我在网上找到的不同例子来合成这个。它似乎对我有用。如果你能为我和其他用户阐明这个解决方案,我会很高兴 - 并告诉你对它的看法。

首先让我们创建构造动态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;

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

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";