"cannot assign non-composite value to a record variable " while storing result in array - postgresql

on Postgres 13 I have a trigger execute ON UPDATE of a table. In this trigger I want to store in an array a result from a query, because I will need to use it inside an inner iterator, multiple times. In this way I avoid performing the same query at every iteration and I can reuse the array.
At first I tried like this, I typed the variable as array record:
create or REPLACE FUNCTION check_companyprofile_between_records_constraints_trigger_hook()
RETURNS trigger as
$body$
declare
l_table1_results record[];
l_table1_result record;
l_record record;
begin
l_table1_results := ARRAY(
select
date_start,
coalesce(date_end, 'infinity'::date) as date_end
FROM application.section_profile_company_metadata
WHERE persona_id = NEW.persona_id and id != new.id
);
for l_record in (
select foo,bar,baz
from table2
)
loop
foreach l_table1_result in array l_table1_results
loop
-- Check some stuffs
end loop;
end loop;
RETURN NEW;
end
$body$
LANGUAGE plpgsql;
But I'm getting a
Errore SQL [0A000]: ERROR: variable "l_table1_results" has pseudo-type record[]
Doing some research I discovered it is not possible to assign an in-memory variable with the result of a query, but instead I need to use a custom type. Ok, so I tried also this
create type apc_dates_pair as (
date_start date,
date_end date
);
create or REPLACE FUNCTION check_companyprofile_between_records_constraints_trigger_hook()
RETURNS trigger as
$body$
declare
l_table1_results apc_dates_pair[];
l_table1_result record;
l_record record;
begin
l_table1_results := ARRAY(
select
date_start,
coalesce(date_end, 'infinity'::date) as date_end
FROM application.section_profile_company_metadata
WHERE persona_id = NEW.persona_id and id != new.id
);
-- TODO do some stuffs with array
for l_record in (
select foo,bar,baz
from table2
)
loop
foreach l_table1_result in l_table1_results
loop
-- Check some stuffs
end loop;
end loop;
RETURN NEW;
end
$body$
LANGUAGE plpgsql;
create or REPLACE FUNCTION check_companyprofile_between_records_constraints_trigger_hook()
RETURNS trigger as
$body$
declare
l_table1_results apc_dates_pair[];
l_table1_result record;
l_record record;
begin
l_table1_results := ARRAY(
select row(
date_start,
coalesce(date_end, 'infinity'::date)
)
FROM application.section_profile_company_metadata
WHERE persona_id = NEW.persona_id and id != new.id
);
for l_record in (
select foo,bar,baz
from table2
)
loop
foreach l_table1_result in array l_table1_results
loop
-- Check some stuffs
end loop;
end loop;
RETURN NEW;
end
$body$
LANGUAGE plpgsql;
Different error, but still an error:
cannot assign non-composite value to a record variable
And from this I did not find much.
Is it possible at all to store a temp query inside and array of records and then iterate them?

An explanation more then an answer:
You seem to be confusing composite types with arrays, they are different things in Postgres. Most notably a Postgres Array contains values of a single type. You can have int[] all integers, varchar[] all characters, and so on, but you cannot mix types in a single array. A Composite Type on the other hand can do just that, have included values that are of different types. In plpgsql declaring a record is to basically declare an anonymous composite type that acquires the properties of whatever actual composite type is passed to it. This is why you get your second error; "cannot assign non-composite value to a record variable". You are trying to assign an array to a composite type, in this case a record. Given that the values you want to store are both dates you could dispense with create type apc_dates_pair ... and just do l_table1_results date[]. Then iterate over the array using the form shown at Array loop.

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.

In clause in postgres

Need Output from table with in clause in PostgreSQL
I tried to make loop or ids passed from my code. I did same to update the rows dynamically, but for select I m not getting values from DB
CREATE OR REPLACE FUNCTION dashboard.rspgetpendingdispatchbyaccountgroupidandbranchid(
IN accountgroupIdCol numeric(8,0),
IN branchidcol character varying
)
RETURNS void
AS
$$
DECLARE
ArrayText text[];
i int;
BEGIN
select string_to_array(branchidcol, ',') into ArrayText;
i := 1;
loop
if i > array_upper(ArrayText, 1) then
exit;
else
SELECT
pd.branchid,pd.totallr,pd.totalarticle,pd.totalweight,
pd.totalamount
FROM dashboard.pendingdispatch AS pd
WHERE
pd.accountgroupid = accountgroupIdCol AND pd.branchid IN(ArrayText[i]::numeric);
i := i + 1;
end if;
END LOOP;
END;
$$ LANGUAGE 'plpgsql' VOLATILE;
There is no need for a loop (or PL/pgSQL actually)
You can use the array directly in the query, e.g.:
where pd.branchid = any (string_to_array(branchidcol, ','));
But your function does not return anything, so obviously you won't get a result.
If you want to return the result of that SELECT query, you need to define the function as returns table (...) and then use return query - or even better make it a SQL function:
CREATE OR REPLACE FUNCTION dashboard.rspgetpendingdispatchbyaccountgroupidandbranchid(
IN accountgroupIdCol numeric(8,0),
IN branchidcol character varying )
RETURNS table(branchid integer, totallr integer, totalarticle integer, totalweight numeric, totalamount integer)
AS
$$
SELECT pd.branchid,pd.totallr,pd.totalarticle,pd.totalweight, pd.totalamount
FROM dashboard.pendingdispatch AS pd
WHERE pd.accountgroupid = accountgroupIdCol
AND pd.branchid = any (string_to_array(branchidcol, ',')::numeric[]);
$$
LANGUAGE sql
VOLATILE;
Note that I guessed the data types for the columns of the query based on their names. You have to adjust the line with returns table (...) to match the data types of the select columns.

Syntax error at or near "unnest"

This request:
unnest('{1,2}'::int[]);
gives to me this error:
syntax error at or near "unnest"
neither unnest('{1,2}'); works
Why?
intire:
CREATE OR REPLACE FUNCTION result() RETURNS setof users AS
$$
DECLARE
BEGIN
unnest('{1,2}'::int[]);
RETURN QUERY SELECT * FROM users;
END;
$$ LANGUAGE plpgsql;
SELECT result();
EDIT
The core idea:
To retrive and manipualate with the bigint[] which is stored inside in a column.
So, i have got this:
SELECT * FROM users WHERE email = email_ LIMIT 1 INTO usr;
Then, usr.chain contains some bigint[] data. For example, {1,2,3,4,5,6,7,8,9,10}. I want to save only the 4 last of them.
How to retrieve {7,8,9,10} and {1,2,3,4,5,6} and iterate over these arrays?
I only found the solution is to use SELECT FROM unnest(usr.chain) AS x ORDER BY x ASC LIMIT (sdl - mdl) OFFSET mchain and so on. but unnest function gives to me this stupid error. I'm really do not understand why it happends. It doesn't work in sucj easy case I wrote at the beginning of the question. subarray function doesn't work because of the data type is bigint[] not int[]
Futher more, the code unnest(ARRAY[1,2]) gives to me the same error.
http://www.postgresql.org/docs/9.2/static/functions-array.html
The same error for array_append function
to iterate over array:
CREATE OR REPLACE FUNCTION someresult(somearr bigint[] ) RETURNS setof bigint AS
$$
DECLARE
i integer;
x bigint;
BEGIN
for x in select unnest($1)
loop
-- do something
return next x;
end loop;
-- or
FOR i IN array_lower($1, 1) .. array_upper($1, 1)
LOOP
-- do something like:
return next ($1)[i];
end loop;
END;
$$ LANGUAGE plpgsql;
select someresult('{1,2,3,4}') ;
array_append ....
CREATE OR REPLACE FUNCTION someresult2(somearr bigint[],val bigint ) RETURNS bigint[] AS
$$
DECLARE
somenew_arr bigint[];
BEGIN
somenew_arr = array_append($1, $2 );
return somenew_arr;
END;
$$ LANGUAGE plpgsql;
select someresult2('{1,2,3,4}' ,222) ;
so, here you have basic example how to iterate and append arrays. Now can you write step by step what you want to do, to achieve .

I am trying to unnet an array in other to query the postgres DB

I am call the function but it is returning error that array value must start with "{" or dimension information using
Create or Replace Function get_post_process_info(IN v_esdt_pp character varying[])
Returns setof Record as
$$
Declare
post_processes RECORD;
esdt_value character varying;
v_sdsname character varying[];
v_dimension character varying[];
counter int := 1;
Begin
-- to loop through the array and get the values for the esdt_values
FOR esdt_value IN select * from unnest(v_esdt_pp)
LOOP
-- esdt_values as a key for the multi-dimensional arrays and also as the where clause value
SELECT distinct on ("SdsName") "SdsName" into v_sdsname from "Collection_ESDT_SDS_Def" where "ESDT" = esdt_values;
raise notice'esdt_value: %',esdt_value;
END LOOP;
Return ;
End
$$ Language plpgsql;
Select get_post_process_info(array['ab','bc]);
Your function sanitized:
CREATE OR REPLACE FUNCTION get_post_process_info(v_esdt_pp text[])
RETURNS SETOF record AS
$func$
DECLARE
esdt_value text;
v_sdsname text[];
v_dimension text[];
counter int := 1;
BEGIN
FOR esdt_value IN
SELECT * FROM unnest(v_esdt_pp) t
LOOP
SELECT distinct "SdsName" INTO v_sdsname
FROM "Collection_ESDT_SDS_Def"
WHERE "ESDT" = esdt_value;
RAISE NOTICE 'esdt_value: %', esdt_value;
END LOOP;
END
$func$ Language plpgsql;
Call:
Select get_post_process_info('{ab,bc}'::text[]);
DISTINCT instead of DISTINCT ON, missing table alias, formatting, some cruft, ...
Finally the immediate cause of the error: a missing quote in the call.
The whole shebang can possibly be replaced with a single SQL statement.
But, obviously, your function is incomplete. Nothing is returned yet. Information is missing.

Create a function to get column from multiple tables in PostgreSQL

I'm trying to create a function to get a field value from multiple tables in my database. I made script like this:
CREATE OR REPLACE FUNCTION get_all_changes() RETURNS SETOF RECORD AS
$$
DECLARE
tblname VARCHAR;
tblrow RECORD;
row RECORD;
BEGIN
FOR tblrow IN SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname='public' LOOP /*FOREACH tblname IN ARRAY $1 LOOP*/
RAISE NOTICE 'r: %', tblrow.tablename;
FOR row IN SELECT MAX("lastUpdate") FROM tblrow.tablename LOOP
RETURN NEXT row;
END LOOP;
END LOOP;
END
$$
LANGUAGE 'plpgsql' ;
SELECT get_all_changes();
But it is not working, everytime it shows this error
tblrow.tablename" not defined in line "FOR row IN SELECT MAX("lastUpdate") FROM tblrow.tablename LOOP"
Your inner FOR loop must use the FOR...EXECUTE syntax as shown in the manual:
FOR target IN EXECUTE text_expression [ USING expression [, ... ] ] LOOP
statements
END LOOP [ label ];
In your case something along this line:
FOR row IN EXECUTE 'SELECT MAX("lastUpdate") FROM ' || quote_ident(tblrow.tablename) LOOP
RETURN NEXT row;
END LOOP
The reason for this is explained in the manual somewhere else:
Oftentimes you will want to generate dynamic commands inside your PL/pgSQL functions, that is, commands that will involve different tables or different data types each time they are executed. PL/pgSQL's normal attempts to cache plans for commands (as discussed in Section 39.10.2) will not work in such scenarios. To handle this sort of problem, the EXECUTE statement is provided[...]
Answer to your new question (mislabeled as answer):
This can be much simpler. You do not need to create a table just do define a record type.
If at all, you would better create a type with CREATE TYPE, but that's only efficient if you need the type in multiple places. For just a single function, you can use RETURNS TABLE instead :
CREATE OR REPLACE FUNCTION get_all_changes(text[])
RETURNS TABLE (tablename text
,"lastUpdate" timestamp with time zone
,nums integer) AS
$func$
DECLARE
tblname text;
BEGIN
FOREACH tblname IN ARRAY $1 LOOP
RETURN QUERY EXECUTE format(
$f$SELECT '%I', MAX("lastUpdate"), COUNT(*)::int FROM %1$I
$f$, tblname)
END LOOP;
END
$func$ LANGUAGE plpgsql;
A couple more points:
Use RETURN QUERY EXECUTE instead of the nested loop. Much simpler and faster.
Column aliases would only serve as documentation, those names are discarded in favor of the names declared in the RETURNS clause (directly or indirectly).
Use format() with %I to replace the concatenation with quote_ident() and %1$I to refer to the same parameter another time.
count() usually returns type bigint. Cast the integer, since you defined the column in the return type as such: count(*)::int.
Thanks,
I finally made my script like:
CREATE TABLE IF NOT EXISTS __rsdb_changes (tablename text,"lastUpdate" timestamp with time zone, nums bigint);
CREATE OR REPLACE FUNCTION get_all_changes(varchar[]) RETURNS SETOF __rsdb_changes AS /*TABLE (tablename varchar(40),"lastUpdate" timestamp with time zone, nums integer)*/
$$
DECLARE
tblname VARCHAR;
tblrow RECORD;
row RECORD;
BEGIN
FOREACH tblname IN ARRAY $1 LOOP
/*RAISE NOTICE 'r: %', tblrow.tablename;*/
FOR row IN EXECUTE 'SELECT CONCAT('''|| quote_ident(tblname) ||''') AS tablename, MAX("lastUpdate") AS "lastUpdate",COUNT(*) AS nums FROM ' || quote_ident(tblname) LOOP
/*RAISE NOTICE 'row.tablename: %',row.tablename;*/
/*RAISE NOTICE 'row.lastUpdate: %',row."lastUpdate";*/
/*RAISE NOTICE 'row.nums: %',row.nums;*/
RETURN NEXT row;
END LOOP;
END LOOP;
RETURN;
END
$$
LANGUAGE 'plpgsql' ;
Well, it works. But it seems I can only create a table to define the return structure instead of just RETURNS SETOF RECORD. Am I right?
Thanks again.