I have a table with two columns.
Example:
create table t1
(
cola varchar,
colb varchar
);
Now I want to insert the rows from function.
In the function: I want to use two parameters which is of type varchar to insert the values into the above table. I am passing the string to insert into the table.
I am passing two string of characters as a parameters to the function:
Parameters:
cola varchar = 'a,b,c,d';
colb varchar = 'e,f,g,h';
The above parameters have to insert into the table like this:
cola colb
----------------
a e
b f
c g
d h
My try:
create or replace function fun_t1(cola varchar,colb varchar)
returns void as
$body$
Declare
v_Count integer;
v_i integer = 0;
v_f1 text;
v_cola varchar;
v_colb varchar;
v_query varchar;
Begin
drop table if exists temp_table;
create temp table temp_table
(
cola varchar,
colb varchar
);
v_Count := length(cola) - length(replace(cola, ',', ''));
raise info '%',v_Count;
WHILE(v_i<=v_Count) LOOP
INSERT INTO temp_table
SELECT LEFT(cola,CHARINDEX(',',cola||',',0)-1)
,LEFT(colb,CHARINDEX(',',colb||',',0)-1);
cola := overlay(cola placing '' from 1 for CHARINDEX(',',cola,0));
colb := overlay(colb placing '' from 1 for CHARINDEX(',',colb,0));
v_i := v_i + 1;
END LOOP;
for v_f1 IN select * from temp_table loop
v_cola := v_f1.cola; /* Error occurred here */
v_colb := v_f1.colb;
v_query := 'INSERT INTO t1 values('''||v_cola||''','''||v_colb||''')';
execute v_query;
end loop;
end;
$body$
language plpgsql;
Note: In the function I have used temp_table that is according to the requirement which
I am using for the other use also in the function which I have not display here.
Calling function:
SELECT fun_t1('a,b,c','d,e,f');
Getting an error:
missing FROM-clause entry for table "v_f1"
Try this way using split_part() : -
create or replace function ins_t1(vala varchar,valb varchar,row_cnt int) returns void as
$$
BEGIN
FOR i IN 1..row_cnt LOOP -- row_cnt is the number rows you need to insert (ex. 4 or 5 or whatever it is)
insert into t1 (cola,colb)
values (
(select split_part(vala,',',i))
,(select split_part(valb,',',i))
);
END LOOP;
END;
$$
language plpgsql
function call :select ins_t1('a,b,c,d','e,f,g,h',4)
As mike-sherrill-cat-recall said in his answer by using regexp_split_to_table
create or replace function fn_t1(vala varchar,valb varchar) returns void
as
$$
insert into t1 (cola, colb)
select col1, col2 from (select
trim(regexp_split_to_table(vala, ',')) col1,
trim(regexp_split_to_table(valb, ',')) col2)t;
$$
language sql
function call :select fn_t1('a,b,c,d','e,f,g,h')
If there's no compelling reason to use a function for this, you can just split the text using a regular expression. Here I've expressed your arguments as a common table expression, but that's just for convenience.
with data (col1, col2) as (
select 'a, b, c, d'::text, 'e, f, g, h'::text
)
select
trim(regexp_split_to_table(col1, ',')) as col_a,
trim(regexp_split_to_table(col2, ',')) as col_b
from data;
col_a col_b
--
a e
b f
c g
d h
If there is a compelling reason to use a function, just wrap a function definition around that SELECT statement.
create function strings_to_table(varchar, varchar)
returns table (col_a varchar, col_b varchar)
as
'select trim(regexp_split_to_table($1, '','')),
trim(regexp_split_to_table($2, '',''));'
language sql
stable
returns null on null input;
select * from strings_to_table('a,b,c,d', 'e,f, g, h');
col_a col_b
--
a e
b f
c g
d h
My personal preference is usually to build functions like this to return tables rather than inserting into tables. To insert, I'd usually write a SQL statement like this.
insert into foo (col_a, col_b)
select col_a, col_b from strings_to_table('a,b,c,d', 'e,f,g,h');
The simpest way is using plpython for this.
create or replace function fill_t1(cola varchar, colb varchar) returns void as $$
for r in zip(cola.split(','), colb.split(',')):
plpy.execute(plpy.prepare('insert into t1(cola, colb) values ($1, $2)', ['varchar', 'varchar']), [r[0], r[1]])
$$ language plpythonu;
The result:
# create table t1 (cola varchar, colb varchar);
CREATE TABLE
# select fill_t1('1,2,3', '4,5,6');
fill_t1
---------
(1 row)
# select * from t1;
cola | colb
------+------
1 | 4
2 | 5
3 | 6
(3 rows)
You can read about Python zip function here: https://docs.python.org/2/library/functions.html#zip
Related
I'm writing postgresql function which inserts data and returns successfully inserted data rows.
The code is below.
CREATE OR REPLACE FUNCTION public.fn_insert_test(json_data jsonb)
returns table(col_1 varchar(255),
col_2 varchar(255),
col_3 timestamp)
LANGUAGE plpgsql
AS $function$
declare
--
begin
with my_table as (
with my_table1(my_json) as (
values(
json_data
)
)
insert into "test"(col_1, col_2, col_3)
select
elem->>'val1', elem->>'val2', now()
from
my_table1 t, jsonb_array_elements(my_json->'_data') elem
on conflict(col_1) do nothing
returning *
)
select * from my_table;
end
$function$
;
select fn_insert_test('{"_data": [{"val1":"1", "val2":"1"}, {"val1":"2", "val2":"2"}]}');
It occurs error below.
SQL Error [42702]: ERROR: column reference "col_1" is ambiguous
Detail: It could refer to either a PL/pgSQL variable or a table column.
Where: PL/pgSQL function fn_insert_test(jsonb) line 5 at SQL statement
[edit]
This is not the real problem here. There is a lot of problems in your code :
-> If you want to return a table, you have to use 'return query' : http://www.postgresqltutorial.com/plpgsql-function-returns-a-table/
-> I do not think you can use the WITH like you do: https://www.postgresql.org/docs/current/queries-with.html
Here is a functoinnal version of your code. But I am not quite sure of what you want :
CREATE OR REPLACE FUNCTION public.fn_insert_test(json_data jsonb)
returns table(col_1 varchar(255),
col_2 varchar(255),
col_3 timestamp)
LANGUAGE plpgsql AS
$$
declare
--json_data jsonb := '{"_data": [{"val1":"1", "val2":"1"}, {"val1":"2", "val2":"2"}]}';
begin
create temp table res (col_1 varchar (255), col_2 varchar (255), col_3 timestamp) on commit drop;
with my_table1(my_json) as
(
values(
json_data
)
)
, inserted as
(
insert into test(col_1, col_2, col_3)
select
elem->>'val1', elem->>'val2', now()
from
my_table1 t
, jsonb_array_elements(json_data->'_data') elem
on conflict do nothing
returning *
)
insert into res
select *
from inserted;
--raise notice '%', res_v;
return query select * from res;
end
$$
;
select fn_insert_test('{"_data": [{"val1":"1", "val2":"1"}, {"val1":"2", "val2":"2"}]}');
I have code like the following that needs to be converted to Oracle. It is basically the code to select from a table all or most of its data, put that into a collection and then load it into another table, using BULK COLLECT and FORALL. I will be thankful for help. Following is the oracle code that needs to be migrated into postgresql.
SQL> desc t
Name Null? Type
----------------------------------------- -------- ----------------------------
EMPNO NUMBER(38)
ENAME VARCHAR2(4)
SQL> desc t1
Name Null? Type
----------------------------------------- -------- ----------------------------
EMPNO NUMBER(38)
ENAME VARCHAR2(4)
SQL> select * from t;
EMPNO ENAM
---------- ----
1 a
4 d
3 c
2 b
CREATE OR REPLACE PROCEDURE temp_n_bulk_load
IS
TYPE v_t IS TABLE OF t%ROWTYPE;
v1_t v_t;
BEGIN
SELECT t.*
BULK COLLECT INTO v1_t
FROM t;
DBMS_OUTPUT.put_line (TO_CHAR (v1_t.COUNT));
FORALL i IN 1 .. v1_t.COUNT
INSERT INTO T1 (EMPNO, ENAME)
VALUES (V1_T (i).empno, v1_t (i).ename);
COMMIT;
END;
/
The direct translation would be to use an array of the table's type.
CREATE OR REPLACE function temp_n_bulk_load()
returns void
as
$$
declare
v_t t[]; -- t is the table name and at the same time a data type
l_count bigint;
BEGIN
SELECT array_agg(t)
into v_t
FROM t;
l_count := cardinality(v_t);
raise notice 'Retrieved % rows', l_count;
INSERT INTO T1 (EMPNO, ENAME)
select r.*
from unnest(v_t) as r;
-- you can't COMMIT in a function
END;
language plpgsql;
But the whole approach is unnecessary complicated and will not scale - that is also true for the Oracle solution. Using a single insert into .. select is way more efficient - in Postgres just as well as in Oracle.
The whole procedure should be replaced with:
insert into t1 (empno, ename)
select empno, ename
from t1;
If you want, you can put that into a function:
CREATE OR REPLACE function temp_n_bulk_load()
returns void
as $$
insert into t1 (empno, ename)
select empno, ename
from t1;
$$
language sql;
I have multiple tables with each two rows of interest: connection_node_start_id and connection_node_end_id. My goal is to get a collection of all those IDs, either as a flat ARRAY or as a new TABLE consisting of one row.
Example output ARRAY:
result = {1,4,7,9,2,5}
Example output TABLE:
IDS
-------
1
4
7
9
2
5
My fist attempt is somewhat clumsy and does not work properly as the SELECT statement just returns one row. It seems there must be a simple way to do this, can someone point me into the right direction?
CREATE OR REPLACE FUNCTION get_connection_nodes(anyarray)
RETURNS anyarray AS
$$
DECLARE
table_name varchar;
result integer[];
sel integer[];
BEGIN
FOREACH table_name IN ARRAY $1
LOOP
RAISE NOTICE 'table_name(%)',table_name;
EXECUTE 'SELECT ARRAY[connection_node_end_id,
connection_node_start_id] FROM ' || table_name INTO sel;
RAISE NOTICE 'sel(%)',sel;
result := array_cat(result, sel);
END LOOP;
RETURN result;
END
$$
LANGUAGE 'plpgsql';
Test table:
connection_node_start_id | connection_node_end_id
--------------------------------------------------
1 | 4
7 | 9
Call:
SELECT get_connection_nodes(ARRAY['test_table']);
Result:
{1,4} -- only 1st row, rest is missing
For Postgres 9.3+
CREATE OR REPLACE FUNCTION get_connection_nodes(text[])
RETURNS TABLE (ids int) AS
$func$
DECLARE
_tbl text;
BEGIN
FOREACH _tbl IN ARRAY $1
LOOP
RETURN QUERY EXECUTE format('
SELECT t.id
FROM %I, LATERAL (VALUES (connection_node_start_id)
, (connection_node_end_id)) t(id)'
, _tbl);
END LOOP;
END
$func$ LANGUAGE plpgsql;
Related answer on dba.SE:
SELECT DISTINCT on multiple columns
Or drop the loop and concatenate a single query. Probably fastest:
CREATE OR REPLACE FUNCTION get_connection_nodes2(text[])
RETURNS TABLE (ids int) AS
$func$
BEGIN
RETURN QUERY EXECUTE (
SELECT string_agg(format(
'SELECT t.id FROM %I, LATERAL (VALUES (connection_node_start_id)
, (connection_node_end_id)) t(id)'
, tbl), ' UNION ALL ')
FROM unnest($1) tbl
);
END
$func$ LANGUAGE plpgsql;
Related:
Loop through like tables in a schema
LATERAL was introduced with Postgres 9.3.
For older Postgres
You can use the set-returning function unnest() in the SELECT list, too:
CREATE OR REPLACE FUNCTION get_connection_nodes2(text[])
RETURNS TABLE (ids int) AS
$func$
BEGIN
RETURN QUERY EXECUTE (
SELECT string_agg(
'SELECT unnest(ARRAY[connection_node_start_id
, connection_node_end_id]) FROM ' || tbl
, ' UNION ALL '
)
FROM (SELECT quote_ident(tbl) AS tbl FROM unnest($1) tbl) t
);
END
$func$ LANGUAGE plpgsql;
Should work with pg 8.4+ (or maybe even older). Works with current Postgres (9.4) as well, but LATERAL is much cleaner.
Or make it very simple:
CREATE OR REPLACE FUNCTION get_connection_nodes3(text[])
RETURNS TABLE (ids int) AS
$func$
BEGIN
RETURN QUERY EXECUTE (
SELECT string_agg(format(
'SELECT connection_node_start_id FROM %1$I
UNION ALL
SELECT connection_node_end_id FROM %1$I'
, tbl), ' UNION ALL ')
FROM unnest($1) tbl
);
END
$func$ LANGUAGE plpgsql;
format() was introduced with pg 9.1.
Might be a bit slower with big tables because each table is scanned once for every column (so 2 times here). Sort order in the result is different, too - but that does not seem to matter for you.
Be sure to sanitize escape identifiers to defend against SQL injection and other illegal syntax. Details:
Table name as a PostgreSQL function parameter
The EXECUTE ... INTO statement can only return data from a single row:
If multiple rows are returned, only the first will be assigned to the INTO variable.
In order to concatenate values from all rows you have to aggregate them first by column and then append the arrays:
EXECUTE 'SELECT array_agg(connection_node_end_id) ||
array_agg(connection_node_start_id) FROM ' || table_name INTO sel;
You're probably looking for something like this:
CREATE OR REPLACE FUNCTION d (tblname TEXT [])
RETURNS TABLE (c INTEGER) AS $$
DECLARE sql TEXT;
BEGIN
WITH x
AS (SELECT unnest(tblname) AS tbl),
y AS (
SELECT FORMAT('
SELECT connection_node_end_id
FROM %s
UNION ALL
SELECT connection_node_start_id
FROM %s
', tbl, tbl) AS s
FROM x)
SELECT string_agg(s, ' UNION ALL ')
INTO sql
FROM y;
RETURN QUERY EXECUTE sql;
END;$$
LANGUAGE plpgsql;
CREATE TABLE a (connection_node_end_id INTEGER, connection_node_start_id INTEGER);
INSERT INTO A VALUES (1,2);
CREATE TABLE b (connection_node_end_id INTEGER, connection_node_start_id INTEGER);
INSERT INTO B VALUES (100, 101);
SELECT * from d(array['a','b']);
c
-----
1
2
100
101
(4 rows)
I need to write a function that returns a table with unknown number of columns.
If i receive 'None' in column input parameter then that column shouldn't be included in the output. In postgres 9+ there is a solution for this problem.
something like below:
CREATE OR REPLACE FUNCTION data_of(id integer,col1 varchar,col2 varchar, col3 varchar)
RETURNS TABLE (count_rec, dimensions text[] ) AS
$func$
DECLARE
_dimensions text := 'col1, col2, col3'; -- If i receive 'None' in input param then i exclude that from column list
BEGIN
RETURN QUERY EXECUTE format('
SELECT count(*) as count_rec,
string_to_array($1) -- AS dimensions
FROM x
WHERE id = $2'
, _dimensions)
USING _dimensions , _id;
END
$func$ LANGUAGE plpgsql;
But in Greenplum (Postgres 8.2) i could not find any. Is there any similar solution?
thanks
You have 2 options to do it: use set-returning function returning "record" or returning your custom type.
First option:
create table test (a int, b int, c int, d varchar, e varchar, f varchar);
insert into test select id, id*2, id*3, (id*4)::varchar, (id*4)::varchar, (id*4)::varchar from generate_series(1,10) id;
create or replace function test_func(column_list varchar[]) returns setof record as $BODY$
declare
r record;
begin
for r in execute 'select ' || array_to_string(column_list, ',') || ' from test' loop
return next r;
end loop;
return;
end;
$BODY$
language plpgsql
volatile;
select * from test_func(array['a','c','e']) as f(a int, c int, e varchar);
Second option:
create table test (a int, b int, c int, d varchar, e varchar, f varchar);
insert into test select id, id*2, id*3, (id*4)::varchar, (id*4)::varchar, (id*4)::varchar from generate_series(1,10) id;
create type testtype as (
a int,
c int,
e varchar
);
create or replace function test_func() returns setof testtype as $BODY$
declare
r testtype;
begin
for r in execute 'select a,c,e from test' loop
return next r;
end loop;
return;
end;
$BODY$
language plpgsql
volatile;
select * from test_func();
But I'm 99% sure you're trying to do something wrong. In Greenplum the result of function execution cannot be used as a "table" in join conditions, because the function executes on the master. You even won't be able to create a table out of the last query returning the data from your function because of this limitation
In short, this is not a recommended way to work with data in Greenplum
I'm looking for help with a PostgreSQL function that returns a table.
I would like to know if there is a way of updating the table that is being returned.
Here's an example,
create or replace function fn_function()
return table(column01 integer, column02 integer, column03 boolean) as $$
return query
select col1, col2, false
from tableXYZ;
/* how can i update the table, let's say column03 before the function exits */
end
$$ language plpgsql;
Can i give an alias to the table being returned?
The postgre version in use is 9.0.8.
Thx in advance.
Here is a set based solution.
The RETURNS TABLE is just portal to get data out you can't do anything inside the function that defines it. You could create another function and call this one and do things to the result set. However I use a temp table so you can manipulate the data before you send it out.
CREATE OR REPLACE FUNCTION fn_function()
RETURNS TABLE(column01 integer, column02 integer, column03 boolean) AS $$
BEGIN
CREATE TEMP TABLE temp_tableXYZ (column01 integer, column02 integer, column03 boolean) ON COMMIT DROP;
INSERT INTO temp_tableXYZ (column01, column02, column03)
SELECT col1,col2,col3
FROM tableXYZ;
--WHERE filter if you can.
UPDATE temp_tableXYZ
SET col1 = 9999;
RETURN QUERY select column01, column02, column03 from temp_tableXYZ;
END;
$$ LANGUAGE plpgsql;
You can call it using an alias like this:
SELECT * FROM fn_function() as my_table;
Just do a query which returns the values you want. It can be plain sql:
create or replace function fn_function()
returns table (
column01 integer, column02 integer, column03 boolean
) as $$
select col1, col2, col2 > 10
from tableXYZ;
$$ language sql;
In the example above column 3 will be true if col2 > 10 and false otherwise. Another example using a subselect:
create or replace function fn_function()
returns table (
column01 integer, column02 integer, column03 boolean
) as $$
select col1, col2, (select max(col1) > 10 from t where col2 = tableXYZ.col1)
from tableXYZ;
$$ language sql;
Notice that it is not return but returns
To select from table, modify the results of select and pass them as results of a function try something like:
create or replace function fn_function()
returns table (
column01 integer, column02 integer, column03 boolean
) as $$
begin
for rec in select col1, col2, false as col3
from tableXYZ;
loop
rec.col3 := col1 > col2;
return next rec;
end loop;
return;
end
$$ language plpgsql;
Details here.
To select get the results of this function simply
SELECT function_alias.col1, function_alias.col2
FROM fn_function() function_alias;
You can do with this function same things you can do with normal tables.