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
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";
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:
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.
You have been very close. The missing piece of the puzzle is to prepare the generated query with dynamic SQL.
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:
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:
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.
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.