I have a query that I use in different parts of a system. Now I want to make a function wherein it accepts a text to be used as a query, I'm not even sure if this is possible. However, I want to know if there's a possible workaround for this.
What I want to achieve is a function that returns a table and accepts a text/varchar that could be used as a query.
Here's what I have, I have a query that is kind of a "base query" that can have different data based on the given CTE which is named:
data_table
refer to the function and its usage below - this is mostly abstract but imo this should be enough.
CREATE OR REPLACE FUNCTION func(
data_query TEXT
)
RETURNS TABLE
(
id BIGINT,
random_text varchar
)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
with data_table AS (
data_query
), another_data_table AS (
SELECT
*
FROM my_data
)
SELECT
d.id,
ad.random_text
FROM data_table d
INNER JOIN another_data_table ad
ON ad.ref_id = d.id;
END; $function$;
usage:
SELECT * FROM func('SELECT * FROM my_data_table');
SELECT * FROM func('SELECT * FROM my_second_data_table');
Instead of passing the query, you may pass the table name and access it dyamically in your query using EXECUTE format
CREATE OR REPLACE FUNCTION func(
table_name_in TEXT
)
RETURNS TABLE
(
id BIGINT,
random_text varchar
)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY EXECUTE format (
'SELECT
d.id :: bigint,
ad.random_text :: varchar
FROM %I d
INNER JOIN my_data ad
ON ad.ref_id = d.id', table_name_in );
END
$function$;
usage:
SELECT * FROM func('my_data_table');
SELECT * FROM func('my_second_data_table');
Related
I'm using postgresql 14 and trying to return an entire record from a table in addition to columns from different tables. The catch is, that I don't want to write all the titles in the record. I tried working with the guide lines here [1], but I'm getting different errors. Can anyone tell me what I'm doing wrong?
CREATE OR REPLACE FUNCTION public.get_license_by_id(license_id_ integer)
RETURNS TABLE(rec tbl_licenses, template_name character varying, company_name character varying)
LANGUAGE 'plpgsql'
COST 100
VOLATILE SECURITY DEFINER PARALLEL UNSAFE
ROWS 1000
AS $BODY$
DECLARE
BEGIN
CREATE TEMPORARY TABLE tempTable AS (
SELECT
(rec).*, B.company_name, C.template_name
-- Tried using A instead of (rec).* with error: column "a" has pseudo-type record
FROM
(
(SELECT * FROM tbl_licenses) A
LEFT JOIN
(SELECT * FROM tbl_customers) B on A.customer_id = B.customer_id
LEFT JOIN
(SELECT * FROM tbl_templates) C on A.template_id = C.template_id
)
);
UPDATE tempTable
SET license = '1'
WHERE tempTable.license IS NOT NULL;
RETURN QUERY (
SELECT * FROM tempTable
);
DROP TABLE tempTable;
RETURN;
END;
$BODY$;
I'm calling the function like SELECT rec FROM get_license_by_id(1);
but getting:
ERROR: structure of query does not match function result type
DETAIL: Returned type integer does not match expected type tbl_licenses in column 1.
You need to cast the A alias to the correct record type. However the nested derived tables are not necessary for the other tables. If you use a coalesce() for the license column in the SELECT, then you get get rid of the inefficient creation and update of the temp table as well.
CREATE OR REPLACE FUNCTION get_license_by_id(license_id_ integer)
RETURNS TABLE(rec tbl_licenses, template_name character varying, company_name character varying)
LANGUAGE sql
STABLE
AS $BODY$
SELECT a::tbl_licenses, -- this is important
B.company_name, C.template_name
FROM (
select license_id, customer_id, ... other columns ...,
coalesce(license, '1') as license -- makes the temp table unnecessary
from tbl_licenses A
) a
LEFT JOIN tbl_customers B on A.customer_id = B.customer_id
LEFT JOIN tbl_templates C on A.template_id = C.template_id
where a.license_id = license_id_;
$BODY$
;
I'm new to Postgres. I tried to run the following in pgAdmin/DBeaver but getting "ERROR: query has no destination for result data" error
do $$
declare customerid integer := 151;
begin
SELECT * FROM get_orders(customerid);
end $$
My guts tell me that it is something simple. What do I need to change so it will display the results in DBeaver or pgAdmin?
I don't want something like this:
SELECT * FROM get_orders(151);
I do want to use something like a variable to separate from the actual select statement.
Thanks.
script segments to prepare the table/function
------------------
CREATE TABLE orders
(
id integer,
customerid INTEGER,
description varchar(100)
)
------------------
INSERT INTO Orders VALUES
(1,101, 'Test Order 1'),
(2,151, 'Random Order')
------------------
CREATE OR REPLACE FUNCTION get_orders (p_customerid int)
RETURNS TABLE (
id integer,
customerid INTEGER,
description varchar(100)
)
AS $$
BEGIN
RETURN QUERY SELECT
*
FROM
orders ord
WHERE
ord.customerid = p_customerid;
END; $$
LANGUAGE 'plpgsql';
SQL is different from other programming languages:
It has no concept of execution order, so the idea of first setting a variable and then using it is alien to SQL.
SQL has no concept of “variables” in the first place.
In SQL, a “program” is a single statement.
So, SQL is lacking when it comes to variables and procedural programming. The remedy is to use a function in a procedural language like PL/pgSQL. Then you can use variables, and you can return the output as function result.
The DO statement has no way of returning results.
with the help of this: How to declare a variable in a PostgreSQL query.
I guess DO statement can't return results. Here are alternatives:
DO $$
declare customerid integer := 151;
BEGIN
CREATE TEMP TABLE tmp_orders ON COMMIT DROP AS
SELECT * FROM get_orders(customerid);
END $$;
SELECT * FROM tmp_orders;
-----------------------------
PREPARE temp(int) AS
SELECT * FROM get_orders($1);
EXECUTE temp(151)
-----------------------------
WITH
custid AS (VALUES (151))
SELECT * FROM get_orders((table custid))
-- DBeaver Only
#set custid = 151
SELECT * FROM get_orders(${custid});
I have a table
create table abac_object (
inbound abac_attribute,
outbound abac_attribute,
created_at timestamptz not null default now(),
primary key (inbound, outbound)
);
abac_attribute is my custom type that is defined like
create type abac_attribute as (
value text,
key text,
namespace_id uuid
);
I try to create a number of functions that work over the table
create function abac_object_list_1(_attr abac_attribute)
returns setof abac_object as $$
select *
from abac_object
where outbound = _attr
$$ language sql stable;
create function abac_object_list_2(_attr1 abac_attribute, _attr2 abac_attribute)
returns setof abac_object as $$
select t1.*
from abac_object as t1
inner join abac_object as t2 on t1.inbound = t2.inbound
where
t1.outbound = _attr1
and t2.outbound = _attr2
$$ language sql stable;
create function abac_object_list_3(_attr1 abac_attribute, _attr2 abac_attribute, _attr3 abac_attribute)
returns setof abac_object as $$
select t1.*
from abac_object as t1
inner join abac_object as t2 on t1.inbound = t2.inbound
inner join abac_object as t3 on t1.inbound = t3.inbound
where
t1.outbound = _attr1
and t2.outbound = _attr2
and t3.outbound = _attr3
$$ language sql stable;
create function abac_object_list(_attrs abac_attribute[], _offset integer, _limit integer)
returns setof abac_attribute as $$
begin
case array_length(_attrs, 1)
when 1 then return query
select t.inbound
from abac_object_list_1(_attrs[1]) as t
order by t.created_at
offset _offset
limit _limit;
when 2 then return query
select t.inbound
from abac_object_list_2(_attrs[1], _attrs[2]) as t
order by t.created_at
offset _offset
limit _limit;
when 3 then return query
select t.inbound
from abac_object_list_3(_attrs[1], _attrs[2], _attrs[3]) as t
order by t.created_at
offset _offset
limit _limit;
else raise exception 'bad argument' using detail = 'array length should be less or equal to 3';
end case;
end
$$ language plpgsql stable;
abac_object_list_1 works fine
select *
from abac_object_list_1(('apple', 'type', '00000000-0000-1000-a000-000000000010') ::abac_attribute);
However with abac_object_list I get the following error
select *
from abac_object_list(array [('apple', 'type', '00000000-0000-1000-a000-000000000010')] :: abac_attribute[], 0, 100);
[42804] ERROR: structure of query does not match function result type Detail: Returned type abac_attribute does not match expected type text in column 1. Where: PL/pgSQL function abac_object_list(abac_attribute[],integer,integer) line 4 at RETURN QUERY
I can't understand where text type comes from.
These functions worked before but then I added created_at column to be able to add explicit ORDER BY clause. And now I can't rewrite functions correctly.
Also maybe there is a better (or more idiomatic) way to define these function.
Could you please help me to figure it out?
Thank you.
P. S.
Well, it seems to work if I write
return query select (t.inbound).*
But I am not sure if it is a right approach.
Also I wonder what is better to use return table or return setof.
I have a fields table to store column information for other tables:
CREATE TABLE public.fields (
schema_name varchar(100),
table_name varchar(100),
column_text varchar(100),
column_name varchar(100),
column_type varchar(100) default 'varchar(100)',
column_visible boolean
);
And I'd like to create a function to fetch data for a specific table.
Just tried sth like this:
create or replace function public.get_table(schema_name text,
table_name text,
active boolean default true)
returns setof record as $$
declare
entity_name text default schema_name || '.' || table_name;
r record;
begin
for r in EXECUTE 'select * from ' || entity_name loop
return next r;
end loop;
return;
end
$$
language plpgsql;
With this function I have to specify columns when I call it!
select * from public.get_table('public', 'users') as dept(id int, uname text);
I want to pass schema_name and table_name as parameters to function and get record list, according to column_visible field in public.fields table.
Solution for the simple case
As explained in the referenced answers below, you can use registered (row) types, and thus implicitly declare the return type of a polymorphic function:
CREATE OR REPLACE FUNCTION public.get_table(_tbl_type anyelement)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE format('TABLE %s', pg_typeof(_tbl_type));
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM public.get_table(NULL::public.users); -- note the syntax!
Returns the complete table (with all user columns).
Wait! How?
Detailed explanation in this related answer, chapter
"Various complete table types":
Refactor a PL/pgSQL function to return the output of various SELECT queries
TABLE foo is just short for SELECT * FROM foo:
Is there a shortcut for SELECT * FROM?
2 steps for completely dynamic return type
But what you are trying to do is strictly impossible in a single SQL command.
I want to pass schema_name and table_name as parameters to function and get record list, according to column_visible field in
public.fields table.
There is no direct way to return an arbitrary selection of columns (return type not known at call time) from a function - or any SQL command. SQL demands to know number, names and types of resulting columns at call time. More in the 2nd chapter of this related answer:
How do I generate a pivoted CROSS JOIN where the resulting table definition is unknown?
There are various workarounds. You could wrap the result in one of the standard document types (json, jsonb, hstore, xml).
Or you generate the query with one function call and execute the result with the next:
CREATE OR REPLACE FUNCTION public.generate_get_table(_schema_name text, _table_name text)
RETURNS text AS
$func$
SELECT format('SELECT %s FROM %I.%I'
, string_agg(quote_ident(column_name), ', ')
, schema_name
, table_name)
FROM fields
WHERE column_visible
AND schema_name = _schema_name
AND table_name = _table_name
GROUP BY schema_name, table_name
ORDER BY schema_name, table_name;
$func$ LANGUAGE sql;
Call:
SELECT public.generate_get_table('public', 'users');
This create a query of the form:
SELECT usr_id, usr FROM public.users;
Execute it in the 2nd step. (You might want to add column numbers and order columns.)
Or append \gexec in psql to execute the return value immediately. See:
How to force evaluation of subquery before joining / pushing down to foreign server
Be sure to defend against SQL injection:
INSERT with dynamic table name in trigger function
Define table and column names as arguments in a plpgsql function?
Asides
varchar(100) does not make much sense for identifiers, which are limited to 63 characters in standard Postgres:
Maximum characters in labels (table names, columns etc)
If you understand how the object identifier type regclass works, you might replace schema and table name with a singe regclass column.
I think you just need another query to get the list of columns you want.
Maybe something like (this is untested):
create or replace function public.get_table(_schema_name text, _table_name text, active boolean default true) returns setof record as $$
declare
entity_name text default schema_name || '.' || table_name;
r record;
columns varchar;
begin
-- Get the list of columns
SELECT string_agg(column_name, ', ')
INTO columns
FROM public.fields
WHERE fields.schema_name = _schema_name
AND fields.table_name = _table_name
AND fields.column_visible = TRUE;
-- Return rows from the specified table
RETURN QUERY EXECUTE 'select ' || columns || ' from ' || entity_name;
RETURN;
end
$$
language plpgsql;
Keep in mind that column/table references may need to be surrounded by double quotes if they have certain characters in them.
Example: I am passing two parameters to function namely n(case number) and tname(table name), and want to display rows accordingly.
--Table "testing"
create table testing
(
rollno integer,
fname text,
lname text,
age integer,
branch text,
phno integer,
email text,
address text,
city text,
state text,
country text
)
--Rows insertion
insert into testing values(1,'aaa','bbb',25,'CS',1234567890,'abc#gmail.com','sector1','xyz','zyx','yxz');
insert into testing values(2,'zzz','xxx',25,'EE',987654321,'zzz#gmail.com','sector2','uvw','wvu','vuw');
--Function "f1()"
create or replace function f1(n integer,tname varchar)/*n for case number and tname for table name */
returns setof tname as
$body$
begin
case n
when 1 then
return query execute format ($$ select rollno,fname from %I $$,tname);
when 2 then
return query execute format ($$ select lname,age,branch from %I $$,tname);
when 3 then
return query execute format ($$ select phno,email,address,city,country from %I $$,tname);
end case;
end
$body$
language plpgsql;
--Function calling
select * from f1(1,'testing');
/*Show only case "1" select query result*/
select * from f1(2,'testing');
/*Show only case "2" select query result*/
select * from f1(3,'testing');
/*Show only case "3" select query result*/
While Craig is correct that return types cannot be dynamic in function declarations, there is a way around this with polymorphic types. This is surprisingly simple and would actually work flawlessly:
CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE 'SELECT * FROM '|| pg_typeof(_tbl_type);
END
$func$ LANGUAGE plpgsql;
Call (important!):
SELECT rollno,fname FROM data_of(NULL::testing);
SELECT * FROM data_of(NULL::my_schema.my_table);
SELECT * FROM data_of(NULL::my_custom_type);
What you need is a well-known type. For every table there is a well-known type automatically. But you can create any type, cast NULL to it and pass it to the function. This way you can build exactly what you have in your question ...
Related answer with a lot more details:
Refactor a PL/pgSQL function to return the output of various SELECT queries