PL/pgSQL: accessing fields of an element of an array of custom type - postgresql

I've defined a custom type:
create type pg_temp.MYTYPE as (f1 int, f2 text);
Then, inside a function or a block, I've declared an array of such type:
declare ax MYTYPE[];
I can access an element through the familiar syntax ax[1].f1, but just for reading it.
When I use the same syntax for setting the field, I get a syntax error.
create type pg_temp.MYTYPE as (f1 int, f2 text);
do $$
declare x MYTYPE;
declare ax MYTYPE[];
declare f int;
begin
x.f1 = 10;
x.f2 = 'hello';
--assigning an element: OK
ax[1] = x;
--reading an element's field: OK
f = ax[1].f1;
--writing an elememt's field: SYNTAX ERROR:
ax[1].f1 = f;
end; $$
The error is the following:
psql:test.sql:28: ERROR: syntax error at or near "."
LINE 20: ax[1].f1 = f;
^
I've tried also the syntax (ax[1]).f1 with the same result.
Which is the correct syntax to do this?
Postgres server version: 9.2.2

PLpgSQL is sometimes very simple, maybe too simple. The left part of assignment statement is a example - on the left can be variable, record field, or array field only. Any complex left part expression are not supported.
You need a auxiliary variable of array element type.
DECLARE aux MTYPE;
aux := ax[1];
aux.f := 100000;
ax[1] := aux;

That seems particularly inconsequent of plpgsql since SQL itself can very well update a field of a composite type inside an array.
Demo:
CREATE TEMP TABLE mytype (f1 int, f2 text);
CREATE TEMP TABLE mycomp (c mytype);
INSERT INTO mycomp VALUES ('(10,hello)');
UPDATE mycomp
SET c.f1 = 12 -- note: without parentheses
WHERE (c).f1 = 10; -- note: with parentheses
TABLE mycomp;
c
------------
(12,hello)
CREATE TEMP TABLE mycomparr (ca mytype[]);
INSERT INTO mycomparr VALUES ('{"(10,hello)"}');
UPDATE mycomparr
SET ca[1].f1 = 12 -- works!
WHERE (ca[1]).f1 = 10;
TABLE mycomparr;
ca
----------------
{"(12,hello)"}
I wonder why plpgsql does not implement the same?
Be that as it may, another possible workaround would be to use UPDATE on a temporary table:
DO
$$
DECLARE
ax mytype[] := '{"(10,hello)"}';
BEGIN
CREATE TEMP TABLE tmp_ax ON COMMIT DROP AS SELECT ax;
UPDATE tmp_ax SET ax[1].f1 = 12;
-- WHERE (ax[1]).f1 = 10; -- not necessary while only 1 row.
SELECT t.ax INTO ax FROM tmp_ax t; -- table-qualify column!
RAISE NOTICE '%', ax;
END
$$;
That's more overhead than with #Pavel's simpler workaround. I would not use it for the simple case. But if you have lots of assignments it might still pay to use the temporary table.

Related

How can I query a custom datatype object inside an array of said custom datatype in PL/pgSQL?

Suppose I have:
CREATE TYPE compfoo AS (f1 int, f2 text);
And I create a table foo containing two columns: fooid and fooname, corresponding to the fields of compfoo, later I insert some records 1, aa, 2, bb, 3, cc
Then, I define a PL/pgSQL function (more or less as follows:)
create or replace function foo_query()
returns text
language plpgsql
as $$
declare
r compfoo;
arr compfoo [];
footemp compfoo;
result text;
begin
for r in
select * from foo where fooid = 1 OR fooid = 2
loop
arr := array_append(arr, r);
end loop;
foreach footemp in array arr
loop
select footemp.f1 into result where footemp.f1 = 1;
end loop;
return result;
end;
$$
Where I query first foo by column names and save the results into arr, an array of compfoo. Later, I iterate over arr and try to query the elements by their fieldnames as defined in compfoo.
I don't get an error per se in Postgres but the result of my function is null.
What am I doing wrong?
The RAISE NOTICE should be your best friend. You can print the result of some variables at some points of your code. The basic issue are not well initialized values. The arr variable is initialized by NULL value, and any operation over NULL is NULL again.
Another problem is in select footemp.f1 into result where footemp.f1 = 1; statement. SELECT INTO in Postgres overwrite the target variable by NULL value when an result is empty. In second iteration, the result of this query is empty set, and the result variable is set on NULL.
The most big problem of your example is style of programming. You use ISAM style, and your code can be terrible slow.
Don't use array_append in cycle, when you can use array_agg function in query, and you don't need cycle,
Don't use SELECT INTO when you don't read data from tables,
Don't try to repeat Oracle' pattern BULK COLLECT and FOREACH read over collection. PostgreSQL is not Oracle, uses very different architecture, and this pattern doesn't increase performance (like on Oracle), but probably you will lost some performance.
Your fixed code can looks like:
CREATE OR REPLACE FUNCTION public.foo_query()
RETURNS text
LANGUAGE plpgsql
AS $function$
declare
r compfoo;
arr compfoo [] default '{}'; --<<<
footemp compfoo;
result text;
begin
for r in
select * from foo where fooid = 1 or fooid = 2
loop
arr := array_append(arr, r);
end loop;
foreach footemp in array arr
loop
if footemp.f1 = 1 then --<<<
result := footemp.f1;
end if;
end loop;
return result;
end;
$function$
postgres-# ;
It returns expected result. But it is perfect example how don't write stored procedures. Don't try to replace SQL in stored procedures. All code of this procedure can be replaced just by one query. In the end this code can be very slow on bigger data.

PostgreSQL - Declaring a variable that can store multiple values

I need a way to declare a variable that can store multiple values. My first attempt was to declare a variable using the TABLE type:
DECLARE __id TABLE(results_id integer);
However this didnt go as planned, giving me type-declaration errors. My next attempt was to make an integer[] type
DECLARE __id integer[];
but it ended up giving me an error of that values needs to be inserted using curly braces whenever i attempted to insert them with a select function.
SELECT p.id FROM files.main p
WHERE p.reference = __reference
AND p.platform = __platform_id
INTO __id;
I wonder if there is any way to solve this problem?
If you have a table name t you can declare a variable of the type t
create or replace function tf1() returns int as
$BODY$
DECLARE
var public.t;
BEGIN
select * from public.t into var limit 1;
return var.id;
END;
$BODY$
LANGUAGE plpgsql ;
select * from tf1();
i need an array to use them afterwards, where every result will be run in a function and the results will be inserted into a table object of same type – Crated
While you can do this with arrays and variables, it's simpler and faster to do it in a single query using an insert into select.
INSERT INTO some_other_table (some_column)
SELECT your_function(p.id)
FROM files.main p
WHERE p.reference = __reference
AND p.platform = __platform_id
This will run each matching p.id through your_function and insert the results as some_column in some_other_table.

Cannot run Postgres Stored Procedure [duplicate]

I have a Postgres function which is returning a table:
CREATE OR REPLACE FUNCTION testFunction() RETURNS TABLE(a int, b int) AS
$BODY$
DECLARE a int DEFAULT 0;
DECLARE b int DEFAULT 0;
BEGIN
CREATE TABLE tempTable AS SELECT a, b;
RETURN QUERY SELECT * FROM tempTable;
DROP TABLE tempTable;
END;
$BODY$
LANGUAGE plpgsql;
This function is not returning data in row and column form. Instead it returns data as:
(0,0)
That is causing a problem in Coldfusion cfquery block in extracting data. How do I get data in rows and columns when a table is returned from this function? In other words: Why does the PL/pgSQL function not return data as columns?
To get individual columns instead of the row type, call the function with:
SELECT * FROM testfunction();
Just like you would select all columns from a table.
Also consider this reviewed form of your test function:
CREATE OR REPLACE FUNCTION testfunction()
RETURNS TABLE(a int, b int)
LANGUAGE plpgsql AS
$func$
DECLARE
_a int := 0;
_b int := 0;
BEGIN
CREATE TABLE tempTable AS SELECT _a, _b;
RETURN QUERY SELECT * FROM tempTable;
DROP TABLE tempTable;
END
$func$;
In particular:
The DECLARE key word is only needed once.
Avoid declaring parameters that are already (implicitly) declared as OUT parameters in the RETURNS TABLE (...) clause.
Don't use unquoted CaMeL-case identifiers in Postgres. It works, unquoted identifiers are cast to lower case, but it can lead to confusing errors. See:
Are PostgreSQL column names case-sensitive?
The temporary table in the example is completely useless (probably over-simplified). The example as given boils down to:
CREATE OR REPLACE FUNCTION testfunction(OUT a int, OUT b int)
LANGUAGE plpgsql AS
$func$
BEGIN
a := 0;
b := 0;
END
$func$;
Of course you can do this by putting the function call in the FROM clause, like Eric Brandstetter correctly answered.
However, this is sometimes complicating in a query that already has other things in the FROM clause.
To get the individual columns that the function returns, you can use this syntax:
SELECT (testfunction()).*
Or to get only the column called "a":
SELECT (testfunction()).a
Place the whole function, including the input value(s) in parenteses, followed by a dot and the desired column name, or an asterisk.
To get the column names that the function returns, you'll have to either:
check the source code
inspect the result of the function first, like so : SELECT * FROM testfunction() .
The input values can still come out of a FROM clause.
Just to illustrate this, consider this function and test data:
CREATE FUNCTION funky(a integer, b integer)
RETURNS TABLE(x double precision, y double precision) AS $$
SELECT a*random(), b*random();
$$ LANGUAGE SQL;
CREATE TABLE mytable(a integer, b integer);
INSERT INTO mytable
SELECT generate_series(1,100), generate_series(101,200);
You could call the function "funky(a,b)", without the need to put it in the FROM clause:
SELECT (funky(mytable.a, mytable.b)).*
FROM mytable;
Which would result in 2 columns:
x | y
-------------------+-------------------
0.202419687062502 | 55.417385618668
1.97231830470264 | 63.3628275180236
1.89781916560605 | 1.98870931006968
(...)

Reference a column of a record, from a variable holding the column name

In a function in plpgsql language and having:
"col" CHARACTER VARYING = 'a'; (can be 'b')
"rec" RECORD; Holding records from: (SELECT 1 AS "a", 2 AS "b")
"res" INTEGER;
I need to reference the named column in "col", from "rec". So if "col" has 'b' I will reference "rec"."b", and then save its value into "res".
You cannot reference the columns of an anonymous record type by name in plpgsql. Even though you spell out column aliases in your example in the SELECT statement, those are just noise and discarded.
If you want to reference elements of a record type by name, you need to use a well-known type. Either create and use a type:
CREATE TYPE my_composite_type(a int, b int);
Or use the row type associated with any existing table. You can just write the table name as data type.
DECLARE
rec my_composite_type;
...
Then you need a conditional statement or dynamic SQL to use the value of "col" as identifier.
Conditional statement:
IF col = 'a' THEN
res := rec.a;
ELSIF col = 'b' THEN
res := rec.b;
ELSE
RAISE EXCEPTION 'Unexpected value in variable "col": %', col;
END IF;
For just two possible cases, that's the way to go.
Or dynamic SQL:
EXECUTE 'SELECT $1.' || col
INTO res
USING rec;
I don't see a problem here, but be wary of SQL injection with dynamic SQL. If col can hold arbitrary data, you need to escape it with quote_ident() or format()
Demo
Demonstrating the more powerful, but also trickier variant with dynamic SQL.
Quick & dirty way to create a (temporary!) known type for testing:
CREATE TEMP TABLE rec_ab(a int, b int);
Function:
CREATE OR REPLACE FUNCTION f_test()
RETURNS integer AS
$func$
DECLARE
col text := 'a'; -- can be 'b'
rec rec_ab;
res int;
BEGIN
rec := '(1, 2)'::rec_ab;
EXECUTE 'SELECT $1.' || col
INTO res
USING rec;
RETURN res;
END
$func$
LANGUAGE plpgsql VOLATILE;
Call:
SELECT f_test();
Returns:
f_test
----
1

How to access plpgsql composite type array components

Let's say I've created a composite type in Postgresql:
CREATE TYPE custom_type AS
(x integer
y integer);
I need to use it in a function as an array:
...
DECLARE
customVar custom_type[];
BEGIN
....
My question is: how do I access custom_type's specific components?
For example, I want to (re)assign 'x' for the third element in custom_type array...
postgres=> create type pt as (x int, y int);
CREATE TYPE
postgres=> create or replace function fx()
returns void as $$
declare a pt[] = ARRAY[(10,20),(30,40)]; declare xx pt;
begin
for i in array_lower(a, 1) .. array_upper(a,1)
loop
xx = a[i]; xx.x := xx.x + 1; a[i] := xx; raise notice '%', a[i].x;
end loop;
end;
$$ language plpgsql;
CREATE FUNCTION
postgres=> select fx();
NOTICE: 11
NOTICE: 31
fx
────
(1 row)
Significant limit for target of assign statement is possibility to refer only one level nested properties. This limit can be bypassed by auxiliary variables - it is not too friendly - and internal implementation is too simple, but it is fast and enough for typical stored procedure usage although it is not strong in comparison with generic programming languages.
Given:
SELECT ARRAY[(1,2),(3,4)]::custom_type[];
Use an array subscript and then refer to the field by name.
regress=> SELECT (ARRAY[(1,2),(3,4)]::custom_type[])[1].x;
x
---
1
(1 row)