POSTGRESQL - FUNCTION SELECT + UPDATE - postgresql

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.

Related

"INSERT INTO ... FETCH ALL FROM ..." can't be compiled

I have some function on PostgreSQL 9.6 returning a cursor (refcursor):
CREATE OR REPLACE FUNCTION public.test_returning_cursor()
RETURNS refcursor
IMMUTABLE
LANGUAGE plpgsql
AS $$
DECLARE
_ref refcursor = 'test_returning_cursor_ref1';
BEGIN
OPEN _ref FOR
SELECT 'a' :: text AS col1
UNION
SELECT 'b'
UNION
SELECT 'c';
RETURN _ref;
END
$$;
I need to write another function in which a temp table is created and all data from this refcursor are inserted to it. But INSERT INTO ... FETCH ALL FROM ... seems to be impossible. Such function can't be compiled:
CREATE OR REPLACE FUNCTION public.test_insert_from_cursor()
RETURNS table(col1 text)
IMMUTABLE
LANGUAGE plpgsql
AS $$
BEGIN
CREATE TEMP TABLE _temptable (
col1 text
) ON COMMIT DROP;
INSERT INTO _temptable (col1)
FETCH ALL FROM "test_returning_cursor_ref1";
RETURN QUERY
SELECT col1
FROM _temptable;
END
$$;
I know that I can use:
FOR _rec IN
FETCH ALL FROM "test_returning_cursor_ref1"
LOOP
INSERT INTO ...
END LOOP;
But is there better way?
Unfortunately, INSERT and SELECT don't have access to cursors as a whole.
To avoid expensive single-row INSERT, you could have intermediary functions with RETURNS TABLE and return the cursor as table with RETURN QUERY. See:
Return a query from a function?
CREATE OR REPLACE FUNCTION f_cursor1_to_tbl()
RETURNS TABLE (col1 text) AS
$func$
BEGIN
-- MOVE BACKWARD ALL FROM test_returning_cursor_ref1; -- optional, see below
RETURN QUERY
FETCH ALL FROM test_returning_cursor_ref1;
END
$func$ LANGUAGE plpgsql; -- not IMMUTABLE
Then create the temporary table(s) directly like:
CREATE TEMP TABLE t1 ON COMMIT DROP
AS SELECT * FROM f_cursor1_to_tbl();
See:
Creating temporary tables in SQL
Still not very elegant, but much faster than single-row INSERT.
Note: Since the source is a cursor only the first call succeeds. Executing the function a second time would return an empty set. You would need a cursor with the SCROLL option and move to the start for repeated calls.
This function does INSERT INTO from refcursor. It is universal for all the tables. The only requirement is that all columns of table corresponds to columns of refcursor by types and order (not necessary by names).
to_json() does the trick to convert any primitive data types to string with double-quotes "", which are later replaced with ''.
CREATE OR REPLACE FUNCTION public.insert_into_from_refcursor(_table_name text, _ref refcursor)
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
_sql text;
_sql_val text = '';
_row record;
_hasvalues boolean = FALSE;
BEGIN
LOOP --for each row
FETCH _ref INTO _row;
EXIT WHEN NOT found; --there are no rows more
_hasvalues = TRUE;
SELECT _sql_val || '
(' ||
STRING_AGG(val.value :: text, ',') ||
'),'
INTO _sql_val
FROM JSON_EACH(TO_JSON(_row)) val;
END LOOP;
_sql_val = REPLACE(_sql_val, '"', '''');
_sql_val = TRIM(TRAILING ',' FROM _sql_val);
_sql = '
INSERT INTO ' || _table_name || '
VALUES ' || _sql_val;
--RAISE NOTICE 'insert_into_from_refcursor(): SQL is: %', _sql;
IF _hasvalues THEN --to avoid error when trying to insert 0 values
EXECUTE (_sql);
END IF;
END;
$$;
Usage:
CREATE TABLE public.table1 (...);
PERFORM my_func_opening_refcursor();
PERFORM public.insert_into_from_refcursor('public.table1', 'name_of_refcursor_portal'::refcursor);
where my_func_opening_refcursor() contains
DECLARE
_ref refcursor = 'name_of_refcursor_portal';
OPEN _ref FOR
SELECT ...;

pass parameter and reference in an update function in 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;

PostgreSQL plpgsql - variable column names

I am creating a trigger, which uses dynamic names for columns
NEW.name:=2222; -- works fine !
but
dynamic_column:='name';
EXECUTE '$1.'||dynamic_column||':=2222 ' USING NEW; -- raises error
gives an error:
ERROR: syntax error at or near "$1" LINE 1: $1.name:=2222
I found info here: Assign to NEW by key in a Postgres trigger
If we enable the module hstore by:
CREATE EXTENSION hstore;
We can do this:
dynamic_column:='name';
temp_sql_string:='"'||dynamic_column||'"=>"2222"';
NEW := NEW #= temp_sql_string::hstore;
And the RECORD NEW.name now is set to the value 2222.
Thank you tough for making an effort to find a solution #Laurenz Albe
The problem is that this is not a valid SQL statement.
You can access the columns in new with dynamic SQL like this:
EXECUTE 'SELECT $1.id' INTO v_id USING NEW;
There is no comfortable way like that for changing individual columns in NEW.
You could use TG_RELID to get the OID of the table, query pg_attribute for the columns, compose a row literal string composed of the values in NEW and your new value, cast this to the table type and assign the result to NEW. Quite cumbersome.
Here is sample code that does that (I tested it, but there may be bugs left):
CREATE OR REPLACE FUNCTION dyntrig() RETURNS trigger
LANGUAGE plpgsql AS
$$DECLARE
colname text;
colval text;
newrow text := '';
fieldsep text := 'ROW(';
BEGIN
/* loop through the columns of the table */
FOR colname IN
SELECT attname
FROM pg_catalog.pg_attribute
WHERE attrelid = TG_RELID
AND attnum > 0
AND NOT attisdropped
ORDER BY attnum
LOOP
IF colname = 'name' THEN
colval = '2222';
ELSE
/* all other columns than 'name' retain their value */
EXECUTE 'SELECT CAST($1.' || quote_ident(colname) || ' AS text)'
INTO colval USING NEW;
END IF;
/* compose a string that represents the new table row */
IF colval IS NULL THEN
newrow := newrow || fieldsep || 'NULL';
ELSE
newrow := newrow || fieldsep || '''' || colval || '''';
END IF;
fieldsep := ',';
END LOOP;
newrow := newrow || ')';
/* assign the new table row to NEW */
EXECUTE 'SELECT (CAST(' || newrow || ' AS '
|| quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME)
|| ')).*'
INTO NEW;
RETURN NEW;
END;$$;
You already found my answer recommending the hstore operator #= on dba.SE. You may also be interested in the corresponding reference answer here on SO:
How to set value of composite variable field using dynamic SQL
Since you construct the auxiliary hstore value from variables I suggest the simple function hstore():
CREATE OR REPLACE FUNCTION dyn_trigger_func()
RETURNS TRIGGER AS
$func$
DECLARE
dyn_col_name text := 'name';
dyn_col_val text := '2222';
BEGIN
NEW := NEW #= hstore(dyn_col_name, dyn_col_val);
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
Faster / simpler / clearer / more secure this way.
Or, since it's obviously a trigger function, you may want to pass column name and value in CREATE TRIGGER statements:
CREATE OR REPLACE FUNCTION dyn_trigger_func()
RETURNS TRIGGER AS
$func$
BEGIN
NEW := NEW #= hstore(TG_ARGV[0], TG_ARGV[1]);
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
And:
CREATE TRIGGER ins_bef
BEFORE INSERT ON tbl
FOR EACH ROW EXECUTE PROCEDURE dyn_trigger_func('name', '2222');
Provide column name unquoted and case-sensitive.
Related:
Get values from varying columns in a generic trigger
Trigger with dynamic field name

Select a column default value into a variable in Pl/PgSQL

I'm trying to implement a generic trigger procedure to enable a sort a versioning scheme on tables. Tables all have version and current fields. On updates, in some situations based on a condition, i want to create a new version of a row instead of updating the old one. I'm having trouble getting the default value for the primary key field (always id).
Here's what i've done:
CREATE FUNCTION version_trigger() RETURNS trigger AS $$
DECLARE
id_default text;
id_value text;
BEGIN
IF version_condition() THEN
old.current = false;
-- I can read the default value
SELECT column_default INTO id_default
FROM information_schema.columns
WHERE table_name = TG_TABLE_NAME AND column_name = 'id';
-- THIS DOESN'T WORK!
EXECUTE 'SELECT $1' INTO id_value USING id_default;
new.id = id_value;
EXECUTE 'INSERT INTO ' || quote_ident(TG_TABLE_NAME) || ' SELECT ($1).*' USING new;
RETURN old;
END IF;
-- regular UPDATE
RETURN new;
END
$$ LANGUAGE plpgsql;
I'm just missing the step where i read the default value for the id (it's just a nextval() call). Can anyone help out on this?
Thanks in advance!
You cannot use a placeholder for expressions.
If DEFAULT doesn't have any reference to record data, then you can use EXECUTE statement, but little bit different
postgres=# DO $$
DECLARE x text; y text;
BEGIN
x := (SELECT column_default
FROM information_schema.columns
WHERE table_name = 'omega' AND column_name = 'a');
EXECUTE 'SELECT ' || x INTO y;
RAISE NOTICE '%', y;
END;
$$;
NOTICE: 2
DO

Postgres pl/pgsql ERROR: column "column_name" does not exist

i have a storerd procedure like below,
CREATE FUNCTION select_transactions3(text, text, int)
RETURNS SETOF transactions AS
$body$
DECLARE
rec transactions%ROWTYPE;
BEGIN
FOR rec IN (SELECT invoice_no, trans_date FROM transactions WHERE $1 = $2 limit $3 )
LOOP
RETURN NEXT rec;
END LOOP;
END;
$body$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
when i execute query like this :
select * from select_transactions3("invoice_no", '1103300105472',10);
or
select * from select_transactions3(invoice_no, '1103300105472',10);
it getting error like this :
ERROR: column "invoice_no" does not exist
but when i try execute with one colon like this :
select * from select_transactions3('invoice_no', '1103300105472',10);
the result is no row.
how i can get the data like this :
invoice_no | trans_date
---------------+-------------------------
1103300105472 | 2011-03-30 12:25:35.694
thanks .
UPDATE : If we want a certain column of table that we want to show
CREATE FUNCTION select_to_transactions14(_col character varying, _val character varying, _limit int)
RETURNS SETOF RECORD AS
$$
DECLARE
rec record;
BEGIN
FOR rec IN EXECUTE 'SELECT invoice_no, amount FROM transactions
WHERE ' || _col || ' = $1 LIMIT $2' USING _val, _limit LOOP
RETURN NEXT rec;
END LOOP;
END;
$$ LANGUAGE plpgsql;
to get the result :
SELECT * FROM select_to_transactions14( 'invoice_no', '1103300105472',1)
as ("invoice_no" varchar(125), "amount" numeric(12,2));
Your function could look like this:
CREATE FUNCTION select_transactions3(_col text, _val text, _limit int)
RETURNS SETOF transactions AS
$BODY$
BEGIN
RETURN QUERY EXECUTE '
SELECT *
FROM transactions
WHERE ' || quote_ident(_col) || ' = $1
LIMIT $2'
USING _val, _limit;
END;
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
IN PostgreSQL 9.1 or later that's simpler with format()
...
RETURN QUERY EXECUTE format('
SELECT *
FROM transactions
WHERE %I = $1
LIMIT $2', _col)
USING _val, _limit;
...
%I escapes identifiers like quote_ident().
Major points:
You were bumping into the limitation of dynamic SQL that you cannot use parameters for identifiers. You have to build the query string with the column name and then execute it.
You can do that with values though. I demonstrate the use of the USING clause for EXECUTE. Also note the use of quote_ident(): prevents SQL injection and certain syntax errors.
I also largely simplified your function. [RETURN QUERY EXECUTE][3] makes your code shorter and faster. No need to loop if all you do is return the row.
I use named IN parameters, so you don't get confused with the $-notation in the query string. $1 and $2 inside the query string refer to the values provided in the USING clause, not to the input parameters.
I change to SELECT * as you have to return the whole row to match the declared return type anyway.
Last but not least: Be sure to consider what the manual has to say about functions declared SECURITY DEFINER.
RETURN TYPE
If you don't want to return the whole row, one convenient possibility is:
CREATE FUNCTION select_transactions3(_col text, _val text, _limit int)
RETURNS TABLE (invoice_no varchar(125), amount numeric(12,2) AS ...
Then you don't have to provide a column definition list with every call and can simplify to:
SELECT * FROM select_to_transactions3('invoice_no', '1103300105472', 1);
You can query all databases from the server and sort them according to your own database.
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tableName';