Postgresql plpgsql multiple row loop - postgresql

I'm busy trying to rewrite an Informix stored procedure for a PostgreSQL
database and I am stuck on something that is probably quite obvious to
everyone who know PostgreSQL.
I have my sql script as follows
-- ensure type and function get created
drop type if exists tp_users cascade;
drop function if exists sp_cmplist();
-- create type
create type tp_users as (
us_id char(30),
us_status char(1)
);
create function sp_cmplist()
returns tp_users as $$
declare
lr_users tp_users;
begin
for lr_users in
select users.us_id, users.us_status
from users
loop
return lr_users;
end loop;
end
$$ language 'plpgsql';
select sp_cmplist();
this is just a dummy script to select from an imaginary users table but how would I use this script with a cursor or loop to make sure all results are returned?

This code works:
CREATE TABLE foo(a int);
INSERT INTO foo VALUES(10),(20);
CREATE OR REPLACE FUNCTION retfoo()
RETURNS SETOF foo AS $$
BEGIN
RETURN QUERY SELECT * FROM foo;
RETURN;
END;
$$ LANGUAGE plpgsql;
postgres=# SELECT * FROM retfoo();
┌────┐
│ a │
├────┤
│ 10 │
│ 20 │
└────┘
(2 rows)
Time: 1.143 ms

I may have answered my own question with the following
drop type if exists tp_users cascade;
drop function if exists sp_cmplist();
create type tp_users as (
us_id text,
us_status text,
lv_nothing text,
lv_cnt int
);
create function sp_cmplist()
returns setof tp_users as $$
declare
lr_users tp_users;
lv_cnt int;
begin
lv_cnt := 0;
for lr_users in
select users.us_id, users.us_status
from users
loop
-- increment this counter for testing purposes
lv_cnt := lv_cnt + 1;
lr_users.lv_nothing := 'yupy';
lr_users.lv_cnt := lv_cnt;
return next lr_users;
end loop;
return;
end
$$ language 'plpgsql';
select * from sp_cmplist();
this seems to work perfectly

Related

Triggers in Postgres: Access NEW fields by name at runtime

In Postgres, someone knows how to substitute the value of the variable in a NEW.variable in a trigger?
For instance, I have a variable with value order_code. I want to execute NEW.variable so that it's getting in fact NEW.order_code.
In detailed:
I have a function to obtain the primary key column of a table:
CREATE FUNCTION getPrimaryKey(_table_name VARCHAR(50))
RETURNS SETOF VARCHAR(50) AS $$
DECLARE
primary_key VARCHAR(50);
BEGIN
FOR primary_key IN SELECT a.attname
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = ANY(i.indkey)
WHERE i.indrelid = _table_name::regclass
AND i.indisprimary LOOP
RETURN NEXT primary_key;
END LOOP;
END;
$$ LANGUAGE plpgsql;
Then I have a trigger to collect some info when an INSERT is done in a table. The procedure in the trigger is called from several triggers from different tables. That's why it's so generic and I have this need.
What I want is to obtain the primary key of the object inserted.
CREATE FUNCTION logAudit()
RETURNS trigger AS $$
DECLARE primary_key VARCHAR(50);
BEGIN
primary_key := getprimarykey(TG_TABLE_NAME::VARCHAR(50));
INSERT INTO test VALUES (TG_TABLE_NAME);
INSERT INTO test VALUES (NEW.primary_key);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER audit_in_client
AFTER INSERT ON tb_client
FOR EACH STATEMENT EXECUTE PROCEDURE logAudit();
The NEW.primary_key is what is causing me issues. I expect primary_key to be the column name of the source table where the insert happened. What I want in NEW.primary_key is to actually use the value in the variable.
Here is the example of anonymous pl/pgsql block which doing something what you want:
do $$
declare
v pg_database = (pg_database) from pg_database where datname = 'template1';
fname text = 'datname';
n text;
begin
n := to_jsonb(v)->>fname;
raise info '%', n;
end $$;
Output:
INFO: template1
It is working example. In your trigger function it could be something like
declare
pk_name text;
pk_value text;
begin
pk_name := getprimarykey(TG_TABLE_NAME::VARCHAR(50));
pk_value := to_jsonb(NEW) ->> pk_name;
-- Do what you want with pk_value here
return null;
end $$;

Query returned successfully but i dont see anyway

I'm new to postgresql, i have a procedure block this is working but i dont see any table or column, what this problem.
CREATE PROCEDURE list3(
)
LANGUAGE sql
AS $$
select * from stok
$$;
call list3()
Functions return values.
Example:
create table table_example(col1 varchar(100),col2 int);
CREATE OR REPLACE FUNCTION get_table_example()
RETURNS TABLE (
l_col1 VARCHAR,
l_col2 INT
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT *
FROM table_example;
END;
$$
select * from get_table_example();

Postgresql query across different tables with dynamic query

I'm trying to get a customer id which can be placed in one of ten different tables. I don't want to hard code those table names to find it so I tried postgresql function as follows.
create or replace FUNCTION test() RETURNS SETOF RECORD AS $$
DECLARE
rec record;
BEGIN
select id from schema.table_0201_0228 limit 1 into rec;
return next rec;
select id from schema.table_0301_0331 limit 1 into rec;
return next rec;
END $$ language plpgsql;
select * from test() as (id int)
As I'm not familiar with postgresql function usage, how can I improve the code to replace 'schema.table1' with a variable, loop each table and return the result?
NOTE: table names may change overtime. For example, table_0201_0228 and table_0301_0331 are for February and March respectively.
You need dynamic SQL for that:
create or replace FUNCTION test(p_schema text)
RETURNS table(id int)
AS $$
DECLARE
l_tab record;
l_sql text;
BEGIN
for l_tab in (select schemaname, tablename
from pg_tables
where schemaname = p_schema)
loop
l_sql := format('select id from %I.%I limit 1', l_tab.schemaname, l_tab.tablename);
return query execute l_sql;
end loop;
END $$
language plpgsql;
I made the schema name a parameter, but of course you can hard-code it. As the function is defined as returns table there is no need to specify the column name when using it:
select *
from test('some_schema');

Pl/Pgsql, Passing array argument to INSERT

Say I have a function with a text array parameter TEXT[]. If I do a EXECUTE FORMAT INSERT, how do I pass a quoted text string of that array to insert?
You should to use USING clause. The dynamic SQL can use a parameters on usual places (non SQL identifiers):
CREATE TABLE foo(a varchar[]);
CREATE OR REPLACE FUNCTION public.fx(tblname text, VARIADIC p character varying[])
RETURNS void LANGUAGE plpgsql AS $function$
BEGIN
EXECUTE format('insert into %I(a) VALUES($1)', tblname) USING p;
END;
$function$
SELECT fx('foo', 'Hi','Hello');
SELECT fx('foo', 'Hi','Hel''lo');
SELECT fx('foo', 'Hi','Hel"lo');
postgres=# SELECT * FROM foo;
┌────────────────┐
│ a │
╞════════════════╡
│ {Hi,Hel'lo} │
│ {Hi,"Hel\"lo"} │
│ {Hi,Hello} │
└────────────────┘
(3 rows)
Thank you, I now know when to use using, and format. Here's my revised code:
CREATE OR REPLACE FUNCTION add_property(catid INT, colname TEXT,
ty catalog_column_type, colval TEXT[])
RETURNS jsonb AS $$
DECLARE
tn TEXT;
BEGIN
--check table exists
SELECT tablename INTO tn FROM catalog WHERE catalogid=catid;
IF NOT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name=tn)
THEN
return jsonb_build_object('error','notable');
END IF;
--check if property exists for table
IF EXISTS(SELECT 1 FROM catalog_columns WHERE catalogid=catid AND
columnname=colname) THEN
return jsonb_build_object('error','exists');
END IF;
IF ty='INT'::catalog_column_type THEN
EXECUTE FORMAT ('ALTER TABLE %I ADD COLUMN %I INT',tn,colname);
ELSIF ty='TEXT'::catalog_column_type THEN
EXECUTE FORMAT ('ALTER TABLE %I ADD COLUMN %I TEXT',tn,colname);
ELSIF ty='ENUM'::catalog_column_type THEN
EXECUTE FORMAT ('ALTER TABLE %I ADD COLUMN %I INT',tn,colname);
ELSIF ty='BOOLEAN'::catalog_column_type THEN
EXECUTE FORMAT ('ALTER TABLE %I ADD COLUMN %I BOOLEAN',tn,colname);
END IF;
EXECUTE 'INSERT INTO catalog_columns (catalogid,columnname,'
|| 'columntype,columnnvalues) VALUES ($1,$2,$3,$4)' USING catid,colname,
ty,colval;
return jsonb_build_object('error','OK');
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