Composite Array Type as OUTPUT parameter in PostgreSQL - postgresql

I'm trying to insert data into composite array type in my function. It should accept data from composite array type of INPUT parameter and store data into OUPUT parameter of same type.
CREATE TYPE public.type_x_type AS (x integer);
CREATE TYPE public.type_y_type AS(x integer,y integer);
My function is
CREATE OR REPLACE FUNCTION GET_PRICE_PC_X
(
IP_PRICE_INFO IN TYPE_X_TYPE[],
PC_COST OUT TYPE_Y_TYPE[],
OP_RESP_CODE OUT VARCHAR,
OP_RESP_MSG OUT VARCHAR
)
RETURNS RECORD AS $$
DECLARE
SELECTED_PRICE CURSOR(IP_PFCNTR INT)
FOR
SELECT ID, PHONE FROM CUSTOMER WHERE ID=IP_PFCNTR;
J NUMERIC(10);
BEGIN
J := 0;
FOR I IN ARRAY_LOWER(IP_PRICE_INFO,1) .. ARRAY_UPPER(IP_PRICE_INFO,1)
LOOP
FOR K IN SELECTED_PRICE(IP_PRICE_INFO[I].X)
LOOP
PC_COST := ROW(K.ID,K.PHONE);
END LOOP;
END LOOP;
OP_RESP_CODE :='000';
OP_RESP_MSG :='Success';
EXCEPTION
WHEN OTHERS THEN
OP_RESP_CODE :='200';
OP_RESP_MSG :=SQLERRM;
END;
$$ language 'plpgsql';
select * from GET_PRICE_PC_X(ARRAY[ROW(1)] :: TYPE_X_TYPE[]);
And I'm getting the below error.
PC_COST | OP_RESPONSE_CODE | OP_RESP_MSG
---------------------------------------------------------
| 200 | malformed array literal: "(1,30003)"
I'll be calling that OUT type somewhere, so I need the data to be inserted into array.

When you develop a function, then doesn't use WHEN OTHERS. The debugging is terrible then. The problem of your function is a assignment a composite type to a array
PC_COST := ROW(K.ID,K.PHONE);
This is wrong. Probably you would to do append.
The critical part should to look like
J := 0; PC_COST := '{}';
FOR I IN ARRAY_LOWER(IP_PRICE_INFO,1) .. ARRAY_UPPER(IP_PRICE_INFO,1)
LOOP
FOR K IN SELECTED_PRICE(IP_PRICE_INFO[I].X)
LOOP
PC_COST := PC_COST || ROW(K.ID,K.PHONE)::type_y_type;
END LOOP;
END LOOP;
Your function can be replaced by one query - maybe less readable, but significantly faster - loops with nested queries can be slow (is faster run one simple SELECT than more trivial SELECTs):
CREATE OR REPLACE FUNCTION public.get_price_pc_x(ip_price_info type_x_type[],
OUT pc_cost type_y_type[],
OUT op_resp_code character varying,
OUT op_resp_msg character varying)
RETURNS record
LANGUAGE plpgsql STABLE
AS $function$
BEGIN
pc_cost := ARRAY(SELECT ROW(id, phone)::type_y_type
FROM customer
WHERE id IN (SELECT (unnest(ip_price_info)).x));
OP_RESP_CODE :='000';
OP_RESP_MSG :='Success';
EXCEPTION
WHEN OTHERS THEN
OP_RESP_CODE :='200';
OP_RESP_MSG :=SQLERRM;
END;
$function$;
Note: using NUMERIC type for cycle variable is a wrong idea - this type is expensive and should be used only for money or precious calculation. The int type is absolutely correct in this place.
Usually you can reduce you function more - the error handling should not be there - there is not a reason why this function should fail - (not a reason that can be handled)
CREATE OR REPLACE FUNCTION public.get_price_pc_x(ip_price_info type_x_type[])
RETURNS type_y_type[]
LANGUAGE sql STABLE
AS $function$
SELECT ARRAY(SELECT ROW(id, phone)::type_y_type
FROM customer
WHERE id IN (SELECT (unnest(ip_price_info)).x));
$function$;

Related

How can I omit records when returning a table in postgresql?

I have a problem when I try to execute a function where, depending on the result of another function, it returns a record or not. What I want to do is that when the function ("function_typecar" in the example) returns 1 as a result, it returns the record, otherwise no. The problem is when the condition is not met. It repeats the same record until there is one that meets the condition again. Is there a way to skip returning the records when the condition is not met? I leave an example of this situation
CREATE OR REPLACE FUNCTION schema.function_test
RETURNS TABLE(id_r INTEGER, name_r CHARACTER VARYING, year_r INTEGER) AS
$BODY$
DECLARE
sql_record RECORD;
type_car INTEGER;
BEGIN
SELECT id, name, year
FROM car
FOR sql_record IN
SELECT id, name, year
FROM car
LOOP
SELECT type INTO type_car FROM function_typecar(sql_record.id);
IF type_car = 1 THEN
id_r := sql_record.id;
name_r := sql_record.name;
year_r := sql_record.year;
END IF;
RETURN NEXT;
END LOOP;
END; $BODY$
This is the result that I want to avoid and that only brings me those that meet the IF condition:
Something like this should work, without a LOOP and in plain SQL:
CREATE OR REPLACE FUNCTION function_test()
RETURNS TABLE(id_r INTEGER, name_r CHARACTER VARYING, year_r INTEGER)
LANGUAGE SQL
AS
$BODY$
SELECT id, name, year
FROM car
JOIN function_typecar(id) ON type = 1;
$BODY$;

Problem with PLPGSQL function return, uuid involved

I want to assign serial numbers from a sequence, along with uuids that are generated automatically. My table looks like
CREATE TABLE master_serialnumbers
(
uuident uuid PRIMARY KEY DEFAULT uuid_generate_v1(),
serno integer GENERATED ALWAYS AS IDENTITY UNIQUE,
requester varchar(20) NOT NULL
);
I can now generate a serial number with
INSERT INTO master_serialnumbers(requester) VALUES ('XY')
RETURNING (uuident,serno,requester);
To automate this and generate a block of several serial numbers, I can use a function like this:
CREATE FUNCTION gen_serials_A(num_serials integer, req varchar(20))
RETURNS SETOF master_serialnumbers
AS $$
DECLARE
new_id uuid;
rr master_serialnumbers%ROWTYPE; -- or simply RECORD
tmpSQL TEXT;
BEGIN
RAISE NOTICE 'generate % serial numbers', num_serials;
CREATE TEMPORARY TABLE tmpIds (id uuid NOT NULL PRIMARY KEY);
FOR i IN 1..num_serials LOOP
RAISE NOTICE 'serno %', i;
INSERT INTO master_serialnumbers(space, requester) VALUES (spc, req)
RETURNING (uuident) INTO new_id;
INSERT INTO tmpIds(id) VALUES (new_id);
END LOOP;
FOR rr IN EXECUTE 'SELECT * FROM master_serialnumbers
WHERE uuident IN (SELECT id FROM tmpIds)' LOOP
RETURN NEXT rr;
END LOOP;
tmpSQL := 'DROP TABLE tmpIds';
EXECUTE tmpSQL;
RETURN;
END
$$ LANGUAGE plpgsql;
This does work, but it seems cumbersome to store the values in a temporary table and have two loops. So I tried to shorten it like this:
CREATE FUNCTION gen_serials(num_serials integer, req varchar(20))
RETURNS SETOF master_serialnumbers
AS $$
DECLARE
rr master_serialnumbers%ROWTYPE;
BEGIN
RAISE NOTICE 'generate % serial numbers', num_serials;
FOR i IN 1..num_serials LOOP
RAISE NOTICE 'serno %', i;
INSERT INTO master_serialnumbers(requester) VALUES (req)
RETURNING (uuident,serno,requester) INTO rr;
RAISE NOTICE 'rr is %', rr;
RETURN NEXT rr;
END LOOP;
RETURN;
END
$$ LANGUAGE plpgsql;
– but the INSERT throws an error
invalid input syntax for type uuid: »(12345678-1234-1234-1234-123456789abc,1,A)«
Changing the declaration to rr RECORD now passes the INSERT:
rr is ("(12345678-1234-1234-1234-123456789abc,1,A)")
ERROR: wrong record type supplied in RETURN NEXT
DETAIL: Returned type does not match expected type uuid in column 1.
Apparently all has been converted into a string.
And changing the return type also to SETOF RECORD yields another error:
ERROR: function with set result called in a context that cannot process set results
(German error messages translated back to English by me, sorry for that).
Any ideas what's (not) happening here? Perhaps I am doing it much too long-winded at all by using a function with procedural loops?
To automate this and generate a block of several serial numbers, I can use a function
Don't make it so complicated. Use something like
INSERT INTO master_serialnumbers(requester)
VALUES ('XY', 'YZ', 'ZA')
RETURNING uuident, serno, requester;
or
INSERT INTO master_serialnumbers(requester)
SELECT UNNEST(ARRAY['XY', 'YZ', 'ZA']) -- useful for parameterised queries
RETURNING uuident, serno, requester;
or
INSERT INTO master_serialnumbers(requester)
SELECT 'XY'
FROM generate_series(1, 20) -- useful for arbitrary repetition of the same value
RETURNING uuident, serno, requester;
(a set-returning function to create a series of numbers)

How to pass and array and modify it in a function in postgres

I am trying to write a function that does this: basically create an array with same data elements as the array that is passed to the function but with some change sometimes - like if some element is even then the flag should change from N to Y. The procedure takes as input an array that has two elements - a number and a flag. eg. (1,'N'), (2,'N'). Now if the passed number is even then the proc should modify that value and change to (2,'Y') whereas the other one remains as (1,'N').
Basically my array basics are not clear and reading through details has not helped so this question.
I tried the following but it is not working...can you please suggest:
CREATE TYPE test_n_t_num AS (
v_n double precision,
is_even character varying(1));
create function temp_n_proc_2(p_nums IN OUT test_n_t_num[])
as
$$
declare
v_nums test_n_t_num[];
v_cnt double precision;
BEGIN
v_cnt := cardinality(p_nums);
v_nums := ARRAY[]::test_n_t_num[];
for i in 1..v_cnt LOOP
if p_nums[i].v_n_double % 2 = 0 then
v_nums [i].is_even := 'Y';
p_nums [i].is_even := 'Y'
else
v_nums [i].is_even := p_nums [i].is_even;
end if;
v_nums[i] := {p_nums[i].v_n,v_nums [i].is_even};
END LOOP;
END;
$$
language plpgsql;
Also later I need to loop through and print out the values in the array v_nums - one that is defined in the function.
Thank you,
Nirav
I had a similar issue when I was trying to do this sort of thing back in the day. Basically you can't assign to composite objects using array notation, i.e. your_array[1].field := value doesn't work. Here's something that does (I don't use cardinality since I'm still on 9.3 and that was added in 9.4):
CREATE TYPE public.test1 AS (a INTEGER, is_even BOOLEAN);
CREATE OR REPLACE FUNCTION f1(ar INOUT public.test1[]) AS $$
DECLARE
t public.test1;
BEGIN
RAISE NOTICE '%', ar;
FOR i IN 1..ARRAY_LENGTH(ar, 1) LOOP
t := ar[i];
t.is_even := t.a % 2 = 0;
ar[i] := t;
END LOOP;
RAISE NOTICE '%', ar;
END
$$ LANGUAGE plpgsql;
So basically create a variable of that type, read the indexed item into the variable, modify the fields, then copy the variable's content back to the array.
SELECT * FROM f1('{"(1,)","(4,)","(6,)"}'::public.test1[]) returns {"(1,f)","(4,t)","(6,t)"}
The messages printed (this is using pgadmin 3) are:
NOTICE: {"(1,)","(4,)","(6,)"}
NOTICE: {"(1,f)","(4,t)","(6,t)"}

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 .

Assign complex type with UUID field from a function call

I am using PostgreSQL 9.3, working with plpgsql functions:
CREATE TEMP TABLE uuid_inside (
id uuid PRIMARY KEY
);
CREATE FUNCTION uuid_test_func(id uuid)
RETURNS uuid_inside AS
$$
DECLARE
res_uuid_inside uuid_inside;
BEGIN
IF (id = '00000000-0000-0000-0000-000000000001'::uuid) THEN
SELECT uuid_test_func('00000000-0000-0000-0000-000000000000'::uuid)
INTO res_uuid_inside;
RETURN res_uuid_inside;
END IF;
res_uuid_inside.id := id;
RETURN res_uuid_inside;
END;
$$
LANGUAGE plpgsql;
Call:
SELECT uuid_test_func('00000000-0000-0000-0000-000000000001'::uuid);
Message:
ERROR: invalid input syntax for uuid: "(00000000-0000-0000-0000-000000000000)"
SQL-state: 22P02
But this works fine:
SELECT uuid_test_func('00000000-0000-0000-0000-000000000002'::uuid);
The problem is not with recursive function calling - the original code is refering to some other function inside.
Simple function
The recursion in your function seems pointless. This simple sql function does the job (without nesting and without the composite type wrapper):
CREATE FUNCTION uuid_test_func(id uuid)
RETURNS uuid AS
$func$
SELECT CASE WHEN $1 = '00000000-0000-0000-0000-000000000001'::uuid
THEN '00000000-0000-0000-0000-000000000000'::uuid
ELSE $1
END
$func$ LANGUAGE plpgsql;
But that's probably just due to simplification for the demo.
Address problem in original
As for the error message. You are running into a confusing "feature" of PL/pgSQL:
Composite type assignment expects one column per type column. Assigning composite types as a whole is not an option.
This has nothing to do with the UUID type per se.
This modified version would work:
CREATE OR REPLACE FUNCTION uuid_test_func(id uuid)
RETURNS uuid_inside AS
$func$
DECLARE
res_uuid_inside uuid_inside;
BEGIN
IF (id = '00000000-0000-0000-0000-000000000001'::uuid) THEN
SELECT * FROM uuid_test_func('00000000-0000-0000-0000-000000000000'::uuid)
INTO res_uuid_inside.id;
-- INTO res_uuid_inside; -- would work, too - 1st col is assigned
RETURN res_uuid_inside;
END IF;
res_uuid_inside.id := id;
RETURN res_uuid_inside;
END
$func$ LANGUAGE plpgsql;
Closely related question:
Passing array of a composite type to stored procedure
Simpler function
That said, I'd suggest this simplified form (keeping the recursion and the composite result):
CREATE OR REPLACE FUNCTION uuid_test_func(id uuid)
RETURNS uuid_inside AS
$func$
BEGIN
IF (id = '00000000-0000-0000-0000-000000000001'::uuid) THEN
RETURN (SELECT f FROM uuid_test_func('00000000-0000-0000-0000-000000000000'::uuid) f);
ELSE
RETURN row(id)::uuid_inside;
END IF;
END
$func$ LANGUAGE plpgsql;