1

I have a table in a PostgreSQL 9.6 database:

CREATE TABLE new_table (
 column_name varchar(255)[],
 name        varchar(40)
);

INSERT INTO new_table VALUES
 ('{one, two}'  , 'first_user'),
 ('{other, two}', 'second_user'),
 ('{one, more}' , 'third_user');

I have an array of arrays (2D-array) and want to find all rows of the table where column_name matches any of the contained 1D-array. I'm after something like this:

select * 
from new_table as s
where s.column_name = any('{{"one", "two"}, {"one", "more"}, {"two", "five"}}')

But this gives me the error:

ERROR:  could not find array type for data type text[]

My desired result:

column_name |    name
------------+--------------
{one,two}   | first_user
{one,more}  | third_user

Any one has any idea?

4
  • If you want to compare (ie query the data), it means you shouldn't be using an array in the first place. You should create a one-to-many table to hold the different values you want for each user Commented Jan 19, 2018 at 15:30
  • Why not disclose your Postgres version? Should go without saying ... Commented Jan 19, 2018 at 23:48
  • @PanagiotisKanavos How do you mean I shouldn't be using a array, in the table or the array of arrays? At the moment I loop through the array and compare the array with '{"one", "two"}' = s.column_name instead, I was just curious I it was possible to do it i one call instead of a lot of calls to the db. Commented Jan 20, 2018 at 8:21
  • @ErwinBrandstetter oh forgot that, has edit the post now. Commented Jan 20, 2018 at 8:22

2 Answers 2

0

Error message

For the record, your superficial error:

ERROR:  could not find array type for data type text[]

.. would go away with an explicit cast. Like:

...
WHERE column_name = any('{{"one", "two"}, {"one", "more"}, {"two", "five"}}'::varchar[])

But that would not solve the underlying problem:

ERROR:  operator does not exist: character varying[] = character varying

Explanation

Postgres support for multi-dimensional arrays can be confusing. In fact, varchar(255)[] resolves to the exact same data type as varchar(255)[][] internally.

The element type of each of these array values is varchar (not varchar[]). The ANY construct effectively ignores array dimensions. The manual:

Array comparisons compare the array contents element-by-element

Solution

To achieve what you want, you need to unnest 2D-arrays to 1D-arrays. Create this function once per database:

CREATE OR REPLACE FUNCTION unnest_2d_1d(ANYARRAY, OUT a ANYARRAY)
  RETURNS SETOF ANYARRAY AS
$func$
BEGIN
   FOREACH a SLICE 1 IN ARRAY $1 LOOP
      RETURN NEXT;
   END LOOP;
END
$func$  LANGUAGE plpgsql IMMUTABLE STRICT;

Further reading:

Now, all of these queries work:

SELECT t.*
FROM   unnest_2d_1d('{{"one", "two"}, {"one", "more"}, {"two", "five"}}'::varchar[]) a(column_name)
JOIN   new_table t USING (column_name);

Or:

SELECT t.*
FROM   new_table t
WHERE  column_name = ANY ((SELECT unnest_2d_1d('{{"one", "two"}, {"one", "more"}, {"two", "five"}}'::varchar[])));

Or:

...
WHERE  column_name IN ((SELECT unnest_2d_1d('{{"one", "two"}, {"one", "more"}, {"two", "five"}}'::varchar[])));

dbfiddle here

Related:

Asides

This is one of the rare cases where a btree index on an array column might be useful:

CREATE INDEX ON new_table(column_name);

There is nothing special about varchar(255) in Postgres. I mostly just use text. See:

Sign up to request clarification or add additional context in comments.

Comments

0

use array operator for work with operators:

t=# select *
from new_table as s
where s.column_name <@ '{{"one", "two"}, {"one", "more"}, {"two", "five"}}';
 column_name |    name
-------------+------------
 {one,two}   | first_user
 {one,more}  | third_user
(2 rows)

I assume https://www.postgresql.org/docs/current/static/functions-comparisons.html#idm46428706346880 could have some notes, that expression is not array and you can't use it for such comparison

update

to be able to "unnest array by 1 dimention" and use the result set for incomarison, use Pavel Stěhule suggested function:

t=# select * from new_table 
where column_name in (
  select reduce_dim('{{one, two}, {two, more}, {one, five}}'::character varying[])
);
 column_name |    name
-------------+------------
 {one,two}   | first_user
(1 row)

2 Comments

Yes this almost works, but I need a method that compare that two arrays are equal, if I use <@ this array '{{"one", "two"}, {"two", "more"}, {"one", "five"}}' will give the same result and in my case I need it to only give one result since {"one", "more"} isn't in it.
@Elmseld I included the slicing array fn(), but there's already Erwins answer ready :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.