I am using postgres functions to create a crosstab table.
My first dynamic query function (dynamic_crosstab) creates a sql select statement, and then this select statement gives the final result on execution.
dynamic_crosstab functions works perfectly
I need to execute this select query (result of dynamic_crosstab function) by using parameters, so I am again using a function again.
CREATE OR REPLACE FUNCTION primary_cross(
date,
date,
integer)
RETURNS text AS
$BODY$
declare
val_1 text;
begin
select * from dynamic_crosstab('select
p.location_id, p.employee_id, pt.description, sum(p.hours_allocated) as hours_allocated
from
preference_type pt, preference p, preference_date_etl pde, date_etl de
where
pt.id = p.preference_type_id and
pde.preference_id = p.id and
pde.corporation_id = $3 and
de.id = pde.date_etl_id and
pde.deleted = ''''N'''' and
p.deleted = ''''N'''' and
pt.deleted = ''''N'''' and
de.local_date between ''''$1'''' and ''''$2'''' and
p.employee_id IN (
select id from employee where user_id IN (select id from app_user where corporation_id = $3))
group by p.location_id, p.employee_id, pt.description',
'select distinct description from preference_type where corporation_id =$3',
'text','location_id int , employee_id int',false) INTO val_1;
return val_1;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Note that dynamic_crosstab function uses 4 input parameters. 2 sql queries, 2 text value and a boolean.
Now the problem is that parameters are passed correctly to first input of dynamic_crosstab function but they do not pass to the second input of dynamic_crosstab which is:
'select distinct description from preference_type where deleted=''''N'''' and corporation_id =$3'
I get the following error:
ERROR: there is no parameter $3
LINE 1: ...ct description from preference_type where corporation_id =$3
^
QUERY: select distinct description from preference_type where corporation_id =$3
Please advise how i can use the parameters to make this work.
Thank You.
Related
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');
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 would like to use a function/procedure to add to a 'template' table an additional column (e.g. period name) with multiple values, and do a cartesian product on the rows, so my 'template' is duplicated with the different values provided for the new column.
E.g. Add a period column with 2 values to my template_country_channel table:
SELECT *
FROM unnest(ARRAY['P1', 'P2']) AS prd(period)
, template_country_channel
ORDER BY period DESC
, sort_cnty
, sort_chan;
/*
-- this is equivalent to:
(
SELECT 'P2'::text AS period
, *
FROM template_country_channel
) UNION ALL (
SELECT 'P1'::text AS period
, *
FROM template_country_channel
)
--
*/
This query is working fine, but I was wondering if I could turn that into a PL/pgSQL function/procedure, providing the new column values to be added, the column to add the extra column to (and optionally specify the order by conditions).
What I would like to do is:
SELECT *
FROM template_with_periods(
'template_country_channel' -- table name
, ARRAY['P1', 'P2'] -- values for the new column to be added
, 'period DESC, sort_cnty, sort_chan' -- ORDER BY string (optional)
);
and have the same result as the 1st query.
So I created a function like:
CREATE OR REPLACE FUNCTION template_with_periods(template regclass, periods text[], order_by text)
RETURNS SETOF RECORD
AS $BODY$
BEGIN
RETURN QUERY EXECUTE 'SELECT * FROM unnest($2) AS prd(period), $1 ORDER BY $3' USING template, periods, order_by ;
END;
$BODY$
LANGUAGE 'plpgsql'
;
But when I run:
SELECT *
FROM template_with_periods('template_country_channel', ARRAY['P1', 'P2'], 'period DESC, sort_cnty, sort_chan');
I have the error ERROR: 42601: a column definition list is required for functions returning “record”
After some googling, it seems that I need to define the list of columns and types to perform the RETURN QUERY (as the error message precisely states).
Unfortunately, the whole idea is to use the function with many 'template' tables, so columns name & type lists is not fixed.
Is there any other approach to try?
Or is the only way to make it work is to have within the function, a way to get list of columns' names and types of the template table?
I did this with refcursor if You want output columns list completely dynamic:
CREATE OR REPLACE FUNCTION is_record_exists(tablename character varying, columns character varying[], keepcolumns character varying[] DEFAULT NULL::character varying[])
RETURNS SETOF refcursor AS
$BODY$
DECLARE
ref refcursor;
keepColumnsList text;
columnsList text;
valuesList text;
existQuery text;
keepQuery text;
BEGIN
IF keepcolumns IS NOT NULL AND array_length(keepColumns, 1) > 0 THEN
keepColumnsList := array_to_string(keepColumns, ', ');
ELSE
keepColumnsList := 'COUNT(*)';
END IF;
columnsList := (SELECT array_to_string(array_agg(name || ' = ' || value), ' OR ') FROM
(SELECT unnest(columns[1:1]) AS name, unnest(columns[2:2]) AS value) pair);
existQuery := 'SELECT ' || keepColumnsList || ' FROM ' || tableName || ' WHERE ' || columnsList;
RAISE NOTICE 'Exist query: %', existQuery;
OPEN ref FOR EXECUTE
existQuery;
RETURN next ref;
END;$BODY$
LANGUAGE plpgsql;
Then need to call FETCH ALL IN to get results. Detailed syntax here or there: https://stackoverflow.com/a/12483222/630169. Seems it is the only way for now. Hope something will be changed in PostgreSQL 11 with PROCEDURES.
I have the following function:
In which I am updating one database table by joining other database table by using the dblink().
I have installed:
create extension dblink;
The more details as shown below:
CREATE OR REPLACE FUNCTION Fun_test
(
Table_Name varchar
)
RETURNS void AS
$BODY$
DECLARE
dynamic_statement varchar;
BEGIN
perform dblink_connect('port=5234 dbname=testdb user=postgres password=****');
dynamic_statement := 'With CTE AS
(
Select HNumber,JoiningDate,Name,Address
From '|| Table_Name ||'c
)
, Test_A
AS
(
Select Row_Number() over ( Partition by PNumber order by Date1 Desc,Date2 Desc) AS roNum,
Name,PNumber,Date1,Address
From dblink(
''Select distinct PNumber,
(
case when fname is null then '' else fname end || '' ||
case when lname is null then '' else lname end
) as FullName,
Address,
Date1,Date2
From testdb_Table
inner join CTE on CTE.HNumber = PNumber''
) Num
)
Update CTE
Set
Name = Test_A.FullName
,SubAddress_A = Test_A.Address
,Date1 = Test_A.Date1
from CTE
left outer join Test_A on
CTE.HNumber= Test_A.PNumber
where roNum =1';
RAISE INFO '%',dynamic_statement;
EXECUTE dynamic_statement;
perform dblink_disconnect();
END;
$BODY$
LANGUAGE PLPGSQL;
Calling Function:
select fun_test('test1');
Getting an error:
ERROR: a column definition list is required for functions returning "record"
LINE 11: From dblink
^
You have to tell PostgreSQL what the columns the dblink query will return are.
See the manual for dblink for details.
This is the same as for any function returning a runtime-determined record type. You can't query it without telling PostgreSQL what the column layout of the results will be.
You use a column specifier list, e.g.
SELECT * FROM my_function_returning_record() f(col1 text, col2 integer);
If you are on a current PostgreSQL version you may want to look at postgres_fdw as an alternative to dblink.
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