Use text output from a function as new query

后端 未结 2 509
星月不相逢
星月不相逢 2020-12-06 14:57

In continuing from a previous case that was assisted by @Erwin Brandstetter and @Craig Ringer, I have fixed my code to become as follows. Note, that my function myresu

2条回答
  •  误落风尘
    2020-12-06 15:42

    The trick with PREPARE doesn't work, since it does not take a * text string* (a value) like CREATE FUNCTION does, but a valid statement (code).

    To convert data into executable code you need to use dynamic SQL, i.e. EXECUTE in a plpgsql function or DO statement. This works without problem as long as the return type does not depend on the outcome of the first function myresult(). Else you are back to catch 22 as outlined in my previous answer:

    • How to execute a string result of a stored procedure in postgres

    The crucial part is to declare the return type (row type in this case) somehow. You can create a TABLE, TEMP TABLE or TYPE for the purpose. Or you can use a prepared statement or a refcursor.

    Solution with prepared statement

    You have been very close. The missing piece of the puzzle is to prepare the generated query with dynamic SQL.

    Function to prepare statement dynamically

    Create this function once. It's a optimized and safe version of your function 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;
    

    I use regclass for the table name parameter _tbl to make it unambiguous and safe against SQLi. Details:

    • Table name as a PostgreSQL function parameter

    The information schema does not include the oid column of system catalogs, so I switched to pg_catalog.pg_attribute instead of information_schema.columns. That's faster, too. There are pros and cons for this:

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

    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.

提交回复
热议问题