Postgresql functions - sql

These are the type definitions provided to me
create type IR as (pattern_number integer, uoc_number integer);
My current progress is:
create or replace function q1(pattern text, uoc_threshold integer)
returns setof IR
as $$
BEGIN
RETURN QUERY
select count(code) from temp where code like $1;
RETURN QUERY
select count(code) from temp where code like $1 and uoc > $2;
END;
$$ language plpgsql;
My output needs to be like this :
Query:-
select *
from q1('ECO%', 6);
pattern_number | uoc_number
80 | 5
I get an error saying:
ERROR: structure of query does not match function result type
DETAIL: Returned type bigint does not match expected type integer in column 1.
CONTEXT: PL/pgSQL function q1(text,integer) line 3 at RETURN QUERY
How do I fix this?

Is it what you want?..
create or replace function q1(pattern text, uoc_threshold integer)
returns setof IR
as $$
BEGIN
RETURN QUERY
select (select count(code) from temp where code like $1)::integer
,(
select count(code) from temp where code like $1 and uoc > $2)::integer;
END;
$$ language plpgsql;

Related

Postgres stored function return result of SELECT DISTINCT

I want to have an array of distinct integer values across my postgres table as the return value of a stored function.
The stored function currently looks like this
create or replace function get_unique_entries(id int)
returns table ("entry_id" int)
language plpgsql
as
$$
begin
return query
select distinct table.entry_id
from my_table
where x = id;
end;
$$;
When executing
select get_unique_entries(2);, I get the following error message:
structure of query does not match function result type
I tried different return types, but nothing worked for me.
Thanks in advance!
Hmm, can you give us a more complete picture of your scenario? I tried using your code and it seems to work (except I needed to replace table with my_table):
postgres=# create table my_table(x int, entry_id int, name text);
CREATE TABLE
postgres=# insert into my_table values(generate_series(1,100),generate_series(1,10),'foo');
INSERT 0 100
postgres=# create or replace function get_unique_entries(id int)
postgres-# returns table ("entry_id" int)
postgres-# language plpgsql
postgres-# as
postgres-# $$
postgres$# begin
postgres$# return query
postgres$# select distinct table.entry_id
postgres$# from my_table
postgres$# where x = id;
postgres$# end;
postgres$# $$;
ERROR: syntax error at or near "table"
LINE 8: select distinct table.entry_id
^
postgres=# create or replace function get_unique_entries(id int)
returns table ("entry_id" int)
language plpgsql
as
$$
begin
return query
select distinct my_table.entry_id
from my_table
where x = id;
end;
$$;
CREATE FUNCTION
postgres=# select get_unique_entries(2);
get_unique_entries
--------------------
2
(1 row)
postgres=#
While preparing the complete example I actually found it out myself.
As I am working with supabase, they display the datatype BIGINT as int8. I was trying to set this as return type. Setting the return type to BIGINT instead worked.
So in general check I would recommend myself and to others to check your column data types exactly.
The working example looks like this (as indicated by #richyen)
create or replace function get_unique_categories_for_platform(platformId int)
returns table ("category_fk" bigint)
language plpgsql
as
$$
begin
return query select distinct course.category_fk
from course
where platform_fk = platformId;
end;
$$;

PostgreSQL call function returning setof record with table and additional columns

I am trying to call a function returning all columns of an existing table + some unrelated additional ones and retrieve the returned data using the following syntax:
select *
from test_func(...)
as (a_table my_table_name, rows_count numeric);
The function has the following format:
CREATE OR REPLACE FUNCTION public.test_func(...)
RETURNS SETOF record
LANGUAGE plpgsql
AS $function$
DECLARE
_sql VARCHAR;
begin
_sql := 'SELECT mtn.*, count(*) over() as rows_count
from public.my_table_name mtn
... inner joins and other stuff';
return query
execute _sql using ..._sql params...;
END;
$function$
;
However, it doesn't work. The error I receive:
ERROR: structure of query does not match function result type
Detail: Returned type numeric does not match expected type "my_table_name" in column 1.
If the query in your function should return a my_table_name (a “whole-row reference), you should write it like
SELECT mtn, count(*) OVER () ...

Multiple ALTER TABLE ADD COLUMN in one SQL function call

I came across some weird behaviour I'd like to understand.
I create a plpgsql function doing nothing except of ALTER TABLE ADD COLUMN. I call it 2 times on the same table:
A) In a single SELECT sentence
B) In a SQL function with same SELECT as in A)
Results are different: A) creates two columns, while B) creates only one column. Why?
Code:
CREATE FUNCTION add_text_column(table_name text, column_name text) RETURNS VOID
LANGUAGE plpgsql
AS $fff$
BEGIN
EXECUTE '
ALTER TABLE ' || table_name || '
ADD COLUMN ' || column_name || ' text;
';
END;
$fff$
;
-- this function is called only in B
CREATE FUNCTION add_many_text_columns(table_name text) RETURNS VOID
LANGUAGE SQL
AS $fff$
WITH
col_names (col_name) AS (
VALUES
( 'col_1' ),
( 'col_2' )
)
SELECT add_text_column(table_name, col_name)
FROM col_names
;
$fff$
;
-- A)
CREATE TABLE a (id integer);
WITH
col_names (col_name) AS (
VALUES
( 'col_1' ),
( 'col_2' )
)
SELECT add_text_column('a', col_name)
FROM col_names
;
SELECT * FROM a;
-- B)
CREATE TABLE b (id integer);
SELECT add_many_text_columns('b');
SELECT * FROM b;
Result:
CREATE FUNCTION
CREATE FUNCTION
CREATE TABLE
add_text_column
-----------------
(2 rows)
id | col_1 | col_2
----+-------+-------
(0 rows)
CREATE TABLE
add_many_text_columns
-----------------------
(1 row)
id | col_1
----+-------
(0 rows)
I'm using PostgreSQL 10.4. Please note that this is only a minimal working example, not the full functionality I need.
CREATE OR REPLACE FUNCTION g(i INTEGER)
RETURNS VOID AS $$
BEGIN
RAISE NOTICE 'g called with %', i;
END
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION t(i INTEGER)
RETURNS VOID AS $$
SELECT g(id)
FROM generate_series(1, i) id;
$$ LANGUAGE SQL;
What do you think happens when I run SELECT t(4)? The only statement printed from g() is g called with 1.
The reason for this is your add_many_text_columns function returns a single result (void). Because it's SQL and is simply returning the result of a SELECT statement, it seems to stop executing after getting the first result, which makes sense if you think of it - it can only return one result after all.
Now change the function to:
CREATE OR REPLACE FUNCTION t(i INTEGER)
RETURNS SETOF VOID AS $$
SELECT g(id)
FROM generate_series(1, i) id;
$$ LANGUAGE SQL;
And run SELECT t(4) again, and now this is printed:
g called with 1
g called with 2
g called with 3
g called with 4
Because the function now returns SETOF VOID, it doesn't stop after the first result and executes it fully.
So back to your functions, you could change your SQL function to return SETOF VOID, but it doesn't really make much sense - better I think to change it to plpgsql and have it do a PERFORM:
CREATE OR REPLACE FUNCTION t(i INTEGER)
RETURNS VOID AS $$
BEGIN
PERFORM g(id)
FROM generate_series(1, i) id;
END
$$ LANGUAGE plpgsql;
That will execute the statement fully and it still returns a single VOID.
eurotrash provided a good explanation.
Alternative solution 1
CREATE OR REPLACE FUNCTION t(i INTEGER)
RETURNS VOID AS
$func$
SELECT g(id)
FROM generate_series(1, i) id;
SELECT null::void;
$func$ LANGUAGE sql;
Because, quoting the manual:
SQL functions execute an arbitrary list of SQL statements, returning
the result of the last query in the list. In the simple (non-set)
case, the first row of the last query's result will be returned.
By adding a dummy SELECT at the end we avoid that Postgres stops after processing the the first row of the query with multiple rows.
Alternative solution 2
CREATE OR REPLACE FUNCTION t(i INTEGER)
RETURNS VOID AS
$func$
SELECT count(g(id))
FROM generate_series(1, i) id;
$func$ LANGUAGE sql;
By using an aggregate function, all underlying rows are processed in any case. The function returns bigint (that's what count() returns), so we get the number of rows as result.
Alternative solution 3
If you need to return void for some unknown reason, you can cast:
CREATE OR REPLACE FUNCTION t(i INTEGER)
RETURNS VOID AS
$func$
SELECT count(g(id))::text::void
FROM generate_series(1, i) id;
$func$ LANGUAGE sql;
The cast to text is a stepping stone because the cast from bigint to void is not defined.

Getting an array return on function

I would like to have this function return as an array which contains all ID (integer) from this query, but i am stuck here:
CREATE OR REPLACE FUNCTION public.all_id(
prm_id integer)
RETURNS SETOF integer
LANGUAGE 'plpgsql'
COST 100.0
AS $function$
DECLARE
all_id integer;
-- all_id integer[]; gives an error, while integer only returns last value
BEGIN
SELECT id
COLLECT INTO all_id
FROM subject_data
WHERE sab_subject = (
SELECT sab_subject
FROM subject_data
WHERE id = prm_id
);
RETURN NEXT all_id;
END;
$function$;
SELECT * FROM public.all_id(1);
here's an example of fn:
t=# create or replace function ar() returns int[] as $$
declare ia int[];
begin
select array_agg(oid::int) into ia from pg_database;
return ia;
end;
$$ language plpgsql;
CREATE FUNCTION
t=# select * from ar();
ar
-----------------------------------------------------------
{13505,16384,1,13504,16419,16816,17135,25542,25679,25723}
(1 row)
SETOF would return "table", language is not literal and thus does not require single quotes, and so on...
as a_horse_with_no_name correctly brings out, you don't need plpgsql for this. A simple query will work:
SELECT array_agg(id)
FROM subject_data
WHERE sab_subject = (
SELECT sab_subject
FROM subject_data
WHERE id = PRM_ID_VALUE
)

Function with SQL query has no destination for result data

I am trying to create a function that returns a SELECTed resultset.
When I call my postgres function like this select * from tst_dates_func() I get an error as shown below:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function "tst_dates_func" line 3 at SQL statement
********** Error **********
ERROR: query has no destination for result data
SQL state: 42601
Hint: If you want to discard the results of a SELECT, use PERFORM instead.
Context: PL/pgSQL function "tst_dates_func" line 3 at SQL statement
Here is the function I created:
CREATE OR REPLACE FUNCTION tst_dates_func()
RETURNS TABLE( date_value date, date_id int, date_desc varchar) as
$BODY$
BEGIN
select a.date_value, a.date_id, a.date_desc from dates_tbl a;
END;
$BODY$
LANGUAGE plpgsql;
I am not sure why I am getting the above error. I would like to run select * from tst_dates_func();
and get data back. Or further join the result set if needed. What is the problem here?
Do it as plain SQL
CREATE OR REPLACE FUNCTION tst_dates_func()
RETURNS TABLE( date_value date, date_id int, date_desc varchar) as
$BODY$
select a.date_value, a.date_id, a.date_desc from dates_tbl a;
$BODY$
LANGUAGE sql;
If you really need plpgsql use return query
CREATE OR REPLACE FUNCTION tst_dates_func()
RETURNS TABLE( date_value date, date_id int, date_desc varchar) as
$BODY$
BEGIN
perform SELECT dblink_connect('remote_db');
return query
select a.date_value, a.date_id, a.date_desc from dates_tbl a;
END;
$BODY$
LANGUAGE plpgsql;
In PLPGSQL - use RETURN QUERY
CREATE OR REPLACE FUNCTION tst_dates_func()
RETURNS TABLE( date_value date, date_id int, date_desc varchar) as
$BODY$
BEGIN
RETURN QUERY (select a.date_value, a.date_id, a.date_desc from dates_tbl a);
END;
$BODY$
LANGUAGE plpgsql;
I couldn't do it as plain SQL as I needed to enter some data into a database for further processing and wanted to create a variable. Or at least I did not figure out the correct syntax for that. And the accepted answer had code I did not need, such as connecting to the database, as I ran this from inside pgAdmin with a connection setup already. I also had to drop the function when I made edits to it.
I was using this for inserting a geometry for intersection. A different use case and example could help someone else. This also shows how to then view this data and use it just like a table.
-- Get a geojson shape inside of postgres for further use
DROP FUNCTION fun();
CREATE OR REPLACE FUNCTION fun()
RETURNS TABLE (geometry geometry) AS
$BODY$
DECLARE geojson TEXT;
BEGIN
geojson := '{
"type":"Polygon",
"coordinates":[[[-90.9516399548092,39.8942337977775],[-90.9513913202472,39.8936939306154],[-90.9522805177147,39.8937108246505],[-90.9549542293894,39.8937616571416],[-90.954948768846,39.8945506794343],[-90.9531755591848,39.894492766522],[-90.9531770788457,39.8942868819087],[-90.9516399548092,39.8942337977775]]],
"crs":{"type":"name","properties":{"name":"EPSG:4326"}},
}';
return query (SELECT ST_GeomFromGeoJSON(geojson) AS geometry);
END;
$BODY$
LANGUAGE plpgsql;
-- View test insert
SELECT * FROM fun()