Pass a Table Column Name in a PostgreSQL Function - postgresql

I've read some posts about using table column names in a PostgreSQL function but I couldn't make it work for me.
I have this simple function
DROP FUNCTION IF EXISTS public.benchmark(CHARACTER VARYING, CHARACTER VARYING, BIGINT, BIGINT, BIGINT);
CREATE OR REPLACE FUNCTION benchmark(params CHARACTER VARYING, colName CHARACTER VARYING, idFrom BIGINT, idTo BIGINT, testNumber BIGINT) RETURNS SETOF RECORD AS $$
DECLARE
elemArray TEXT[] := ARRAY(SELECT colName FROM public.test WHERE test.id BETWEEN idFrom AND idTo);
selectedElem RECORD;
elem TEXT;
BEGIN
FOREACH elem IN ARRAY elemArray LOOP
raise notice 'elem Value: %', elem;
SELECT elem INTO selectedElem;
RETURN NEXT selectedElem;
END LOOP;
END;
$$ LANGUAGE plpgsql;
When I execute it with
SELECT * FROM public.benchmark('ad','name',1,2,1) AS x(Item TEXT);
I get
and what I should be getting are the name column values between idFrom and idTo. How can I use the colName variable as an actual column name in elemArray TEXT[] := ARRAY(SELECT colName FROM public.test WHERE test.id BETWEEN idFrom AND idTo);

You may use RETURNS TABLE + RETURN QUERY EXECUTE for dynamic columns.
CREATE OR REPLACE FUNCTION benchmark(params CHARACTER VARYING, colName
CHARACTER VARYING, idFrom BIGINT, idTo BIGINT, testNumber BIGINT)
RETURNS TABLE (colvalue TEXT) AS
$$
BEGIN
RETURN QUERY EXECUTE --dynamic query
format('SELECT %I::TEXT FROM test WHERE test.id BETWEEN $1 AND $2',colName)
--dynamic cols --bind parameters
USING idFrom,idTo;
END;
$$ LANGUAGE plpgsql;
Demo
EDIT
I just want to populate it with the elements of colName and use the
array later in the extended code
You could use ARRAY_AGG & load it into an array variable instead.
CREATE OR REPLACE FUNCTION benchmark(params CHARACTER VARYING, colName
CHARACTER VARYING, idFrom BIGINT, idTo BIGINT, testNumber BIGINT)
RETURNS void AS
$$
DECLARE
elemArray TEXT[];
elem TEXT;
BEGIN
EXECUTE format('SELECT array_agg(%I::TEXT) FROM test
WHERE test.id BETWEEN $1 AND $2',colName)
USING idFrom,idTo INTO elemArray ;
FOREACH elem IN ARRAY elemArray LOOP
raise notice 'elem Value: %', elem;
END LOOP;
END;
$$ LANGUAGE plpgsql;
knayak=# DO $$
knayak$# BEGIN
knayak$# PERFORM benchmark('ad','name',1,2,1);
knayak$# END
knayak$# $$;
NOTICE: elem Value: TalG
NOTICE: elem Value: John Doe
DO

Related

PostgreSQL - how to use equals for null in routine

I have a routine with 4 parameters passed in. Sometimes the last parameter "membership_type " is null but when I use equals it does not return records with null value, it does work if I use "is null" but looking for advice how to make sure both work in the same routine:
create or replace function fitnessone_master.get_location_cancellation_count(club_number double precision, date date, member_profile character varying, membership_type character varying) returns integer
language plpgsql
as $$
declare
member_count integer;
begin
return (select count(distinct a.agreement_number) as member_count
from fitnessone_master.cancelled_accounts a
where cancel_date = get_location_cancellation_count.date
and a.club_number = get_location_cancellation_count.club_number
and a.member_profile = get_location_cancellation_count.member_profile
and a.membership_type = get_location_cancellation_count.membership_type);
end;
$$;
Instead of using = x, which will never match NULL, you can use IS NOT DISTINCT FROM x:
create or replace function fitnessone_master.get_location_cancellation_count(club_number double precision, date date, member_profile character varying, membership_type character varying) returns integer
language plpgsql
as $$
declare
member_count integer;
begin
return (select count(distinct a.agreement_number) as member_count
from fitnessone_master.cancelled_accounts a
where cancel_date = get_location_cancellation_count.date
and a.club_number = get_location_cancellation_count.club_number
and a.member_profile = get_location_cancellation_count.member_profile
and a.membership_type is not distinct from get_location_cancellation_count.membership_type);
end;
$$;

nested cursor loop in postgresql

I am new to postgresql, and get a problem about nested loop.Here is my code:
CREATE TABLE q_39442172
(
id character varying,
event_id character varying,
createdat character varying
);
insert into q_39442172 values('id1', 'event_1', '20160789');
insert into q_39442172 values('id2', 'event_2', '20160689');
insert into q_39442172 values('id3', 'event_3', '20160679');
insert into q_39442172 values('id4', 'event_4', '20160579');
insert into q_39442172 values('id3', 'event_3', '20160579');
insert into q_39442172 values('id2', 'event_5', '20160379');
insert into q_39442172 values('id1', 'event_6', '20160339');
create or replace function query_event_sequence() returns table( r_id character varying, r_events text ) as
$$
declare
vc_id character varying;
vc_event_id character varying;
begin
for ref_User in execute 'select distinct id from q_39442172 order by id' loop
vc_id := ref_User.id;
r_id := ref_User.id;
for ref_Event in execute 'select event_id from q_39442172 where id = ' || vc_id loop
vc_event_id := ref_Event.event_id;
r_events := concat_ws( ',', r_events, vc_event_id );
end loop;
raise notice '%: %', r_id, r_events;
return next;
end loop;
end;
$$
language plpgsql;
The exception i get:
NOTICE: id1: event_6,event_1
ERROR: cursor "<unnamed portal 2>" already in use
CONTEXT: PL/pgSQL function query_event_sequence() line 13 at OPEN
********** Error **********
ERROR: cursor "<unnamed portal 2>" already in use
SQL state: 42P03
Actually, using array_agg can do what i want to do, but i am just confused about why nested cursor loop in my code won't work.
You don't need a function or a cursor for this. A single SQL statement will do:
select string_agg(concat_ws(',', event_id, id), ',' order by id)
from q_39442172
where id in (select id from q_39442172)

Is there a functional form of union using a set or array in Postgresql

I have an SET of id's in a WHERE statement that gives me valid seasonal days for certain taxa id's
WHERE
...
tx_id IN ('00020','00030','00059') AND
datepart('doy',dt) IN (
SELECT TAXA_SEASON(1,'00020') UNION
SELECT TAXA_SEASON(1,'00030') UNION
SELECT TAXA_SEASON(1,'00059') )
and tax_sesion is a function for the 4 seasons I can select.
CREATE OR REPLACE FUNCTION taxa_season(SEAS INTEGER, EURING TEXT)
RETURNS SETOF INTEGER AS
$BODY$
...
$BODY$
LANGUAGE plpgsql;
Is there an functional form of union using a set or array in Postgresql
datepart('doy',dt) IN (SELECT TAXA_SEASION(1,{'00020','00030','00059'}) )
CREATE OR REPLACE FUNCTION taxa_season(SEAS INTEGER, EURINGS TEXT[])
RETURNS SETOF INTEGER AS
$BODY$
DECLARE
E TEXT;
BEGIN
FOREACH E IN ARRAY EURINGS LOOP
RETURN QUERY SELECT TAXA_SEASON(SEAS, E);
END LOOP;
RETURN;
END
$BODY$
LANGUAGE plpgsql;
Usage:
WHERE
...
tx_id = ANY(ARRAY['00020','00030','00059']) AND
datepart('doy',dt) IN (SELECT TAXA_SEASON(1,ARRAY['00020','00030','00059']) )
Upd: It was "lazy" solution does not required the changes in the existing code. The right solution is to invert the logic:
Create functions like:
CREATE OR REPLACE FUNCTION taxa_season(SEAS INTEGER, EURING TEXT[])
RETURNS SETOF INTEGER AS
$BODY$
-- Get ready to use (distinct) data for all values from EURING
$BODY$
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION taxa_season(SEAS INTEGER, EURING TEXT)
RETURNS SETOF INTEGER AS
$BODY$
select taxa_season(SEAS, ARRAY[EURING])
$BODY$
LANGUAGE sql; -- Note that it is simple SQL function
It would be more efficiency.
Thanks #Abelisto finally it helps me out and the whole construct looks like this..
-- --------------------------------------------------------
CREATE OR REPLACE FUNCTION taxa_season(SEAS INTEGER, EURING TEXT)
RETURNS SETOF INTEGER AS
$BODY$
...find some date limits in the taxa tables
$BODY$
LANGUAGE plpgsql;
-- --------------------------------------------------------
CREATE OR REPLACE FUNCTION taxa_season(SEAS INTEGER, EURINGS TEXT[])
RETURNS SETOF INTEGER AS
$BODY$
DECLARE
E TEXT;
BEGIN
FOREACH E IN ARRAY EURINGS LOOP
RETURN QUERY SELECT TAXA_SEASON(SEAS, E);
END LOOP;
RETURN;
END
$BODY$
LANGUAGE plpgsql;
-- ----------------------------------------------
CREATE OR REPLACE FUNCTION sessions_taxa_season(
YR INTEGER,
SEAS INTEGER,
LOC TEXT,
EURINGS TEXT[])
RETURNS SETOF TEXT AS
$BODY$
DECLARE
R RECORD;
BEGIN
IF SEAS > 0 AND SEAS < 4 THEN
FOR R IN
SELECT DISTINCT session
FROM sync_utm32
WHERE
date_part('doy', gps_dt)
IN (SELECT DISTINCT TAXA_SEASON(SEAS, EURINGS)) AND
date_part('year', gps_dt) = YR AND
session ~~ LOC
ORDER BY SESSION
LOOP
RETURN NEXT R.SESSION;
END LOOP;
END IF;
IF SEAS=4 THEN
FOR R IN
SELECT DISTINCT SESSION
FROM sync_utm32
WHERE
date_part('doy', gps_dt)
IN (SELECT DISTINCT TAXA_SEASON(SEAS, EURINGS)) AND
date_part('year', gps_dt) = YR-1 AND
date_part('doy', gps_dt) >= 365/2 AND
session ~~ LOC
ORDER BY SESSION
LOOP
RETURN NEXT R.SESSION;
END LOOP;
FOR R IN
SELECT DISTINCT SESSION
FROM sync_utm32
WHERE
date_part('doy', gps_dt)
IN (SELECT DISTINCT TAXA_SEASON(SEAS, EURINGS)) AND
date_part('year', gps_dt) = YR AND
date_part('doy', gps_dt) < 365/2 AND
session ~~ LOC
LOOP
RETURN NEXT R.SESSION;
END LOOP;
END IF;
RETURN;
END
$BODY$
LANGUAGE plpgsql;
Result of request:
SELECT SESSIONS_TAXA_SEASON(2016, 4,'%XX',ARRAY['00020','00030','00059']);
sessions_taxa_season
----------------------
2015-11-01-XX
2015-12-07-XX
2016-02-16-XX
2016-01-21-XX
The rest is sprintf template driven from perl DBI.

Use Variable in a select statement in a Function

Is it possible to declare a variable from another variable that was already declared in a function?
Example of what I am expecting below..
CREATE OR REPLACE FUNCTION insertRecord
(
a varchar (100),
b varchar(100),
c varchar(100)
)
RETURNS TEXT AS $$
DECLARE
orgId := (select id from org)
projectId := select id from project where orgId = orgId
BEGIN
return projectId;
END;
$$ LANGUAGE plpgsql;
First declare the variables. And rename the orgId to avoid ambiguity with the column name.
CREATE OR REPLACE FUNCTION insertRecord
(
a varchar (100),
b varchar(100),
c varchar(100)
)
RETURNS TEXT AS $$
DECLARE
orgId_var integer;
projectId varchar;
BEGIN
orgId_var := (select id from org limit 1);
projectId := (select id from project where orgId = orgId_var);
return projectId;
END;
$$ LANGUAGE plpgsql;
Perhaps you just want to use a join instead? Something along these lines (but with more filters added so as not to return multiple rows):
CREATE OR REPLACE FUNCTION insertRecord(
a varchar (100),
b varchar(100),
c varchar(100)
)
RETURNS TEXT AS $$
DECLARE
v_project_id project.id%TYPE;
BEGIN
SELECT p.id
INTO v_project_id
FROM project p
JOIN org o ON (o.id = p.org_id);
RETURN v_project_id;
END;
$$ LANGUAGE plpgsql;

How to Return a Table That Was Created Within Function?

I have a PL/pgSQL function that creates a table. I would like to return the table that was created as part of a "Return Table" function. Here is an example of the type of function I'm talking about:
CREATE OR REPLACE FUNCTION my_schema.new_foo_table(character varying)
RETURNS table(row_id bigint,
catalog_id varchar,
value numeric(5,4)) AS
$BODY$
DECLARE
v_table_name ALIAS FOR $1;
cmd text;
BEGIN
cmd := 'CREATE TABLE '||v_table_name||'
(row_id bigint,
catalog_id varchar,
value numeric(5,4))';
EXECUTE cmd;
cmd := 'INSERT INTO '||v_table_name||'
SELECT row_id, catalog_id, sum(value)
FROM other_table
GROUP BY row_id, catalog_id';
EXECUTE cmd;
RETURN --results of select * from v_table_name;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
How would I return the results of the new table I created? I'm guess the return would have to use some type of dynamic SQL "execute" statement since the name of the newly created table is in a variable.
Yes this can be done using RETURN QUERY EXECUTE 'command string'. For your example it would look like this:
CREATE OR REPLACE FUNCTION my_schema.new_foo_table(character varying)
RETURNS table(row_id bigint,
catalog_id varchar,
value numeric(5,4)) AS
$BODY$
DECLARE
arg_table_name ALIAS FOR $1;
v_table_name varchar;
cmd text;
BEGIN
v_table_name := quote_ident(arg_table_name);
cmd := 'CREATE TABLE '||v_table_name||'
(row_id bigint,
catalog_id varchar,
value numeric(5,4))';
EXECUTE cmd;
cmd := 'INSERT INTO '||v_table_name||'
SELECT row_id, catalog_id, sum(value)
FROM other_table
GROUP BY row_id, catalog_id';
EXECUTE cmd;
RETURN QUERY EXECUTE 'select * from v_table_name';
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
For additional information see section 40.6.1.2. in the POSTGRESQL documentation: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html#PLPGSQL-STATEMENTS-RETURNING