Postgres stored function return result of SELECT DISTINCT - sql

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;
$$;

Related

Functions(procedure)

I have many different queries (here is an example)
delete from dict.dct_task_type;
insert into dict.dct_task_type(id_task_type,nm_task_type)
SELECT * FROM dblink
('db',
$$ select id_dict, nm_dict from dict.d_dict where kd_dict_entity = 30 $$)
AS dct_task_type(id_task_type bigint,nm_task_type varchar);
delete from dict.dct_task_resolution;
insert into dict.dct_task_resolution (id_task_resolution,nm_task_resolution)
SELECT * FROM dblink
('db',
$$ select id_dict, nm_dict from dict.d_dict where kd_dict_entity = 32 $$)
AS dct_task_resolution(id_task_resolution bigint,nm_task_resolution varchar);
I want to do a job in time, for this I want to save the requests as procedures, how do I write them correctly?
DECLARE
variable_name datatype;
BEGIN
statements;
EXCEPTION
WHEN exception_name THEN
statements;
END;
or
CREATE OR REPLACE FUNCTION
Write an example with my values.
CREATE OR REPLACE FUNCTION dict.only_dict (
)
RETURNS void AS
$body$
BEGIN
delete from dict.dct_task_type;
insert into dict.dct_task_type(id_task_type,nm_task_type)
SELECT * FROM dblink
('ccrm_pp',
$$ select id_dict, nm_dict from dict.d_dict where kd_dict_entity = 30 $$)
AS dct_task_type(id_task_type bigint,nm_task_type varchar);
delete from dict.dct_task_resolution;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
PARALLEL UNSAFE
COST 100;
ALTER FUNCTION dict.only_dict ()
OWNER TO mb_owner;

How to create a procedure that returns a set of rows from a table in postgreSQL

How do i create a Procedure that returns a set of rows from a table?
or is it even possible to return a tabular result set with procedure.
I tried adding returns setof students like you do in a function and table(id int) but it doesn't work.
SAMPLE CODE:
CREATE OR REPLACE PROCEDURE getStudents()
LANGUAGE plpgsql
AS $$
BEGIN
SELECT * FROM STUDENTS
COMMIT;
RETURN;
END;
$$;
I can call it but it says query has no destination for result data
Procedures aren't meant to return data, that's what functions are for.
You can use a plain SQL function for this, no need for PL/pgSQL:
CREATE OR REPLACE funct get_students()
returns setof student
LANGUAGE sqö
AS $$
select *
from students;
$$;
Then use it like a table:
select *
from get_students();
There is also no need for a commit.
Try to use function instead of procedure. I usually use this.
You need to create a ctype for fetching the data.
Put whatever columns you have to fetch from STUDENTS table.
Syntax is as follows:
CREATE TYPE students_data_ctype AS
(
column_1 int4,
column_2 varchar(100),
column_3 varchar(500)
)
Then create a funcction :
CREATE
OR
REPLACE
FUNCTION PUBLIC.getStudents
()
RETURNS SETOF students_data_ctype AS $BODY$ DECLARE res
students_data_ctype;
BEGIN
FOR res IN
SELECT
column_1,
column_2,
column_3
FROM
STUDENTS
LOOP RETURN NEXT res;
END LOOP;
END
; $BODY$ LANGUAGE 'plpgsql'
GO
Function call :
Select * FROM getStudents()
Taddaaa! You will get your data.

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.

Dynamic Query Creation and Execution PostgreSQL

I have below two tables and two pl/pgsql functions.
CREATE TABLE tt1(id int, name text);
CREATE TABLE tt2(id int, name text);
INSERT INTO tt1 VALUES (1,'name1');
INSERT INTO tt1 VALUES (2,'name2');
INSERT INTO tt1 VALUES (3,'name3');
INSERT INTO tt2 VALUES (4,'name4');
INSERT INTO tt2 VALUES (5,'name5');
INSERT INTO tt2 VALUES (6,'name6');
CREATE OR REPLACE FUNCTION query_string() RETURNS TEXT AS
$BODY$
DECLARE
query1 TEXT:='';
BEGIN
query1 := 'SELECT * FROM tt1 UNION ALL SELECT * FROM tt2';
RETURN query1;
END;
$BODY$
LANGUAGE PLPGSQL;
CREATE OR REPLACE FUNCTION use_generated_string() RETURNS VOID AS
$BODY$
DECLARE
BEGIN
-- Need to modify here to get the result same as below query by
-- calling above function.
-- "SELECT * FROM tt1 UNION ALL SELECT * FROM tt2"
END;
$BODY$
LANGUAGE PLPGSQL;
query_string function returns the query string.
How can i modify the "use_generated_string" function so that I can get the result of the below query by calling the use_generated_string function.
SELECT * FROM tt1 UNION ALL SELECT * FROM tt2;
Can anyone help ?
If you declare the return type (and keep the called SQL fixed to that type) you can do:
CREATE OR REPLACE FUNCTION use_generated_string() RETURNS TABLE( c1 INT, c2 TEXT ) AS
$BODY$
DECLARE
BEGIN
RETURN QUERY EXECUTE query_string();
END;
$BODY$
LANGUAGE PLPGSQL;
If the return type should remain dynamic, this is a delicate problem and I suggest to start with reading Erwin Brandstetter's excellent answers.
klin's answer avoids the whole problem by using RAISE NOTICE, which is quite clever, but I'm unsure how one would go about using the results of such a call, except for manual text parsing.
Use EXECUTE:
CREATE OR REPLACE FUNCTION use_generated_string() RETURNS VOID AS
$BODY$
DECLARE
rec record;
BEGIN
FOR rec IN EXECUTE(query_string()) LOOP
RAISE NOTICE '%', rec.name;
END LOOP;
END;
$BODY$
LANGUAGE PLPGSQL;
SELECT use_generated_string();
NOTICE: name1
NOTICE: name2
NOTICE: name3
NOTICE: name4
NOTICE: name5
NOTICE: name6
use_generated_string
----------------------
(1 row)

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()