3

在继续由@Erwin Brandstetter 和@Craig Ringer 协助的先前案例中,我已将我的代码固定为如下。请注意,我的函数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"; 
4

2 回答 2

2

with 的技巧PREPARE不起作用,因为它不像 * text string* (一个值)那样CREATE FUNCTION,而是一个有效的语句(代码)。

要将数据转换为可执行代码,您需要使用动态 SQL,即EXECUTE在 plpgsql 函数或DO语句中。只要返回类型不依赖于第一个函数的结果,这将毫无问题myresult()。否则,您将回到我之前的回答中概述的第 22 条:

关键部分是以某种方式声明返回类型(在这种情况下为行类型)。您可以创建一个TABLE,TEMP TABLETYPE用于该目的。或者您可以使用准备好的语句或引用。

带有准备好的语句的解决方案

你已经很亲近了。缺少的部分是使用动态 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;

我使用regclasstable name 参数_tbl使其对SQLi明确且安全。细节:

信息架构不包括系统目录的 oid 列,所以我切换到pg_catalog.pg_attribute而不是information_schema.columns. 这也更快。这样做有好处也有坏处:

If a prepared statement with the name stmt_dyn already existed, PREPARE would raise an exception. If that is acceptable, remove the check on the system view pg_prepared_statements and the following DEALLOCATE.
More sophisticated algorithms are possible to manage multiple prepared statements per session, or take the name of the prepared statement as additional parameter, or even use an MD5 hash of the query string as name, but that's beyond the scope of this question.

Be aware that PREPARE operates outside the scope of transactions, once PREPARE succeeds, the prepared statement exists for the lifetime of the session. If the wrapping transaction is aborted, PREPARE is unaffected. ROLLBACK cannot remove prepared statements.

Dynamic query execution

Two queries, but only one call to the server. And very efficient, too.

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

Simpler and much more efficient for most simple use cases than creating a temp table or a cursor and selecting / fetching from that (which would be other options).

SQL Fiddle.

于 2015-01-07T20:15:06.417 回答
1

I think I found a solution too, using a refcursor.
I would be very glad if you could go through it, check and tell me if you think it is 'Kosher'. Frankly, I am not too sure what I've came up with here, as I am not that familiar with the syntax. But I was rather able to synthesize this using different examples I found on the web. It seems to work for me. I would be very glad if you could articulate this solution for me and for other users - and tell what do you think of it.

First lets create the function that constructs the dynamic SELECT statement:

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;

Now, lets create a second function that can execute the string TEXT output of the first function 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;

Call:

BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy";
于 2015-01-08T00:15:28.563 回答