Returning table with specific parameters from a PostgreSQL function - sql

I have this function, with which I would like to return a table, which holds two columns: game_name and follow (this would be an integer 0 or 1):
CREATE OR REPLACE FUNCTION public.toggle2(uid numeric, gid NUMERIC)
RETURNS TABLE (
follow INT,
game_name TEXT
)
LANGUAGE plpgsql
AS $$
BEGIN
IF NOT EXISTS(SELECT *
FROM game_follows
WHERE user_id = uid and game_id = gid)
THEN
INSERT INTO game_follows(user_id, game_id) VALUES(uid, gid);
follow := 1;
ELSE
DELETE FROM game_follows WHERE user_id = uid and game_id = gid;
follow := 0;
END IF;
SELECT name INTO game_name FROM games WHERE id = gid;
END;
$$
;
Sadly, the function returns empty values. I am using it as this:
SELECT * FROM toggle2(83, 12);

A function declared to RETURN TABLE can return 0-n rows.
You must actively return rows, or nothing will be returned (no row). One way to do this:
RETURN NEXT; -- as last line before END;
There are other ways, see the manual.
However, it seems you want to return exactly one row every time. So rather use OUT parameters:
CREATE OR REPLACE FUNCTION public toggle2(uid numeric, gid numeric, OUT follow int, OUT game_name text) AS ...
Then it's enough to assign those OUT parameters, they are returned in the single result row automatically.
See:
Returning from a function with OUT parameter
plpgsql error "RETURN NEXT cannot have a parameter in function with OUT parameters" in table-returning function
How to return result of a SELECT inside a function in PostgreSQL?

Related

Why is RETURN QUERY returning a string instead of a TABLE

This MWE is NOT how you would typically solve this problem, however, it is as simple as I can explain the problem I am encountering. I am merely trying to point out 2 things
I am doing more than simply returning the contents of a Table
What is being returned is NOT being returned as a Table but a String
Supporting SQL Statements:
DROP DATABASE IF EXISTS test;
CREATE DATABASE test;
\c test
CREATE TABLE credit_card(
id BIGSERIAL PRIMARY KEY,
balance BIGINT
);
Functions:
CREATE FUNCTION get_credit_card(
p_id BIGINT
)
RETURNS TABLE(
id BIGINT,
balance BIGINT
)
AS $$
DECLARE
BEGIN
RETURN QUERY
SELECT
credit_card.id,
credit_card.balance
FROM
credit_card
WHERE
credit_card.id = p_id;
END $$ LAnguage 'plpgsql';
CREATE FUNCTION pay_with_card(
p_id BIGINT,
p_amount BIGINT
)
RETURNS TABLE(
id BIGINT,
balance BIGINT
)
AS $$
DECLARE
v_balance BIGINT;
BEGIN
SELECT
credit_card.balance
FROM
credit_card
INTO
v_balance
WHERE
credit_card.id = p_id;
IF v_balance < p_amount
THEN
RETURN;
END IF;
UPDATE
credit_card
SET
balance = credit_card.balance - p_amount;
RETURN QUERY
SELECT get_credit_card (p_id);
END $$ LAnguage 'plpgsql';
Populate Table and Call function:
INSERT INTO credit_card
(balance)
VALUES
(100);
SELECT
pay_with_card (1, 100);
Error:
DROP DATABASE
CREATE DATABASE
You are now connected to database "test" as user "postgres".
CREATE TABLE
CREATE FUNCTION
CREATE FUNCTION
INSERT 0 1
psql:test.sql:74: ERROR: structure of query does not match function result type
DETAIL: Returned type record does not match expected type bigint in column 1.
CONTEXT: PL/pgSQL function pay_with_card(bigint,bigint) line 24 at RETURN QUERY
It took me a long time to figure out that pay_with_card is returning a String, or what appears to be a String, instead of a TABLE(id BIGINT, balance BIGINT). With the Python psycopg2 library, the returned query is
[('(1,100)'),]
So my entire code is breaking because I can't get the values (unless I hack it and use string manipulation.
Question:
How can I fix it so that it returns the correct query like so
[(1,100),]
An alternative to the hint in horse_with_no_name's comment, you can replace
RETURN QUERY
SELECT get_credit_card (p_id);
with
RETURN QUERY SELECT (get_credit_card(p_id)).*;
You need some way of expanding the returned record back into its constituent fields. (I think horse’s SELECT * … has the same effect.)

Reference a parameter in Postgres function

The table org has a column called npi. Why does query1 work and query2 not?
Query 1 -
CREATE OR REPLACE FUNCTION check (npi TEXT)
RETURNS BOOLEAN AS $$
DECLARE
pass_npi TEXT;
BEGIN
pass_npi := npi;
SELECT 1
FROM org doc
WHERE doc.npi = pass_npi
;
RETURN 1;
END $$
Query 2 -
CREATE OR REPLACE FUNCTION check (npi TEXT)
RETURNS BOOLEAN AS $$
BEGIN
SELECT 1
FROM org doc
WHERE doc.npi = npi
;
RETURN 1;
END $$
ERROR -
Ambigious column name NPI
Because in the second case it is unclear if npi is the table column (that would be a valid, if useless statement) or the function parameter.
There are three solutions apart from the one in your first query:
The best one: use function parameters that have names different from table columns. This can be done by using a prefix:
CREATE FUNCTION check (p_npi TEXT) RETURNS boolean AS
...
SELECT ...
WHERE doc.npi = p_npi
Use the ALIAS command to “rename” the parameter:
CREATE FUNCTION check (npi TEXT) RETURNS boolean AS
$$DECLARE
p_npi ALIAS FOR npi;
BEGIN
...
SELECT ...
WHERE doc.npi = p_npi
Qualify the parameter with the function name:
CREATE FUNCTION check (npi TEXT) RETURNS boolean AS
...
SELECT ...
WHERE doc.npi = check.npi
What happens is that in Query2 you are comparing the field the doc.npi field with it, it is the same to say doc.npi and to say npi, for that reason it shows you that the sentence is ambiguous, on the contrary case in Query1 you are comparing the doc.npi field with a different field that is the pass_npi.
To solve this problem you must compare the same columns but from different tables or different columns from the same table.
Query2:
CREATE OR REPLACE FUNCTION check (npi TEXT)
RETURNS BOOLEAN AS $$
BEGIN
SELECT 1
FROM org doc
WHERE doc.npi = pass_npi
;
RETURN 1;
END $$

How return dynamic number of columns in function?

In PostgreSQL 11 database I have table with 6 column. Next function return static number of defined columns.
CREATE FUNCTION CALCULATION(INTEGER)
RETURNS TABLE(
ORGANIZATION_ID INT4,
ORGANIZATION_NAME VARCHAR,
ORGANIZATION_RANG INT4,
PARENT_ORGANIZATION_ID INT4,
PARENT_ORGANIZATION_NAME VARCHAR,
PARENT_ORGANIZATION_RANG INT4
) AS $$
SELECT * FROM ANALYTICS;
$$ LANGUAGE SQL;
How can I make an SQL function in Postgres 11 which return a result set with dynamic number of columns according to a parameter passed in?
For example if I call SELECT * FROM CALCULATION(2);, function return first 2 columns.
If this is not possible with an SQL function, is it possible with a PL/pgSQL function?
This is possible for RECORD returning functions.
CREATE FUNCTION calculation(how_many integer) RETURNS SETOF RECORD
LANGUAGE plpgsql
AS $fff$
BEGIN
IF how_many = 1
THEN RETURN QUERY SELECT 'foo'::text;
ELSIF how_many = 2
THEN RETURN QUERY SELECT 'foo'::text, 'bar'::text;
END IF;
END;
$fff$
;
And now you can do:
jbet=> SELECT * FROM calculation(1) AS f(first_col text);
first_col
-----------
foo
(1 row)
jbet=> SELECT * FROM calculation(2) AS f(first_col text, second_col text);
first_col | second_col
-----------+------------
foo | bar
(1 row)
The very serious downside is that each time you call the function you have to define set of returned columns, so I don't think you'll find this answer useful : )
Anyway, Postgresql needs to know returned type of each SELECT before it runs the query, so one or other way you have to define the columns.
JSON return value could be a reasonable answer if you just want the data and don't care if there are separate columns or not.
Backing up a step, why not use a standard select to get the columns you want from your set-returning function?
select organization_name,
organization_rang,
parent_organization_name,
parent_organization_rang
from calculation();
That's easy to follow and flexible. I'm guessing that you've written a simplified example and have a good reason for what you're asking...but I figured I'd double-check.

SQL Function: "Query Has No Destination for Result Data"

I'm working on what shouldn't be too difficult an SQL function: it takes a few parameters to find a specific course in a table, counts how many people are in that course, compares it to the course's maximum capacity, and returns 1 or 0 as appropriate:
drop function if exists room_for_more_students(the_class_name varchar, the_semester_code int);
create function room_for_more_students(the_class_name varchar, the_semester_code int)
returns int as $BODY$
begin
select * from class_offerings as match_table
where class_name = the_class_name and semester_code = the_semester_code;
select count(student_id) from match_table as num_students_in_class;
select avg(maximum_capacity) from match_table as num_students_allowed_in_class;
--These will all be the same so "avg" just means "the maximum capacity for the class"
if num_students_in_class < num_students_allowed_in_class then return 1;
else return 0;
end if;
end
$BODY$
language plpgsql;
This doesn't really seem like it should be all that complex to implement, and the function creates without issue, but every time I try and invoke it through psycopg2 I receive:
ProgrammingError: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead
I have tried experimenting with PERFORM instead, but any combination I try seems to either keep the same issue or create a host of new ones. I've also done some research on this as there are a few other posts about the same issue, but the majority of the time the answer seems to be that the user hasn't added specific return statements, which I have. I'm completely out of ideas and would appreciate any input possible.
For your case, you must declare some variable and assign it with the result of query. You can not run a query without assign its result to nowhere.
I update your function as below:
drop function if exists room_for_more_students(the_class_name varchar, the_semester_code int);
create function room_for_more_students(the_class_name varchar, the_semester_code int)
returns int as
$BODY$
DECLARE
num_students_allowed_in_class numeric;
num_students_in_class numeric;
begin
WITH match_table AS (
select *
from class_offerings
where class_name = the_class_name and semester_code = the_semester_code
)
select count(student_id), avg(maximum_capacity)
INTO num_students_in_class, num_students_allowed_in_class
from match_table;
if num_students_in_class
Hopefully it match your request!

Passing a ResultSet into a Postgresql Function

Is it possible to pass the results of a postgres query as an input into another function?
As a very contrived example, say I have one query like
SELECT id, name
FROM users
LIMIT 50
and I want to create a function my_function that takes the resultset of the first query and returns the minimum id. Is this possible in pl/pgsql?
SELECT my_function(SELECT id, name FROM Users LIMIT 50); --returns 50
You could use a cursor, but that very impractical for computing a minimum.
I would use a temporary table for that purpose, and pass the table name for use in dynamic SQL:
CREATE OR REPLACE FUNCTION f_min_id(_tbl regclass, OUT min_id int) AS
$func$
BEGIN
EXECUTE 'SELECT min(id) FROM ' || _tbl
INTO min_id;
END
$func$ LANGUAGE plpgsql;
Call:
CREATE TEMP TABLE foo ON COMMIT DROP AS
SELECT id, name
FROM users
LIMIT 50;
SELECT f_min_id('foo');
Major points
The first parameter is of type regclass to prevent SQL injection. More info in this related answer on dba.SE.
I made the temp table ON COMMIT DROP to limit its lifetime to the current transaction. May or may not be what you want.
You can extend this example to take more parameters. Search for code examples for dynamic SQL with EXECUTE.
-> SQLfiddle demo
I would take the problem on the other side, calling an aggregate function for each record of the result set. It's not as flexible but can gives you an hint to work on.
As an exemple to follow your sample problem:
CREATE OR REPLACE FUNCTION myMin ( int,int ) RETURNS int AS $$
SELECT CASE WHEN $1 < $2 THEN $1 ELSE $2 END;
$$ LANGUAGE SQL STRICT IMMUTABLE;
CREATE AGGREGATE my_function ( int ) (
SFUNC = myMin, STYPE = int, INITCOND = 2147483647 --maxint
);
SELECT my_function(id) from (SELECT * FROM Users LIMIT 50) x;
It is not possible to pass an array of generic type RECORD to a plpgsql function which is essentially what you are trying to do.
What you can do is pass in an array of a specific user defined TYPE or of a particular table row type. In the example below you could also swap out the argument data type for the table name users[] (though this would obviously mean getting all data in the users table row).
CREATE TYPE trivial {
"ID" integer,
"NAME" text
}
CREATE OR REPLACE FUNCTION trivial_func(data trivial[])
RETURNS integer AS
$BODY$
DECLARE
BEGIN
--Implementation here using data
return 1;
END$BODY$
LANGUAGE 'plpgsql' VOLATILE;
I think there's no way to pass recordset or table into function (but I'd be glad if i'm wrong). Best I could suggest is to pass array:
create or replace function my_function(data int[])
returns int
as
$$
select min(x) from unnest(data) as x
$$
language SQL;
sql fiddle demo