I'd like to write a PLPGSQL (targeting PostgreSQL 9.3) function which will return multiple result sets (i.e. when accessed through JDBC I'll call getMoreResults() to move to the next set of rows), but everything I've tried either gives me a syntax error or simply concatenates everything together into a single result set.
Here is a simple example that illustrates the issue:
CREATE TYPE my_type AS (a BIGINT, b TEXT, c DOUBLE PRECISION);
CREATE FUNCTION my_func(_arg1 TEXT, _arg2 TEXT)
RETURNS SETOF my_type
AS $$
BEGIN
RETURN QUERY SELECT a, b, c FROM table1 WHERE d = _arg1 AND e = _arg2;
RETURN QUERY SELECT a, b, c FROM table2 WHERE d = _arg1 AND e = _arg2;
END;
$$ LANGUAGE PLPGSQL;
When I run this the rows from both table1 and table2 get concatenated together into a single result set.
I've also tried something like
CREATE FUNCTION my_func(_arg1 TEXT, _arg2 TEXT)
RETURNS SETOF TABLE(a BIGINT, b TEXT, c DOUBLE PRECISION)
But this just results in a cryptic syntax error: ERROR: syntax error at end of input.
For completeness sake here is the Java code I'd like to use to process the results. I'm fairly certain the issue is on the db function side, but it's possible I'm misunderstanding how the JDBC API is supposed to work.
(Error handling and resource closing removed for readability)
PreparedStatement statement = connection.prepareStatement("SELECT * FROM my_func(?, ?);");
statement.setString(1, "foo");
statement.setString(2, "bar");
statement.execute();
ResultSet rs1 = statement.getResultSet();
while(rs1.next()) {
// Process first result set
}
statement.getMoreResults();
ResultSet rs2 = statement.getResultSet();
while(rs2.next()) {
// Process second result set
}
Based on the searching I've done so far it seems like this type of solution is supposed to be supported by JDBC and PostgreSQL, but I can't find any explicit examples of it in action.
The only way you can get multiple resultsets from PostgreSQL (at time of writing - current as of PostgreSQL 9.4) is by returning refcursors.
Define your function RETURNS SETOF refcursor then return a cursor for each resultset. PgJDBC will recognise that it's a refcursor and fetch it for you; see the documentation.
Slightly modifying your question, but if you don't have to use a function, you can get multiple ResultSets from PostgreSQL exactly as you normally would with JDBC. Just concatenate your queries and process each result set using getMoreResults(). See Queries returning multiple result sets.
Related
Is there a difference in performance between the following queries?
1. foo() returns an unfiltered set which is then filtered:
create function foo() returns setof table1 as
$$
select * from table1
$$ language SQL;
select * from foo() where name = 'bar'
2. foo() accepts a parameter and returns a filtered set:
create function foo(varchar) returns setof table1 as
$$
select * from table1 where name = $1
$$ language SQL;
select * from foo('bar')
I assume the DB is smart enough to "inline" the function before planning the final query, so that it make no difference in execution. But i'm not sure.
the first runs function without any parameters (possibly getting more data) and then filter data on field. So probably will cost more.
the second runs function with parameter (possibly reducing data on function run)
without body of the function, it is pure speculation
I have found a wiki page that answers this.
There are multiple conditions that have to be met for the function to be inlined and thus get opmimized as part of the whole query.
A function in question will be inlined if it's declared Immutable or Stable:
create function foo() returns setof table1 as
$$
select * from table1
$$ language SQL stable;
Stable is more appropriate because the function does a table lookup.
Put several million (for example 5) rows into table, try to use at least 10-20 different values for column name (including 'bar'). Then add index create index iixx on table1(name)
Then run select count(*) from foo() where name = 'bar'
then try second version select count(*) from foo1('bar')
and you will see difference
I would like to use a plpgsql function with a table and several columns as input parameter. The idea is to split the table in chunks and do something with each part.
I tried the following function:
CREATE OR REPLACE FUNCTION my_func(Integer)
RETURNS SETOF my_part
AS $$
DECLARE
out my_part;
BEGIN
FOR i IN 0..$1 LOOP
FOR out IN
SELECT * FROM my_func2(SELECT * FROM table1 WHERE id = i)
LOOP
RETURN NEXT out;
END LOOP;
END LOOP;
RETURN;
END;
$$
LANGUAGE plpgsql;
my_func2() is the function that does some work on each smaller part.
CREATE or REPLACE FUNCTION my_func2(table1)
RETURNS SETOF my_part2 AS
$$
BEGIN
RETURN QUERY
SELECT * FROM table1;
END
$$
LANGUAGE plpgsql;
If I run:
SELECT * FROM my_func(99);
I guess I should receive the first 99 IDs processed for each id.
But it says there is an error for the following line:
SELECT * FROM my_func2(select * from table1 where id = i)
The error is:
The subquery is only allowed to return one column
Why does this happen? Is there an easy way to fix this?
There are multiple misconceptions here. Study the basics before you try advanced magic.
Postgres does not have "table variables". You can only pass 1 column or row at a time to a function. Use a temporary table or a refcursor (like commented by #Daniel) to pass a whole table. The syntax is invalid in multiple places, so it's unclear whether that's what you are actually trying.
Even if it is: it would probably be better to process one row at a time or rethink your approach and use a set-based operation (plain SQL) instead of passing cursors.
The data types my_part and my_part2 are undefined in your question. May be a shortcoming of the question or a problem in the test case.
You seem to expect that the table name table1 in the function body of my_func2() refers to the function parameter of the same (type!) name, but this is fundamentally wrong in at least two ways:
You can only pass values. A table name is an identifier, not a value. You would need to build a query string dynamically and execute it with EXECUTE in a plpgsql function. Try a search, many related answers her on SO. Then again, that may also not be what you wanted.
table1 in CREATE or REPLACE FUNCTION my_func2(table1) is a type name, not a parameter name. It means your function expects a value of the type table1. Obviously, you have a table of the same name, so it's supposed to be the associated row type.
The RETURN type of my_func2() must match what you actually return. Since you are returning SELECT * FROM table1, make that RETURNS SETOF table1.
It can just be a simple SQL function.
All of that put together:
CREATE or REPLACE FUNCTION my_func2(_row table1)
RETURNS SETOF table1 AS
'SELECT ($1).*' LANGUAGE sql;
Note the parentheses, which are essential for decomposing a row type. Per documentation:
The parentheses are required here to show that compositecol is a column name not a table name
But there is more ...
Don't use out as variable name, it's a keyword of the CREATE FUNCTION statement.
The syntax of your main query my_func() is more like psudo-code. Too much doesn't add up.
Proof of concept
Demo table:
CREATE TABLE table1(table1_id serial PRIMARY KEY, txt text);
INSERT INTO table1(txt) VALUES ('a'),('b'),('c'),('d'),('e'),('f'),('g');
Helper function:
CREATE or REPLACE FUNCTION my_func2(_row table1)
RETURNS SETOF table1 AS
'SELECT ($1).*' LANGUAGE sql;
Main function:
CREATE OR REPLACE FUNCTION my_func(int)
RETURNS SETOF table1 AS
$func$
DECLARE
rec table1;
BEGIN
FOR i IN 0..$1 LOOP
FOR rec IN
SELECT * FROM table1 WHERE table1_id = i
LOOP
RETURN QUERY
SELECT * FROM my_func2(rec);
END LOOP;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM my_func(99);
SQL Fiddle.
But it's really just a a proof of concept. Nothing useful, yet.
As the error log is telling you.. you can return only one column in a subquery, so you have to change it to
SELECT my_func2(SELECT Specific_column_you_need FROM hasval WHERE wid = i)
a possible solution can be that you pass to funct2 the primary key of the table your funct2 needs and then you can obtain the whole table by making the SELECT * inside the function
I have a SELECT from a function (with ordinary, not SETOF argument). However, I would like to call the function multiple times within the query (and then to aggregate the results), feeding it with a CTE.
Some (example) code:
CREATE TYPE MY_TYPE AS ( a integer, b integer );
CREATE OR REPLACE FUNCTION foo( foobar INTEGER )
RETURNS MY_TYPE AS $$
BEGIN
-- do something, return MY_TYPE
END;
$$ LANGUAGE plpgsql;
WITH x AS (
SELECT a FROM bar WHERE b = 1
)
SELECT min(a) FROM foo( (SELECT a FROM x) ) f
This doesn't work, I get following error message:
ERROR: more than one row returned by a subquery used as an expression
Now I wonder how to rewrite the query. I could loop over the CTE results, then call the function with one input value at a time, store results in a column, and finally find the minimum - this feels clumsy and should be quite slow. Another approach would be to wrap my function in another, set-returning and with array parameter. But this just pushes the problem away: I don't know how to simply wrap it.
What seems fine is to rewrite the function to the SETOF form. Still, this is a workaround, and learning how to call ordinary functions with more than one row returned by a query (if it is possible) would be better.
Try this:
SELECT min(f.a)
FROM bar
CROSS JOIN foo(bar.a) f
WHERE bar.b = 1
I'm using the squirrel sql client to work with Postgres and the problem is that the same query returns different results.
If a query below execute as is, it works fine:
select foo.column1 as Field1, 1 as Field2 from (values (3343),(45323)) as Foo
But if the query execute from stored function like this one:
CREATE OR REPLACE FUNCTION getSomeData(text) RETURNS setof tmp_stub_type AS
$body$
DECLARE
r tmp_stub_type%rowtype;
BEGIN
FOR r IN
select foo.column1 as Field1, 1 as Field2 from (values (3343),(45323)) as Foo
LOOP
RETURN NEXT r;
END LOOP;
RETURN;
END;
$body$
LANGUAGE 'plpgsql';
It returns the same rows count but with UnknownType values with one field instead two.
tmp_stub_type is normal table with two integer fields and nothing more.
I tried to solve it by the Postgres pgAdmin, but he showed me the same thing except the values - they was right but placed in one field and separated by comma.
I need run the query inside stored function, please help deal with it and sorry for my english.
i'm using: Postgres 9.3, Squirrel 3.5.3, OS Windows
To get multiple columns from a function that returns a row type or has multiple output parameters, use e.g. SELECT * FROM getSomeData(...) instead of SELECT getSomeData(...).
Source: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html#AEN58217
I would like to execute a SQL statement based on a string. I have created the SQL statement through a function which returns a string. Can anybody explain how I can execute the statement that is returned? I know that you can't do it in plain SQL, so I was thinking about putting it in a function. The only issue is that the columns in the statement aren't always the same, so I don't know which data types to use for the columns. I'm using Postgres 9.1.0.
For example, suppose the SQL string returned from my function the is:
Select open, closed, discarded from abc
But, it can also be:
Select open from abc
Or
Select open, closed from abc
How can I execute any of these strings, so that the results would be returned as a table with only the columns listed in the statement?
Edit: the function is written in PL/pgSQL. And the results will be used for reporting where they don't want to see columns that have no values. So the function that I wrote returns the names of all columns that have values and then add it to the SQL statement.
Thanks for your help!
I don't think you can return the rows directly from a function, because its return type would be unknown. Even if you specified the return type as RECORD, you'd have to list the returned columns at call time. Based on Wayne Conrad's idea, you could do this:
CREATE FUNCTION my_create(cmd TEXT) RETURNS VOID AS $$
BEGIN
EXECUTE 'CREATE TEMPORARY TABLE temp_result AS ' || cmd;
END;
$$ VOLATILE LANGUAGE plpgsql;
Then use the function like this:
BEGIN;
SELECT my_create(...);
SELECT * FROM temp_result;
ROLLBACK; -- or COMMIT