pass parameter and reference in an update function in postgresql - postgresql

So I have a function select_id_from_table(_t). It chooses certain column of the table (_t) where _t is a table name as a parameter.
I call it like SELECT select_id_from_table('tablename').Now I want to create another function where the function does something like this:
CREATE OR REPLACE FUNCTION myfunction(_u type1, _t type2) returns void as $$
BEGIN
UPDATE (_u) set score=score+1 where _u.id in _t.id;
END;
$$ LANGUAGE plpgsql
The problem is it can not pass the parameter properly. And also what should type1 and type2 be? _u and _t are both names of tables. I have tried:
$$begin
create temp table lid as (select * from select_id_from_table(_t));
execute format ('update '||quote_ident(_u) ||' set score= score+1 where
'||quote_ident(_u) ||'.id_ in (
select * from select_id_from_table ('||quote_ident(_t)||') as
abc )');
end;$$
I also tried creating a temp table, select select_id_from_table(_t)into that temp table and make reference of it later. But I still don't know how to quote it in execute format(''). Any ideas would be appreciated.

I assume that select_id_from_table is a function returning SETOF <something> and that the return type can be cast to text.
I didn't test it, but I would do it similar to this:
DECLARE
in_clause text;
BEGIN
SELECT string_agg(quote_literal(t::text), ', ') INTO in_clause
FROM select_id_from_table(_u) s(t);
EXECUTE format ('UPDATE %I
SET score = score + 1
WHERE %I.id_ =ANY (' || in_clause || ')',
_u, _u);
END;

Related

Declare a Table as a variable in a stored procedure?

I am currently working a stored procedure capable of detecting continuity on a specific set of entries..
The specific set of entries is extracted from a sql query
The function takes in two input parameter, first being the table that should be investigated, and the other being the list of ids which should be evaluated.
For every Id I need to investigate every row provided by the select statement.
DROP FUNCTION IF EXISTS GapAndOverlapDetection(table_name text, entity_ids bigint[]);
create or replace function GapAndOverlapDetection ( table_name text, enteity_ids bigint[] )
returns table ( entity_id bigint, valid tsrange, causes_overlap boolean, causes_gap boolean)
as $$
declare
x bigint;
var_r record;
begin
FOREACH x in array $2
loop
EXECUTE format('select entity_id, valid from' ||table_name|| '
where entity_id = '||x||'
and registration #> now()::timestamp
order by valid ASC') INTO result;
for var_r in result
loop
end loop;
end loop ;
end
$$ language plpgsql;
select * from GapAndOverlapDetection('temp_country_registration', '{1,2,3,4}')
I currently get an error in the for statement saying
ERROR: syntax error at or near "$1"
LINE 12: for var_r in select entity_id, valid from $1
You can iterate over the result of the dynamic query directly:
create or replace function gapandoverlapdetection ( table_name text, entity_ids bigint[])
returns table (entity_id bigint, valid tsrange, causes_overlap boolean, causes_gap boolean)
as $$
declare
var_r record;
begin
for var_r in EXECUTE format('select entity_id, valid
from %I
where entity_id = any($1)
and registration > now()::timestamp
order by valid ASC', table_name)
using entity_ids
loop
... do something with var_r
-- return a row for the result
-- this does not end the function
-- it just appends this row to the result
return query
select entity_id, true, false;
end loop;
end
$$ language plpgsql;
The %I injects an identifier into a string and the $1 inside the dynamic SQL is then populated through passing the argument with the using keyword
Firstly, decide whether you want to pass the table's name or oid. If you want to identify the table by name, then the parameter should be of text type and not regclass.
Secondly, if you want the table name to change between executions then you need to execute the SQL statement dynamically with the EXECUTE statement.

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.

POSTGRESQL - FUNCTION SELECT + UPDATE

I need execute update for each return of the select, but I don't know how I can do it.
In firebird I have this code:
BEGIN
FOR
SELECT data_cadastro || ' ' || hora_cadastro as data_hora_cadastro,codigo_alteracao_convenio
FROM CC_ALTERACAO_CONVENIO
INTO:data_hora_cadastro,codigo_alteracao_convenio
DO
BEGIN
update CC_ALTERACAO_CONVENIO
set data_hora_cadastro = :data_hora_cadastro
where codigo_alteracao_convenio = :codigo_alteracao_convenio;
suspend;
END
END
I want change to function in postgresql.
I tried this, but not work because I don't know the syntax of postgresql of how can I do it.
CREATE OR REPLACE FUNCTION sim.ajuste_alteracao_convenio(OUT data_hora_cadastro character varying, OUT codigo_alteracao_convenio integer)
RETURNS SETOF record AS
$BODY$
DECLARE
v_data_hora_cadastro character varying;
v_codigo_alteracao_convenio INTEGER;
BEGIN
RETURN QUERY
SELECT data_cadastro || ' ' || hora_cadastro as data_hora_cadastro,codigo_alteracao_convenio
FROM sim.CC_ALTERACAO_CONVENIO
--loop
BEGIN
update sim.CC_ALTERACAO_CONVENIO
set data_hora_cadastro = v_data_hora_cadastro
where codigo_alteracao_convenio = v_codigo_alteracao_convenio;
END
--END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
Could someone give me a direction of how can I solved this?
SOLVED
create type foo as (
data_hora_cadastro timestamp,
codigo_alteracao_convenio integer
)
CREATE OR REPLACE FUNCTION sim.ajuste_alteracao_convenio3()
RETURNS SETOF foo AS
$BODY$
DECLARE
r foo%rowtype;
BEGIN
FOR r IN SELECT data_cadastro || ' ' || hora_cadastro as data_hora_cadastro,codigo_alteracao_convenio FROM sim.CC_ALTERACAO_CONVENIO
LOOP
update sim.CC_ALTERACAO_CONVENIO
set data_hora_cadastro = r.data_hora_cadastro
where codigo_alteracao_convenio = r.codigo_alteracao_convenio;
RETURN NEXT r; -- return current row of SELECT
END LOOP;
RETURN;
END
$BODY$
LANGUAGE 'plpgsql' ;
Thank you all
Information is missing in the question, but it looks like all you need is a simple UPDATE with RETURNING:
UPDATE sim.cc_alteracao_convenio a
SET data_hora_cadastro = a.data_cadastro + a.hora_cadastro
RETURNING a.data_hora_cadastro, a.codigo_alteracao_convenio;
Assuming data_cadastro is data type date and hora_cadastro is data type time. Currently, you convert both to text, concatenate and cast back to timestamp. That's much more expensive than it needs to be. Just add both together: data_cadastro + hora_cadastro
The UPDATE itself looks like you are storing functionally dependent values redundantly. Once you've updated data_hora_cadastro you can drop data_cadastro and hora_cadastrocan.
If you positively need a function:
CREATE OR REPLACE FUNCTION sim.ajuste_alteracao_convenio3()
RETURNS TABLE (data_hora_cadastro timestamp
, codigo_alteracao_convenio integer) AS
$func$
UPDATE sim.cc_alteracao_convenio a
SET data_hora_cadastro = a.data_cadastro + a.hora_cadastro
RETURNING a.data_hora_cadastro, a.codigo_alteracao_convenio;
$func$ LANGUAGE sql; -- never quote the language name
You don't need to create a composite type, just use RETURNS TABLE() instead.
Or, if you need pre-UPDATE values:
Return pre-UPDATE Column Values Using SQL Only - PostgreSQL Version
this is what I do, it's an idea but may be it inspires you:
WITH insusu AS (
SELECT data_cadastro || ' ' || hora_cadastro
as data_hora_cadastro,codigo_alteracao_convenio
FROM sim.CC_ALTERACAO_CONVENIO
RETURNING id
)
update sim.CC_ALTERACAO_CONVENIO
set data_hora_cadastro =:data_hora_cadastro
from insusu;
select *
from insusu;
The Objetive is uses a with to determine with what data need work.

Only perform update if column exists

Is it possible to execute an update conditionally if a column exists?
For instance, I may have a column in a table and if that column exists I want that update executed, otherwise, just skip it (or catch its exception).
You can do it inside a function. If you don't want to use the function later you can just drop it afterwards.
To know if a column exists in a certain table, you can try to fetch it using a select(or a perform, if you're gonna discard the result) in information_schema.columns.
The query bellow creates a function that searches for a column bar in a table foo, and if it finds it, updates its value. Later the function is run, then droped.
create function conditional_update() returns void as
$$
begin
perform column_name from information_schema.columns where table_name= 'foo' and column_name = 'bar';
if found then
update foo set bar = 12345;
end if;
end;
$$ language plpgsql;
select conditional_update();
drop function conditional_update();
With the following table as example :
CREATE TABLE mytable (
idx INT
,idy INT
);
insert into mytable values (1,2),(3,4),(5,6);
you can create a custom function like below to update:
create or replace function fn_upd_if_col_exists(_col text,_tbl text,_val int) returns void as
$$
begin
If exists (select 1
from information_schema.columns
where table_schema='public' and table_name=''||_tbl||'' and column_name=''||_col||'' ) then
execute format('update mytable set '||_col||'='||_val||'');
raise notice 'updated';
else
raise notice 'column %s doesn''t exists on table %s',_col,_tbl;
end if;
end;
$$
language plpgsql
and you can call this function like:
select fn_upd_if_col_exists1('idz','mytable',111) -- won't update raise "NOTICE: column idz deosnt exists on table mytables"
select fn_upd_if_col_exists1('idx','mytable',111) --will upadate column idx with value 1111 "NOTICE: updated"

EXECUTE...USING statement in PL/pgSQL doesn't work with record type?

I'm trying to write a function in PL/PgSQL that have to work with a table it receives as a parameter.
I use EXECUTE..INTO..USING statements within the function definition to build dynamic queries (it's the only way I know to do this) but ... I encountered a problem with RECORD data types.
Let's consider the follow (extremely simplified) example.
-- A table with some values.
DROP TABLE IF EXISTS table1;
CREATE TABLE table1 (
code INT,
descr TEXT
);
INSERT INTO table1 VALUES ('1','a');
INSERT INTO table1 VALUES ('2','b');
-- The function code.
DROP FUNCTION IF EXISTS foo (TEXT);
CREATE FUNCTION foo (tbl_name TEXT) RETURNS VOID AS $$
DECLARE
r RECORD;
d TEXT;
BEGIN
FOR r IN
EXECUTE 'SELECT * FROM ' || tbl_name
LOOP
--SELECT r.descr INTO d; --IT WORK
EXECUTE 'SELECT ($1)' || '.descr' INTO d USING r; --IT DOES NOT WORK
RAISE NOTICE '%', d;
END LOOP;
END;
$$ LANGUAGE plpgsql STRICT;
-- Call foo function on table1
SELECT foo('table1');
It output the following error:
ERROR: could not identify column "descr" in record data type
although the syntax I used seems valid to me. I can't use the static select (commented in the example) because I want to dinamically refer the columns names.
So..someone know what's wrong with the above code?
It's true. You cannot to use type record outside PL/pgSQL space.
RECORD value is valid only in plpgsql.
you can do
EXECUTE 'SELECT $1.descr' INTO d USING r::text::xx;
$1 should be inside the || ,like || $1 || and give spaces properly then it will work.
BEGIN
EXECUTE ' delete from ' || quote_ident($1) || ' where condition ';
END;