Intersection of multiple text arrays: ERROR: array value must start with "{" - sql

I'm attemping to get the functions in this question to work: Intersection of multiple arrays in PostgreSQL
Unlike that question, I want to intersect text arrays instead of integer arrays. I've modified both functions accordingly. Base array intersect function:
CREATE FUNCTION array_intersect(a1 text[], a2 text[]) RETURNS text[] AS $$
DECLARE
ret text[];
BEGIN
IF a1 is null THEN
return a2;
ELSEIF a2 is null THEN
RETURN a1;
END IF;
SELECT array_agg(e) INTO ret
FROM (
SELECT unnest(a1)
INTERSECT
SELECT unnest(a2)
) AS dt(e);
RETURN ret;
END;
$$ language plpgsql;
Aggregate function definition:
CREATE AGGREGATE utility.array_intersect_agg(
sfunc = array_intersect,
basetype = text[],
stype = text[],
initcond = NULL
);
I get the error "ERROR: array value must start with "{" or dimension information
SQL state: 22P02" when I try to run the following code:
SELECT array_intersect_agg(test)
FROM(
SELECT ARRAY['A','B','C'] test
UNION ALL
SELECT ARRAY['A','C'] test
) a
What needs to change in order for these functions to work?

For the documentation:
initial_condition
The initial setting for the state value. This must be a string
constant in the form accepted for the data type state_data_type. If
not specified, the state value starts out null.
So the aggregate declaration should look like this:
CREATE AGGREGATE array_intersect_agg(
sfunc = array_intersect,
basetype = text[],
stype = text[]
);

Related

Return a composite type or multiple columns from a PostgreSQL function

My aim is to write a function that takes in one parameter and returns two values. The query is working perfectly, however, when executed via the function made, I receive an error that a subquery should not return multiple columns.
My function is as follows:
CREATE TYPE double_integer_type AS (p1 integer, p2 integer);
DROP FUNCTION next_dvd_in_queue;
CREATE OR REPLACE FUNCTION next_dvd_in_queue (member_id_p1 integer) RETURNS double_integer_type as $$
BEGIN
RETURN(
select temp2.dvdid,
temp2.movie_title
from
(select temp1.dvdid,
temp1.movie_title,
temp1.customer_priority
from
(select *
from rentalqueue
where rentalqueue.memberid=member_id_p1) temp1
inner join dvd on dvd.dvdid=temp1.dvdid
where dvd.dvdquantityonhand>0) temp2
order by temp2.customer_priority asc
limit 1
);
END; $$ LANGUAGE PLPGSQL
Call:
select dvdid from next_dvd_in_queue(3);
The query, when executed with a hard-coded value, is:
select temp2.dvdid,
temp2.movie_title
from
(select temp1.dvdid,
temp1.movie_title,
temp1.customer_priority
from
(select *
from rentalqueue
where rentalqueue.memberid=3) temp1
inner join dvd on dvd.dvdid=temp1.dvdid
where dvd.dvdquantityonhand>0) temp2
order by temp2.customer_priority asc
limit 1
The above query works fine.
However, when I call the function in the following way:
select * from next_dvd_in_queue(3);
I get the following error:
ERROR: subquery must return only one column
LINE 1: SELECT (
^
QUERY: SELECT (
select temp2.dvdid,
temp2.movie_title
from
(select temp1.dvdid,
temp1.movie_title,
temp1.customer_priority
from
(select *
from rentalqueue
where rentalqueue.memberid=3) temp1
inner join dvd on dvd.dvdid=temp1.dvdid
where dvd.dvdquantityonhand>0) temp2
order by temp2.customer_priority asc
limit 1
)
CONTEXT: PL/pgSQL function next_dvd_in_queue(integer) line 3 at RETURN
You can fix the syntax error with an explicit cast to the composite type:
CREATE OR REPLACE FUNCTION next_dvd_in_queue (member_id_p1 integer)
RETURNS double_integer_type AS
$func$
BEGIN
RETURN (
SELECT ROW(temp2.dvdid, temp2.movie_title)::double_integer_type
FROM ...
);
END
$func$ LANGUAGE plpgsql
But I would remove the needless complication with the composite type and use OUT parameters instead:
CREATE OR REPLACE FUNCTION pg_temp.next_dvd_in_queue (member_id_p1 integer
OUT p1 integer
OUT p2 varchar(100)) AS
$func$
BEGIN
SELECT INTO p1, p2
temp2.dvdid, temp2.movie_title
FROM ...
END
$func$ LANGUAGE plpgsql;
Avoid naming collisions between parameter names and column names. I like to stick to a naming convention where I prefix all parameter names with _, so _member_id_p1, _p1, _p2.
Related:
Returning from a function with OUT parameter
How to return result of a SELECT inside a function in PostgreSQL?

Reference a parameter in Postgres function

The table org has a column called npi. Why does query1 work and query2 not?
Query 1 -
CREATE OR REPLACE FUNCTION check (npi TEXT)
RETURNS BOOLEAN AS $$
DECLARE
pass_npi TEXT;
BEGIN
pass_npi := npi;
SELECT 1
FROM org doc
WHERE doc.npi = pass_npi
;
RETURN 1;
END $$
Query 2 -
CREATE OR REPLACE FUNCTION check (npi TEXT)
RETURNS BOOLEAN AS $$
BEGIN
SELECT 1
FROM org doc
WHERE doc.npi = npi
;
RETURN 1;
END $$
ERROR -
Ambigious column name NPI
Because in the second case it is unclear if npi is the table column (that would be a valid, if useless statement) or the function parameter.
There are three solutions apart from the one in your first query:
The best one: use function parameters that have names different from table columns. This can be done by using a prefix:
CREATE FUNCTION check (p_npi TEXT) RETURNS boolean AS
...
SELECT ...
WHERE doc.npi = p_npi
Use the ALIAS command to “rename” the parameter:
CREATE FUNCTION check (npi TEXT) RETURNS boolean AS
$$DECLARE
p_npi ALIAS FOR npi;
BEGIN
...
SELECT ...
WHERE doc.npi = p_npi
Qualify the parameter with the function name:
CREATE FUNCTION check (npi TEXT) RETURNS boolean AS
...
SELECT ...
WHERE doc.npi = check.npi
What happens is that in Query2 you are comparing the field the doc.npi field with it, it is the same to say doc.npi and to say npi, for that reason it shows you that the sentence is ambiguous, on the contrary case in Query1 you are comparing the doc.npi field with a different field that is the pass_npi.
To solve this problem you must compare the same columns but from different tables or different columns from the same table.
Query2:
CREATE OR REPLACE FUNCTION check (npi TEXT)
RETURNS BOOLEAN AS $$
BEGIN
SELECT 1
FROM org doc
WHERE doc.npi = pass_npi
;
RETURN 1;
END $$

Returning table with specific parameters from a PostgreSQL function

I have this function, with which I would like to return a table, which holds two columns: game_name and follow (this would be an integer 0 or 1):
CREATE OR REPLACE FUNCTION public.toggle2(uid numeric, gid NUMERIC)
RETURNS TABLE (
follow INT,
game_name TEXT
)
LANGUAGE plpgsql
AS $$
BEGIN
IF NOT EXISTS(SELECT *
FROM game_follows
WHERE user_id = uid and game_id = gid)
THEN
INSERT INTO game_follows(user_id, game_id) VALUES(uid, gid);
follow := 1;
ELSE
DELETE FROM game_follows WHERE user_id = uid and game_id = gid;
follow := 0;
END IF;
SELECT name INTO game_name FROM games WHERE id = gid;
END;
$$
;
Sadly, the function returns empty values. I am using it as this:
SELECT * FROM toggle2(83, 12);
A function declared to RETURN TABLE can return 0-n rows.
You must actively return rows, or nothing will be returned (no row). One way to do this:
RETURN NEXT; -- as last line before END;
There are other ways, see the manual.
However, it seems you want to return exactly one row every time. So rather use OUT parameters:
CREATE OR REPLACE FUNCTION public toggle2(uid numeric, gid numeric, OUT follow int, OUT game_name text) AS ...
Then it's enough to assign those OUT parameters, they are returned in the single result row automatically.
See:
Returning from a function with OUT parameter
plpgsql error "RETURN NEXT cannot have a parameter in function with OUT parameters" in table-returning function
How to return result of a SELECT inside a function in PostgreSQL?

Pass extra parameter to PostgreSQL aggregate final function

Is the only way to pass an extra parameter to the final function of a PostgreSQL aggregate to create a special TYPE for the state value?
e.g.:
CREATE TYPE geomvaltext AS (
geom public.geometry,
val double precision,
txt text
);
And then to use this type as the state variable so that the third parameter (text) finally reaches the final function?
Why aggregates can't pass extra parameters to the final function themselves? Any implementation reason?
So we could easily construct, for example, aggregates taking a method:
SELECT ST_MyAgg(accum_number, 'COMPUTE_METHOD') FROM blablabla
Thanks
You can define an aggregate with more than one parameter.
I don't know if that solves your problem, but you could use it like this:
CREATE OR REPLACE FUNCTION myaggsfunc(integer, integer, text) RETURNS integer
IMMUTABLE STRICT LANGUAGE sql AS
$f$
SELECT CASE $3
WHEN '+' THEN $1 + $2
WHEN '*' THEN $1 * $2
ELSE NULL
END
$f$;
CREATE AGGREGATE myagg(integer, text) (
SFUNC = myaggsfunc(integer, integer, text),
STYPE = integer
);
It could be used like this:
CREATE TABLE mytab
AS SELECT * FROM generate_series(1, 10) i;
SELECT myagg(i, '+') FROM mytab;
myagg
-------
55
(1 row)
SELECT myagg(i, '*') FROM mytab;
myagg
---------
3628800
(1 row)
I solved a similar issue by making a custom aggregate function that did all the operations at once and stored their states in an array.
CREATE AGGREGATE myagg(integer)
(
INITCOND = '{ 0, 1 }',
STYPE = integer[],
SFUNC = myaggsfunc
);
and:
CREATE OR REPLACE FUNCTION myaggsfunc(agg_state integer[], agg_next integer)
RETURNS integer[] IMMUTABLE STRICT LANGUAGE 'plpgsql' AS $$
BEGIN
agg_state[1] := agg_state[1] + agg_next;
agg_state[2] := agg_state[2] * agg_next;
RETURN agg_state;
END;
$$;
Then made another function that selected one of the results based on the second argument:
CREATE OR REPLACE FUNCTION myagg_pick(agg_state integer[], agg_fn character varying)
RETURNS integer IMMUTABLE STRICT LANGUAGE 'plpgsql' AS $$
BEGIN
CASE agg_fn
WHEN '+' THEN RETURN agg_state[1];
WHEN '*' THEN RETURN agg_state[2];
ELSE RETURN 0;
END CASE;
END;
$$;
Usage:
SELECT myagg_pick(myagg("accum_number"), 'COMPUTE_METHOD') FROM "mytable" GROUP BY ...
Obvious downside of this is the overhead of performing all the functions instead of just one. However when dealing with simple operations such as adding, multiplying etc. it should be acceptable in most cases.
You would have to rewrite the final function itself, and in that case you might as well write a set of new aggregate functions, one for each possible COMPUTE_METHOD. If the COMPUTE_METHOD is a data value or implied by a data value, then a CASE statement can be used to select the appropriate aggregate method. Alternatively, you may want to create a custom composite type with fields for accum_number and COMPUTE_METHOD, and write a single new aggregate function that uses this new data type.

Custom aggregate function

I am trying to understand aggregate functions and I need help.
So for instance the following sample:
CREATE OR REPLACE FUNCTION array_median(timestamp[])
RETURNS timestamp AS
$$
SELECT CASE WHEN array_upper($1,1) = 0 THEN null ELSE asorted[ceiling(array_upper(asorted,1)/2.0)] END
FROM (SELECT ARRAY(SELECT ($1)[n] FROM
generate_series(1, array_upper($1, 1)) AS n
WHERE ($1)[n] IS NOT NULL
ORDER BY ($1)[n]
) As asorted) As foo ;
$$
LANGUAGE 'sql' IMMUTABLE;
CREATE AGGREGATE median(timestamp) (
SFUNC=array_append,
STYPE=timestamp[],
FINALFUNC=array_median
)
I am not understanding the structure/logic that needs to go into the select statement in the aggregate function itself. Can someone explain what the flow/logic is?
I am writing an aggregate, a strange one, that the return is always the first string it ever sees.
You're showing a median calculation, but want the first text value you see?
Below is how to do that. Assuming you want the first non-null value, that is. If not, you'll need to keep track of if you've got a value already or not.
The accumulator function is written as plpgsql and sql - the plpgsql one lets you use variable names and debug it too. It simply uses COALESCE against the previous accumulated value and the new value and returns the first non-null. So - as soon as you have a non-null in the accumulator everything else gets ignored.
You may also want to consider the "first_value" window function for this sort of thing if you're on a modern (8.4+) version of PostgreSQL.
http://www.postgresql.org/docs/9.1/static/functions-window.html
HTH
BEGIN;
CREATE FUNCTION remember_first(acc text, newval text) RETURNS text AS $$
BEGIN
RAISE NOTICE '% vs % = %', acc, newval, COALESCE(acc, newval);
RETURN COALESCE(acc, newval);
END;
$$ LANGUAGE plpgsql IMMUTABLE;
CREATE FUNCTION remember_first_sql(text,text) RETURNS text AS $$
SELECT COALESCE($1, $2);
$$ LANGUAGE SQL IMMUTABLE;
-- No "initcond" means we start out with null
--
CREATE AGGREGATE first(text) (
sfunc = remember_first,
stype = text
);
CREATE TEMP TABLE tt (t text);
INSERT INTO tt VALUES ('abc'),('def'),('ghi');
SELECT first(t) FROM tt;
ROLLBACK;