Stored procedure syntax with IN condition - arrays

(1)
=>CREATE TABLE T1(id BIGSERIAL PRIMARY KEY, name TEXT);
CREATE TABLE
(2)
=>INSERT INTO T1
(name) VALUES
('Robert'),
('Simone');
INSERT 0 2
(3)
SELECT * FROM T1;
id | name
----+--------
1 | Robert
2 | Simone
(2 rows)
(4)
CREATE OR REPLACE FUNCTION test_me(id_list BIGINT[])
RETURNS BOOLEAN AS
$$
BEGIN
PERFORM * FROM T1 WHERE id IN ($1);
IF FOUND THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
END;
$$
LANGUAGE 'plpgsql';
CREATE FUNCTION
My problem is when calling the procedure. I'm not able to find an example on the net showing how to pass a list of values of type BIGINT (or integer, whatsoever).
I tried what follows without success (syntax errors):
First syntax:
eway=> SELECT * FROM test_me('{1,2}'::BIGINT[]);
ERROR: operator does not exist: bigint = bigint[]
LINE 1: SELECT * FROM T1 WHERE id IN ($1)
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
QUERY: SELECT * FROM T1 WHERE id IN ($1)
CONTEXT: PL/pgSQL function test_me(bigint[]) line 3 at PERFORM
Second syntax:
eway=> SELECT * FROM test_me('{1,2}');
ERROR: operator does not exist: bigint = bigint[]
LINE 1: SELECT * FROM T1 WHERE id IN ($1)
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
QUERY: SELECT * FROM T1 WHERE id IN ($1)
CONTEXT: PL/pgSQL function test_me(bigint[]) line 3 at PERFORM
Third syntax:
eway=> SELECT * FROM test_me(ARRAY [1,2]);
ERROR: operator does not exist: bigint = bigint[]
LINE 1: SELECT * FROM T1 WHERE id IN ($1)
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
QUERY: SELECT * FROM T1 WHERE id IN ($1)
CONTEXT: PL/pgSQL function test_me(bigint[]) line 3 at PERFORM
Any clues about a working syntax?
It's like the parser was trying to translate a BIGINT to BIGINT[] in the PEFORM REQUEST but it doesn't make any sense to me...

All your syntax variants to pass an array are correct.
Pass array literal to PostgreSQL function
The problem is with the expression inside the function. You can test with the ANY construct like #Mureinik provided or a number of other syntax variants. In any case run the test with an EXISTS expression:
CREATE OR REPLACE FUNCTION test_me(id_list bigint[])
RETURNS bool AS
$func$
BEGIN
IF EXISTS (SELECT 1 FROM t1 WHERE id = ANY ($1)) THEN
RETURN true;
ELSE
RETURN false;
END IF;
END
$func$ LANGUAGE plpgsql STABLE;
Notes
EXISTS is shortest and most efficient:
PL/pgSQL checking if a row exists - SELECT INTO boolean
The ANY construct applied to arrays is only efficient with small arrays. For longer arrays, other syntax variants are faster. Like:
IF EXISTS (SELECT 1 FROM unnest($1) id JOIN t1 USING (id)) THEN ...
How to do WHERE x IN (val1, val2,…) in plpgsql
Don't quote the language name, it's an identifier, not a string: LANGUAGE plpgsql
Simple variant
While you are returning a boolean value, it can be even simpler. It's probably just for the demo, but as a proof of concept:
CREATE OR REPLACE FUNCTION test_me(id_list bigint[])
RETURNS bool AS
$func$
SELECT EXISTS (SELECT 1 FROM t1 WHERE id = ANY ($1))
$func$ LANGUAGE sql STABLE;
Same result.

The easiest way to check if an item is in an array is with = ANY:
CREATE OR REPLACE FUNCTION test_me(id_list BIGINT[])
RETURNS BOOLEAN AS
$$
BEGIN
PERFORM * FROM T1 WHERE id = ANY ($1);
IF FOUND THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
END;
$$
LANGUAGE 'plpgsql';

Related

How can a PostgreSQL function accept an integer or array of integers for the same function?

I have a function in Postgres 9.6 that accepts an int[] parameter. I'd like to have the function accept a single int as well (and convert it to a single element array if necessary).
CREATE OR REPLACE FUNCTION get_subordinates(inp_persona_ids integer[])
-- Get all subordnates of the people passed in as array
-- TODO allow a single persona ID (int) to be passed in as inp_persona_ids
RETURNS TABLE (persona_id int) AS
$$
BEGIN
RETURN QUERY(
WITH RECURSIVE children AS (
-- passed in persona_id
SELECT
id AS persona_id,
manager_id
FROM
personas
WHERE
id = ANY(inp_persona_ids)
UNION
-- and all subordinates
SELECT
p.id AS persona_id,
p.manager_id
FROM
personas p
JOIN children c ON p.manager_id = c.persona_id
)
SELECT
children.persona_id
FROM
children
LEFT JOIN
personas on children.persona_id = personas.id
WHERE personas.disabled IS NOT TRUE
);
END;
$$ LANGUAGE plpgsql
How would I change the function definition and also add some conditional logic to test for int and change to ARRAY[int] if necessary?
It is not possible to handle this in a single function, but you can just overload the function with an integer parameter and pass this as an array to your existing function:
CREATE OR REPLACE FUNCTION get_subordinates(inp_persona_id integer)
RETURNS TABLE (persona_id int) AS
$$
BEGIN
RETURN QUERY SELECT * FROM get_subordinates(ARRAY[inp_persona_id]);
END;
$$ LANGUAGE plpgsql;
Perhaps you might also want to check the argument(s) against NULL, this is up to you.
It is possible with a single function using the VARIADIC modifier:
CREATE OR REPLACE FUNCTION get_subordinates(VARIADIC inp_persona_ids int[])
RETURNS TABLE (persona_id int) AS
$func$
WITH RECURSIVE children AS ( -- passed in persona_id
SELECT id AS persona_id, manager_id, disabled
FROM personas
WHERE id = ANY(inp_persona_ids)
UNION ALL -- and all subordinates
SELECT p.id AS persona_id
, p.manager_id
FROM children c
JOIN personas p ON p.manager_id = c.persona_id
)
SELECT c.persona_id
FROM children c
WHERE c.disabled IS NOT TRUE
$func$ LANGUAGE sql;
But you need to add the keyword VARIADIC in the call when providing an array instead of a list:
SELECT * FROM get_subordinates(VARIADIC '{1,2,3}'::int[]);
SELECT * FROM get_subordinates(1,2,3);
SELECT * FROM get_subordinates(1);
If that's not an option you are back to function overloading as suggested in another answer.
See:
Passing multiple values in single parameter
Pass multiple values in single parameter
Asides
Looks like this can be a simpler SQL function.
UNION made no sense. Duplicates can only occur if your tree goes in circles, which would create an endless loop and the rCTE would error out. Use the cheaper UNION ALL.
LEFT JOIN made no sense. The added WHERE forced it to behave like a plain [INNER] JOIN anyway.
But remove the join completely and retrieve the column disabled inside the rCTE.

Assigning JSONB_ARRAY_ELEMENTS results to a VARCHAR array

When trying to assign results of a JSONB_ARRAY_ELEMENTS call to a VARCHAR array (for later use in SELECT ... WHERE IN (UNNEST(...)) statements), the following stored function:
CREATE OR REPLACE FUNCTION test1(
IN in_sids jsonb,
OUT out_uid integer)
RETURNS integer AS
$func$
DECLARE
sids varchar[];
uids integer[];
BEGIN
sids := (SELECT x->>'sid' FROM JSONB_ARRAY_ELEMENTS(in_sids) x);
-- uids := (SELECT uid FROM social WHERE sid IN (UNNEST(sids)));
RAISE NOTICE 'sids = %', sids;
RAISE NOTICE 'uids = %', uids;
SELECT 1;
END
$func$ LANGUAGE plpgsql;
unfortunately prints the error:
# select test1('[{"sid":"aaa"},{"sid":"bbb"}]'::jsonb);
ERROR: more than one row returned by a subquery used as an expression
CONTEXT: SQL statement "SELECT (SELECT x->>'sid' FROM JSONB_ARRAY_ELEMENTS(in_sids) x)"
PL/pgSQL function test1(jsonb) line 6 at assignment
So I have tried to fix the assignment by using ARRAY_AGG -
sids := SELECT ARRAY_AGG(SELECT x->>'sid' FROM JSONB_ARRAY_ELEMENTS(in_sids) x);
but get the syntax error:
ERROR: syntax error at or near "SELECT"
LINE 10: sids := SELECT ARRAY_AGG(SELECT x->>'sid' FROM JSONB...
^
How to store JSONB_ARRAY_ELEMENTS results to an array please?
UPDATE:
I have followed Nick's advise (thank you), but now stuck at the next step:
CREATE OR REPLACE FUNCTION test1(
IN in_sids jsonb,
OUT out_uid integer)
RETURNS integer AS
$func$
DECLARE
sids varchar[];
uids integer[];
BEGIN
sids := (SELECT ARRAY_AGG(x->>'sid') FROM JSONB_ARRAY_ELEMENTS(in_sids) x);
uids := (SELECT uid FROM social WHERE sid IN (UNNEST(sids)));
SELECT 1;
END
$func$ LANGUAGE plpgsql;
when trying to use the array in a SELECT ... WHERE IN ... statement:
# select test1('[{"sid":"aaa"},{"sid":"bbb"}]'::jsonb);
ERROR: argument of IN must not return a set
LINE 1: SELECT (SELECT uid FROM social WHERE sid IN (UNNEST(si...
^
QUERY: SELECT (SELECT uid FROM social WHERE sid IN (UNNEST(sids)))
CONTEXT: PL/pgSQL function test1(jsonb) line 7 at assignment
You were close...
sids := (SELECT ARRAY_AGG(x->>'sid') FROM JSONB_ARRAY_ELEMENTS(in_sids) x);
In order to use the expression IN (subquery) construct, you need to write a self-contained SELECT statement:
uids := (SELECT ARRAY_AGG(uid) FROM social WHERE sid IN (SELECT UNNEST(sids)));
Alternatively, you can check if a value is in an array using ANY:
uids := (SELECT ARRAY_AGG(uid) FROM social WHERE sid = ANY(sids));
If you're not using the sids array for some later calculation, you can combine these into one query:
uids := (
SELECT array_agg(uid)
FROM social
JOIN JSONB_ARRAY_ELEMENTS(in_sids) x ON
sid = x->>'sid'
);

Postgres function with text array and select where in query

I need to create a function like this (scaled down to a minimum) where I send an array of strings that should be matched. But I cant make the query to work.
create or replace function bar(x text[]) returns table (c bigint) language plpgsql as $$
begin
return query select count(1) as counter from my_table where my_field in (x);
end;$$;
and call it like this
select * from bar(ARRAY ['a','b']);
I could try to let the parameter x be a single text string and then use something like
return query execute 'select ... where myfield in ('||x||')';
So how would I make it work with the parameter as an array?
would that be better or worse compared to let the parameter be a string?
Yes, an array is the cleaner form. String matching would leave corner cases where separators and patterns combined match ...
To find strings that match any of the given patterns, use the ANY construct:
CREATE OR REPLACE FUNCTION bar(x text[])
RETURNS bigint LANGUAGE sql AS
$func$
SELECT count(*) -- alias wouldn't visible outside function
FROM my_table
WHERE my_field = ANY(x);
$func$;
count(*) is slightly faster than count(1). Same result.
Note, I am using a plain SQL function (instead of plpgsql). Either has its pros and cons.
That's fixed with the help of unnest that converts an array to a set (btw, the function doesn't have to be plpgsql):
CREATE OR REPLACE FUNCTION bar(x text[]) RETURNS BIGINT LANGUAGE sql AS $$
SELECT count(1) AS counter FROM my_table
WHERE my_field IN (SELECT * FROM unnest(x));
$$;
The problem with using the array seems to be fixed by using
return query select count(1) as counter from my_table where my_field in (array_to_string(x,','));
The point of effiency still remains unsolved.

Using return next and return query in the same Postgres function

I am a newbie in PostgreSQL (using v9.0) and wanted to know if using return next and Return query in the same function is possible without exiting the function before attaching the results in the resultset?
CREATE TYPE return_type AS
(paramname character varying,
value character varying);
CREATE OR REPLACE FUNCTION myfuntion(param1 character varying)
RETURNS SETOF return_type AS
declare
r return_type;
message varchar;
status integer;
$BODY$
BEGIN
o_call_status := 0;
o_call_message := '';
Return query Select 'mystatus' as paramName, status::varchar as value;
Return query Select 'mymessage' as paramName, message as value;
for r in SELECT 'mycolumnname1' as paramName,mycolumn1 as value FROM tb1
WHERE column1 = val
UNION ALL
SELECT 'mycolumnname2' as paramName,mycolumn2 as value FROM tb1
WHERE column1 = val
UNION ALL
SELECT 'mycolumnname3' as paramName,mycolumn3 as value FROM tb2
WHERE column1 = val1 AND
column4 = val4 loop
return next r;
end loop;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
Returning from a plpgsql function
You can mix RETURN NEXT and RETURN QUERY freely and repeatedly. The underlying principle is that plpgsql builds the table locally and does not return until the function is finished:
Per documentation:
Note: The current implementation of RETURN NEXT and RETURN QUERY
stores the entire result set before returning from the function, as discussed above.
That means you can even raise en exception to abort the operation if you are unsatisfied with the results so far and the client won't see a thing. We also included an example in the manual demonstrating this, just above said quote.
Cross tabulation
As for what you are trying do achieve, consider the crosstab() function for the tablefunc extension. Related answer with sample details:
PostgreSQL Crosstab Query
I'm unsure if you can mix the two (try it…).
That said, it'll be much more efficient in you use-case to write a single query with a union all clauses that returns all rows directly:
return query
select 'mystatus' as paramName, status::varchar as value
union all
select 'mymessage' as paramName, message as value
union all
SELECT 'mycolumnname1' as paramName,mycolumn1 as value
FROM tb1
WHERE column1 = val
union all
…
You might also find this contrib module helpful, btw:
http://www.postgresql.org/docs/current/static/tablefunc.html
And, if possible, revisit your schema or the way you use it, so you don't need this kind of function to begin with — set returning functions that return potentially large sets can be particularly inefficient. In your case, it seems like you want three columns. Why not simple use?
select col1, col2, col3 from tbl

Postgres UNIQUE CONSTRAINT for array

How to create a constraint on the uniqueness of all the values ​​in the array like:
CREATE TABLE mytable
(
interface integer[2],
CONSTRAINT link_check UNIQUE (sort(interface))
)
my sort function
create or replace function sort(anyarray)
returns anyarray as $$
select array(select $1[i] from generate_series(array_lower($1,1),
array_upper($1,1)) g(i) order by 1)
$$ language sql strict immutable;
I need that would be the value {10, 22} and {22, 10} considered the same and check under the UNIQUE CONSTRAINT
I don't think you can use a function with a unique constraint but you can with a unique index. So given a sorting function something like this:
create function sort_array(anyarray) returns anyarray as $$
select array_agg(distinct n order by n) from unnest($1) as t(n);
$$ language sql immutable;
Then you could do this:
create table mytable (
interface integer[2]
);
create unique index mytable_uniq on mytable (sort_array(interface));
Then the following happens:
=> insert into mytable (interface) values (array[11,23]);
INSERT 0 1
=> insert into mytable (interface) values (array[11,23]);
ERROR: duplicate key value violates unique constraint "mytable_uniq"
DETAIL: Key (sort_array(interface))=({11,23}) already exists.
=> insert into mytable (interface) values (array[23,11]);
ERROR: duplicate key value violates unique constraint "mytable_uniq"
DETAIL: Key (sort_array(interface))=({11,23}) already exists.
=> insert into mytable (interface) values (array[42,11]);
INSERT 0 1
#mu already demonstrated how an index on an expression can solve your problem.
My attention was caught by the used functions. Both seem like an overkill for array of two integers. This may be a simplification of the real situation. (?)
Anyway, I was intrigued and ran a test with a couple of variants.
Test setup
-- temporary table with 10000 random pairs of integer
CREATE TEMP TABLE arr (i int[]);
INSERT INTO arr
SELECT ARRAY[(random() * 1000)::int, (random() * 1000)::int]
FROM generate_series(1,10000);
Test candidates with a short comment to explain each one:
-- 1) mu's query
CREATE OR REPLACE FUNCTION sort_array1(integer[]) RETURNS int[] AS
$$
SELECT array_agg(n) FROM (SELECT n FROM unnest($1) AS t(n) ORDER BY n) AS a;
$$ LANGUAGE sql STRICT IMMUTABLE;
-- 2) simplified with ORDER BY inside aggregate (pg 9.0+)
CREATE OR REPLACE FUNCTION sort_array2(int[]) RETURNS int[] AS
$$
SELECT array_agg(n ORDER BY n) FROM unnest($1) AS t(n);
$$ LANGUAGE sql STRICT IMMUTABLE;
-- 3) uralbash's query
CREATE OR REPLACE FUNCTION sort_array3(anyarray) RETURNS anyarray AS
$$
SELECT ARRAY(
SELECT $1[i]
FROM generate_series(array_lower($1,1), array_upper($1,1)) g(i)
ORDER BY 1)
$$ LANGUAGE sql STRICT IMMUTABLE;
-- 4) change parameters to int[]
CREATE OR REPLACE FUNCTION sort_array4(int[]) RETURNS int[] AS
$$
SELECT ARRAY(
SELECT $1[i]
FROM generate_series(array_lower($1,1), array_upper($1,1)) g(i)
ORDER BY 1)
$$ LANGUAGE sql STRICT IMMUTABLE;
-- 5) simplify array_lower() - it's always 1
CREATE OR REPLACE FUNCTION sort_array5(int[]) RETURNS int[] AS
$$
SELECT ARRAY(
SELECT $1[i]
FROM generate_series(1, array_upper($1,1)) g(i)
ORDER BY 1)
$$ LANGUAGE sql STRICT IMMUTABLE;
-- 6) further simplify to case with 2 elements
CREATE OR REPLACE FUNCTION sort_array6(int[]) RETURNS int[] AS
$$
SELECT ARRAY(
SELECT i
FROM (VALUES ($1[1]),($1[2])) g(i)
ORDER BY 1)
$$ LANGUAGE sql STRICT IMMUTABLE;
-- 7) my radically simple query
CREATE OR REPLACE FUNCTION sort_array7(int[]) RETURNS int[] AS
$$
SELECT CASE WHEN $1[1] > $1[2] THEN ARRAY[$1[2], $1[1]] ELSE $1 END;
$$ LANGUAGE sql STRICT IMMUTABLE;
-- 8) without STRICT modifier
CREATE OR REPLACE FUNCTION sort_array8(int[]) RETURNS int[] AS
$$
SELECT CASE WHEN $1[1] > $1[2] THEN ARRAY[$1[2], $1[1]] ELSE $1 END;
$$ LANGUAGE sql IMMUTABLE;
Results
I executed each around 20 times and took the best result from EXPLAIN ANALYZE.
SELECT sort_array1(i) FROM arr -- Total runtime: 183 ms
SELECT sort_array2(i) FROM arr -- Total runtime: 175 ms
SELECT sort_array3(i) FROM arr -- Total runtime: 183 ms
SELECT sort_array4(i) FROM arr -- Total runtime: 183 ms
SELECT sort_array5(i) FROM arr -- Total runtime: 177 ms
SELECT sort_array6(i) FROM arr -- Total runtime: 144 ms
SELECT sort_array7(i) FROM arr -- Total runtime: 103 ms
SELECT sort_array8(i) FROM arr -- Total runtime: 43 ms (!!!)
These are the results from a v9.0.5 server on Debian Squeeze. Similar results on v.8.4.
I also tested plpgsql variants which were a bit slower as expected: too much overhead for a tiny operation, no query plan to cache.
The simple function (nr. 7) is substantially faster than the others. That was to be expected, the overhead of the other variants is just too much for a tiny array.
But that leaving away the STRICT modifier more than doubles the speed was not to be expected. At least I didn't. I posted a question about this phenomenon here.
Just create a unique index on the two values:
create unique index ix on
mytable(least(interface[1], interface[2]), greatest(interface[1], interface[2]));

Resources