Question

How can I cast an array of texts into an array of UUIDs?

I need to do a join between two tables: users and projects.

The users table has an array field named project_ids containing the project IDs as text.

The projects table had a UUID field named id.

My initial idea was a query looks like:

SELECT * FROM projects
JOIN users ON
projects.id = ANY(users.project_ids)

But that does not work since users.project_ids are not UUIDs so I tried:

projects.id = ANY(users.project_ids::uuid[])

and even:

projects.id = ANY(ARRAY[users.project_ids]::uuid[])

but neither one works:

ERROR: invalid input syntax for type uuid: ""

UPDATE

@a_horse_with_no_name is definitely right. The best option should be using an array of UUIDs.

The question now is how can I alter an array of text into an array of uuid?

The users table is currently empty (0 records).

I have tried

ALTER TABLE "users" ALTER COLUMN "project_ids" SET DATA TYPE UUID USING "project_ids"::uuid[];

which generates

ERROR: result of USING clause for column "product_ids" cannot be cast automatically to type uuid HINT: You might need to add an explicit cast.

ALTER TABLE "users" ALTER COLUMN "product_ids" SET DATA TYPE UUID USING "product_ids"::UUID;

I have also tried

ALTER TABLE "users" ALTER COLUMN "project_ids" SET DATA TYPE UUID[] USING "project_ids"::uuid[];

which generates

ERROR: default for column "project_ids" cannot be cast automatically to type uuid[]

The column is set to an empty array as default.

I'm running PG version 10.4 and project_ids is currently text[] nullable.

Was it helpful?

Solution

Like has been commented, the column project_ids should be uuid[], which would preclude the problem. It would also be more efficient.

To change (with no illegal data in column like you asserted):

ALTER TABLE users ALTER COLUMN project_ids DROP DEFAULT;

ALTER TABLE users
ALTER COLUMN project_ids SET DATA TYPE uuid[] USING project_ids::uuid[];

You had uuid instead of uuid[] by mistake.

And this:

ERROR: default for column "product_ids" cannot be cast automatically to type uuid[]

.. means you have a default value set for the column. That expression cannot be transformed automatically. Remove it before altering the type. You can add a new DEFAULT later.

Fix to original problem

The efficient fix in your original situation is to remove empty strings from the array with array_remove() before the cast (requires Postgres 9.3+):

SELECT *
FROM   users    u
JOIN   projects p ON p.id = ANY(array_remove(u.project_ids, '')::uuid[]);

... after investigating why there can be empty stings in that text[] column.

Related:

Fine points

  • The [INNER] JOIN in your query removes users without valid projects in projects_ids from the result. Typically, you'd want to keep those, too: use LEFT [OUTER] JOIN instead (with users first).

  • The JOIN folds duplicate entries either way, which may or may not be as desired. If you want to represent duplicate entries, unnest before the join instead.

And if your aim is simply to resolve the array of IDs to an array of project names, you'll also want to preserve original order of array elements:

SELECT *
FROM   users u
LEFT   JOIN LATERAL (
   SELECT ARRAY(
      SELECT project_name  -- use the actual column(s) of your case
      FROM   unnest (array_remove(u.project_ids, '')::uuid[]) WITH ORDINALITY AS p(id, ord)
      JOIN   projects USING (id)
      ORDER  BY ord
      )
   ) p(projects) ON true;

db<>fiddle here (loosely based on McNets' fiddle)

Related:

OTHER TIPS

create table users (user_id int, projects text[]);

insert into users values
(1, (array['a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
           'b0eebc99-9c0b-4ef8-bb6d-cbb9bd380a11'])::text[]);

insert into users values
(2, (array['a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
           'b0eebc99-9c0b-4ef8-bb6d-cbb9bd380a11',
           'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'])::text[]);

insert into users values
(3, (array['f0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11',
           '',
           'e0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'])::text[]);
create table projects (project_id uuid);

insert into projects values
('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid),
('d0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid),
('e0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid);

As a_horse_with_no_name has pointed out, it fails if some array element has no value. It cannot be converted.

select user_id, project_id
from   projects
join   users
on     project_id = any(projects::uuid[])
ERROR:  invalid input syntax for type uuid: ""

However, you could try to cast project_id as text in this way:

select user_id, project_id
from   projects
join   users
on     project_id::text = any(projects);
user_id | project_id                          
------: | :-----------------------------------
      1 | a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
      2 | a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
      3 | e0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11

db<>fiddle here

Or you could expand the array (unnest) and avoid empty/null values:

with usr as
(
    select user_id, unnest(projects) as project_id
    from   users
)
select user_id, projects.project_id
from   projects
join   usr
on     coalesce(usr.project_id, '') <> ''
and    usr.project_id::uuid = projects.project_id;
user_id | project_id                          
------: | :-----------------------------------
      2 | a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
      1 | a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
      3 | e0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11

db<>fiddle here

Licensed under: CC-BY-SA with attribution
Not affiliated with dba.stackexchange
scroll top