If the results are not meant to be used in a subquery but by code, you may use a REFCURSOR
in a transaction.
Example:
CREATE FUNCTION example_cursor() RETURNS refcursor AS $$
DECLARE
c refcursor;
BEGIN
c:='mycursorname';
OPEN c FOR select * from generate_series(1,100000);
return c;
end;
$$ language plpgsql;
Usage for the caller:
BEGIN;
SELECT example_cursor();
[output: mycursor]
FETCH 10 FROM mycursor;
Output:
generate_series
-----------------
1
2
3
4
5
6
7
8
9
10
CLOSE mycursor;
END;
When not interested in piecemeal retrieval, FETCH ALL FROM cursorname
may also be used to stream all results to the caller in one step.
I derive from your example that you only want to match tables with the same structure (compatible row types).
Base query
First of all, your base query is needlessly complex and possibly incorrect. Consider instead:
(TABLE a EXCEPT ALL TABLE b) -- ALL keeps duplicate rows and is faster
UNION ALL
(TABLE b EXCEPT ALL TABLE a);
I doubt you want to fold duplicates. Typically, tables do not hold complete duplicates to begin with, and omitting the ALL
key word would instruct Postgres to try and fold duplicates in every step, which is just a waste of time. Details:
Even if completely duplicate rows are possible, it might be best to return all of them to avoid misleading answers. If you want to fold duplicates in the result anyway, a single UNION
does the job:
(TABLE a EXCEPT ALL TABLE b)
UNION
(TABLE b EXCEPT ALL TABLE a);
However, a query with NATURAL FULL OUTER JOIN
is more efficient, doing almost the same:
SELECT *
FROM a NATURAL FULL OUTER JOIN b
WHERE a IS NULL OR b IS NULL;
There are two subtle, exotic corner cases:
- This returns rows that are all NULL to begin with. It would be an odd table design that allows such rows, though.
- This does not return duplicate rows that are not matched with the same number of duplicate rows in the other table: a single match is enough to eliminate all - which is subtly different from folding duplicates in the result! Have a look at the fiddle below. Again, it would be an odd table design that allows completely duplicate rows.
Function
What you have so far does not work for several reasons.
To return actual table rows dynamically (and not just a count or a text representation), you need to use a polymorphic type.
Since the second table is bound to have a compatible row type (as per my assumption) it's enough to hand in just the table name for that.
CREATE OR REPLACE FUNCTION f_tbl_diff(_tbl1_type ANYELEMENT, _tbl2 text)
RETURNS SETOF ANYELEMENT AS
$func$
BEGIN
RETURN QUERY EXECUTE format('
SELECT *
FROM %1$s NATURAL FULL OUTER JOIN %2$I
WHERE %1$s IS NULL OR %2$I IS NULL'
, pg_typeof(_tbl1_type), _tbl2);
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM f_tbl_diff(NULL::a, 'b');
Note the special syntax for the first argument! We hand in an actual row defining the return type, not just a table name. Related answer on SO with ample details (scroll to the last chapter):
This returns all rows that have no exact, complete match in the other table
SQL Fiddle also demonstrating exotic corner cases.
About the dynamic SQL:
Best Answer
There are two questions here,
EXECUTE .. USING
andEXECUTE FORMAT()
The difference between
EXECUTE .. USING
andEXECUTE FORMAT()
From the docs,
So you have a few arguments here.
EXECUTE FORMAT() ... USING
USING
allows the plan to be cached.USING
allows symbols to stay symbols and stops them from having to be converted to text and re-escaped.USING
can not be used with identifiers, only literals.Wrapping and generating simple SQL statements in a procedural function is a bad idea.
As for the other part of the question,
There are a lot of reasons for that,
TABLE (id int)
And, it's not SQL. You're building a new language on top of a DBMS. Why?
As for the dynamic component, there are other ways to engineer around the problem. Take for instance the exact statement provided, the worst case scenario is where ever you see that
You have to explicitly write out the order-by. As bad as it, it's a better solution in my opinion.
A step even further would be to use a library which provides some kind of assistance for generating , like
pg-promise
Or, DBIx::Abstract, or an ORM like DBIx::Class.