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?

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
Elmseld
  • 83
  • 12
  • 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 – Panagiotis Kanavos Jan 19 '18 at 15:30
  • Why not disclose your Postgres version? Should go without saying ... – Erwin Brandstetter Jan 19 '18 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. – Elmseld Jan 20 '18 at 08:21
  • @ErwinBrandstetter oh forgot that, has edit the post now. – Elmseld Jan 20 '18 at 08:22

2 Answers2

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)
Vao Tsun
  • 47,234
  • 13
  • 100
  • 132
  • 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 Jan 20 '18 at 08:12
  • @Elmseld I included the slicing array fn(), but there's already Erwins answer ready :) – Vao Tsun Jan 20 '18 at 14:41
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:

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228