plpgsql - selecting array of multiple columns - postgresql

I am trying to create procedure which selects data, processes and returns them, but I am struggling how to define array variable for multiple columns.
This works:
CREATE OR REPLACE FUNCTION testing_array_return()
RETURNS TABLE(id BIGINT) AS
$body$
DECLARE
l_rows BIGINT[];
BEGIN
-- select data using for update etc
l_rows := ARRAY(
SELECT 1 AS id
UNION
SELECT 2 AS id
);
-- do some stuff
-- return previously selected data
RETURN QUERY
SELECT *
FROM UNNEST(l_rows);
END;
$body$
LANGUAGE 'plpgsql'
But I want to do this for 2 or more columns without using composite type or rowtype:
CREATE OR REPLACE FUNCTION testing_array_return()
RETURNS TABLE(id BIGINT, text VARCHAR2) AS
$body$
DECLARE
l_rows -- what should I put here?
BEGIN
-- select data using for update etc
l_rows := ARRAY(
SELECT 1 AS id, 'test' AS text
UNION
SELECT 2 AS id, 'test2' AS text
);
-- do some stuff
-- return previously selected data
RETURN QUERY
SELECT *
FROM UNNEST(l_rows);
END;
$body$
LANGUAGE 'plpgsql'
In oracle I could define record type and then table type of it, but I can't find how to do this in postgres. Maybe using wrong keywords when searching...
edit: this is how I would do this in Oracle (without returning).
DECLARE
TYPE t_row IS RECORD(
id NUMBER
,text VARCHAR2(10));
TYPE t_tbl IS TABLE OF t_row;
l_rows t_tbl := t_tbl(); --how to do this in postgres?
BEGIN
SELECT *
BULK COLLECT
INTO l_rows
FROM (SELECT 1 AS id
,'test' AS text
FROM dual
UNION ALL
SELECT 2 AS id
,'test' AS text
FROM dual);
END;
Anything similiar in postgres? Like record but for array.

You would do the same in Postgres:
Create the record type:
create type footype as (id bigint, text_ varchar);
Then use an array of that type in your function:
CREATE OR REPLACE FUNCTION testing_array_return()
RETURNS TABLE(id BIGINT, text VARCHAR) AS
$body$
DECLARE
l_rows footype[];
BEGIN
-- select data using for update etc
l_rows := ARRAY(
SELECT (1, 'test')
UNION
SELECT (2, 'test2')
);
-- do some stuff
-- return previously selected data
RETURN QUERY
SELECT *
FROM UNNEST(l_rows);
END;
$body$
LANGUAGE plpgsql;
Online example: http://rextester.com/UMRZFO54266
The expression (1, 'test') creates a single value of record type. As that is assigned to a typed variable, there is no need to alias the columns (and in fact you can't do that anyway).
Unrelated, but: the language name is an identifier. Do not put that in single quotes.
Note that text is also a keyword in Postgres because it's a data type. You shouldn't use it as a column name

Related

How avoid extra quotes added within a loop in postgresql

When performing a loop on a table in which a value has ", I get those quotes doubled such as:
initial input: 'test AND "test"'
output in the loop: 'test AND ""test""'
How to reproduce:
CREATE TABLE roles(
id int,
criteria VARCHAR (255)
);
INSERT INTO roles (id, criteria) VALUES (1, 'test AND "test"');
CREATE OR REPLACE FUNCTION test_quote()
RETURNS VARCHAR
LANGUAGE plpgsql
AS $$
DECLARE
sample varchar;
rec record;
BEGIN
FOR rec IN
SELECT * FROM roles
LOOP
sample:= rec;
END LOOP;
RETURN sample;
END
$$;
SELECT * FROM test_quote();
Expected result: (1,"test AND "test"")
Actual result: (1,"test AND ""test""")
Does anyone have an idea how to get the expected behaviour here?
Found it, indeed, the issue is from the conversion from record to varchar.
I can directly pass the column from the rec like:
sample:= rec.criteria;
(I didn't know that we could do that)
So, that gives:
CREATE TABLE roles(
id int,
criteria VARCHAR (255)
);
INSERT INTO roles (id, criteria) VALUES (1, 'test AND "test"');
CREATE OR REPLACE FUNCTION test_quote()
RETURNS VARCHAR
LANGUAGE plpgsql
AS $$
DECLARE
sample varchar;
rec record;
BEGIN
FOR rec IN
SELECT * FROM roles
LOOP
sample := rec.criteria;
END LOOP;
RETURN sample;
END
$$;
SELECT * FROM test_quote();
Thx guys!

Avoid 'ambiguous column' in simple PostgreSQL function returning table

Suppose I have the following table, function and execution:
create table mytable (a INTEGER, b INTEGER);
create function test(q INTEGER)
returns table(a INTEGER, b INTEGER)
as
$body$
begin
return query select a,b from mytable;
end;
$body$
language plpgsql STABLE;
select * from test(1);
I get an 'ambiguous column name' error. I can get rid of it by changing the selection to "select t.a, t.b from mytable t" (per some similar-ish posts). But it seems very odd to have to qualify the column names when there is only 1 table in my query. I'm porting code that has quite a lot of stored procedures selecting from single tables (in various ways) and returning a table with columns that have the same name. Is there a better way than this of avoiding the error, and still having an output table with the same column names?
Thanks for any leads.
you can (you should) to use aliases.
create table mytable (a INTEGER, b INTEGER);
create function test(q INTEGER)
returns table(a INTEGER, b INTEGER)
as
$body$
begin
return query select mt.a, mt.b from mytable mt;
end;
$body$
language plpgsql STABLE;
select * from test(1);
maybe thats an option, using format and query execute:
( as the error says, it doesnt know which a to take, the pl/pgSQL variable or a columname )
create function test(q INTEGER)
returns table(a INTEGER, b INTEGER)
as
$body$
declare
lsQueryExecute text;
begin
lsQueryExecute = format('select a,b from mytable');
return query execute lsQueryExecute;
end;
$body$
language plpgsql STABLE;

Pass a schema name and table name dynamically in FROM in a select query in Postgres

Pass a schema name and table name dynamically in FROM in a select query in postgres.
I need to call a table dynamically in a from (in the select clause)
CREATE OR REPLACE FUNCTION xx.fn_build_test_(
IN p_var_archive_schema character varying,
IN p_var_archive_table character varying)
RETURNS record AS
$BODY$
declare
l_var_archive_schema VARCHAR;
l_var_archive_table VARCHAR;
l_var_test VARCHAR[];
BEGIN
l_var_archive_schema := p_var_archive_schema;
l_var_archive_table := p_var_archive_table;
SELECT array
( SELECT TO_CHAR(column_name,'YYYYMMDD')
FROM "test_table"
WHERE col1 = 1)
INTO l_var_test;
END;
$BODY$
LANGUAGE plpgsql
VOLATILE SECURITY INVOKER;
I need values for:
l_var_archive_schema VARCHAR;
l_var_archive_table VARCHAR;
in place of the test table
You do not need those local variables for schema and table.
Use format option to construct the queries and EXECUTE to run it dynamically
CREATE OR REPLACE FUNCTION xx.fn_build_test_(
IN p_var_archive_schema character varying,
IN p_var_archive_table character varying )
RETURNS record AS
$BODY$
DECLARE
l_var_test VARCHAR[];
BEGIN
SELECT array
( SELECT TO_CHAR(column_name,'YYYYMMDD')
FROM "test_table"
WHERE col1 = 1
) INTO l_var_test;
EXECUTE format (
'select col_name FROM %I.%I',
p_var_archive_schema,p_var_archive_table)
--INTO rec_variable;
END;
$BODY$
LANGUAGE plpgsql
VOLATILE SECURITY INVOKER;
If you want to return the result of a dynamic query you may use
RETURNS TABLE option and then do RETURN QUERY EXECUTE to return results from the query.
You can use dynamic SQL. For example change the SQL in the function as following. The function needs further correction as by definition it is supposed to return a record but currently not returning any value.
EXECUTE 'SELECT array
( SELECT TO_CHAR(column_name,''YYYYMMDD'')
FROM '||l_var_archive_schema||'.'||l_var_archive_table||' WHERE col1 = 1)'
INTO l_var_test;

Function to return dynamic set of columns for given table

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.

How to return multiple rows from PL/pgSQL function?

I have spent good amount of time trying to figure it out and I haven't been able to resolve it. So, I need your help please.
I am trying to write a PL/pgSQL function that returns multiple rows. The function I wrote is shown below. But it is not working.
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS SETOF RECORD
AS
$$
DECLARE result_record keyMetrics;
BEGIN
return QUERY SELECT department_id into result_record.visits
from fact_department_daily
where report_date='2013-06-07';
--return result_record;
END
$$ LANGUAGE plpgsql;
SELECT * FROM get_object_fields;
It is returning this error:
ERROR: RETURN cannot have a parameter in function returning set;
use RETURN NEXT at or near "QUERY"
After fixing the bugs #Pavel pointed out, also define your return type properly, or you have to provide a column definition list with every call.
This call:
SELECT * FROM get_object_fields()
... assumes that Postgres knows how to expand *. Since you are returning anonymous records, you get an exception:
ERROR: a column definition list is required for functions returning "record"
One way (of several) to fix this is with RETURNS TABLE (Postgres 8.4+):
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS TABLE (department_id int) AS
$func$
BEGIN
RETURN QUERY
SELECT department_id
FROM fact_department_daily
WHERE report_date = '2013-06-07';
END
$func$ LANGUAGE plpgsql;
Works for SQL functions just the same.
Related:
PostgreSQL: ERROR: 42601: a column definition list is required for functions returning "record"
I see more bugs:
first, a SET RETURNING FUNCTIONS call has following syntax
SELECT * FROM get_object_fields()
second - RETURN QUERY forwards query result to output directly. You cannot store this result to variable - it is not possible ever in PostgreSQL now.
BEGIN
RETURN QUERY SELECT ....; -- result is forwarded to output directly
RETURN; -- there will not be any next result, finish execution
END;
third - these simple functions is better to implement in SQL languages
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS SETOF RECORD AS $$
SELECT department_id WHERE ...
$$ LANGUAGE sql STABLE;
Here's one way
drop function if exists get_test_type();
drop type if exists test_comp;
drop type if exists test_type;
drop type if exists test_person;
create type test_type as (
foo int,
bar int
);
create type test_person as (
first_name text,
last_name text
);
create type test_comp as
(
prop_a test_type[],
prop_b test_person[]
);
create or replace function get_test_type()
returns test_comp
as $$
declare
a test_type[];
b test_person[];
x test_comp;
begin
a := array(
select row (m.message_id, m.message_id)
from message m
);
-- alternative 'strongly typed'
b := array[
row('Bob', 'Jones')::test_person,
row('Mike', 'Reid')::test_person
]::test_person[];
-- alternative 'loosely typed'
b := array[
row('Bob', 'Jones'),
row('Mike', 'Reid')
];
-- using a select
b := array (
select row ('Jake', 'Scott')
union all
select row ('Suraksha', 'Setty')
);
x := row(a, b);
return x;
end;
$$
language 'plpgsql' stable;
select * from get_test_type();
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS table (department_id integer)
AS
$$
DECLARE result_record keyMetrics;
BEGIN
return QUERY
SELECT department_id
from fact_department_daily
where report_date='2013-06-07';
--return result_record;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM get_object_fields()