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 myresult()
outputs now text
, and not a table (as indeed, as was pointed out in the former case, there is no point in outputting a table object, since we would need to define all its columns ahead, which basically defies the entire purpose):
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;
Call:
select myresult('dkj_p_k27ac','enri');
Upon running the above procedure I get a text string, which is basically a query. I'll refer to it up next as 'oneliner-output', just for simplicity.
The 'oneline-output' looks as follows (I just copy/paste it from the one output cell that I've got into here):
"SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac FROM dkj_p_k27ac"
- Note that the double quotes from both sides of the statement were part of the
myresult()
output. I didn't add them.
I understand much better now the problematic idea of thinking to construct a single function that would both create the 'oneliner-output' AND execute it. I am able to copy/paste the 'oneliner-output' into a new Postgres query window and execute it as a normal query just fine, receiving the desired columns and rows in my Data Output window.
I would like, however, to automate this step, so to avoid the copy/paste step. Is there a way in Postgres to use the text
output (the 'oneliner-output') that I receive from myresult()
function, and execute it? Can a second function be created that would receive the output of myresult()
and use it for executing a query?
Along these lines, while I know that the following scripting (here below) works and actually outputs exactly the desired columns and rows:
-- 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;
I was thinking maybe something like the following scripting could potentially work, after doing the correct tweaking? Not sure how though.
prepare stmt1 as THE_OUTPUT_OF_myresult(); execute stmt1;
Attempt with a 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;
Call:
BEGIN;
SELECT show_mytable('roy');
FETCH ALL IN "roy";
This procedure, actually works and spits out the desired columns and rows, and yet again, I have to provide the exact SELECT statement.
I basically would like to be able and provide it instead as the output of my myresult()
function. Something like this:
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;
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) likeCREATE 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 orDO
statement. This works without problem as long as the return type does not depend on the outcome of the first functionmyresult()
. 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
orTYPE
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()
: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 ofinformation_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 viewpg_prepared_statements
and the followingDEALLOCATE
.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, oncePREPARE
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.
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.
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:Now, lets create a second function that can execute the string TEXT output of the first function
myresult2()
:Call: