109

I have the following simplified table in Postgres:

  • User Model
    • id (UUID)
    • uid (varchar)
    • name (varchar)

I would like a query that can find the user on either its UUID id or its text uid.

SELECT * FROM user
WHERE id = 'jsdfhiureeirh' or uid = 'jsdfhiureeirh';

My query generates an invalid input syntax for uuid since I'm obviously not using a UUID in this instance.

How do I polish this query or check if the value is a valid UUID?

Wainage
  • 4,892
  • 3
  • 12
  • 22

4 Answers4

198

Found it! Casting the UUID column to ::text stops the error. Not sure about the performance hit but on about 5000 rows I get more than adequate performance.

SELECT * FROM user
WHERE id::text = 'jsdfhiureeirh' OR uid = 'jsdfhiureeirh';

SELECT * FROM user
WHERE id::text = '33bb9554-c616-42e6-a9c6-88d3bba4221c' 
  OR uid = '33bb9554-c616-42e6-a9c6-88d3bba4221c';
Wainage
  • 4,892
  • 3
  • 12
  • 22
  • this works for me, not `unhex` or another functions – Tiana987642 Nov 22 '22 at 09:14
  • This works and it works great for anything with 5000 entries. I work on a database with 6mio entries and this costs 2.6 seconds on average. My solution to this is to use a regex and based on its result, execute a different query: `IF your_uuid_var ~ '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' THEN ELSE END IF;`. In my case, time for planning increased by a factor of 5 and time for execution reduced by a factor of 309. – Alb May 03 '23 at 14:15
27

I had originally misunderstood the question. If you want to "safely" try to cast a string to a UUID, you can write a function to catch the invalid_text_representation exception and just return null (modified from an answer to a different question):

CREATE OR REPLACE FUNCTION uuid_or_null(str text)
RETURNS uuid AS $$
BEGIN
  RETURN str::uuid;
EXCEPTION WHEN invalid_text_representation THEN
  RETURN NULL;
END;
$$ LANGUAGE plpgsql;

SELECT uuid_or_null('INVALID') IS NULL will then result in true.

In other words (given that (true or null) = true),

SELECT * FROM user
WHERE id = uuid_or_null('FOOBARBAZ') OR uid = 'FOOBARBAZ';

Original answer:

Postgres will automatically convert the string to a UUID for you, but you need to use a valid UUID. For example:

SELECT * FROM user
WHERE id = '5af75c52-cb8e-44fb-93c8-1d46da518ee6' or uid = 'jsdfhiureeirh';

You can also let Postgres generate UUIDs for you using a DEFAULT clause with the uuid_generate_v4() function by using the uuid-ossp extension:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE user (  
   id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
   uid TEXT,
   name TEXT
);
Josh Bowden
  • 5,735
  • 2
  • 25
  • 36
  • I have unknown ```value``` coming in (merging legacy system with modern one) hence the shape of my query (not sure where the ID could be coming from). I can obviously do this on the backend but was hoping for a SQL solution – Wainage Sep 26 '17 at 18:46
  • @Wainage thanks for clarifying, I've update my answer – Josh Bowden Sep 26 '17 at 21:11
  • 1
    maybe ```SELECT * FROM user WHERE id = uuid_or_null('FOOBARBAZ') OR uid = 'FOOBARBAZ'```?.. id::uuid – Vao Tsun Sep 27 '17 at 07:48
  • 2
    On 2 million rows, converting ID to string to query took 600ms and doing the above instead took 18 seconds. – red6 Jul 29 '19 at 23:36
  • 1
    @red6 I'm not entirely surprised (as I've learned more working with Postgres the past couple years), the overhead of running a PL/pgSQL function on every row is going to be much slower than Postgres internally converting to `text`. Perhaps someone might find this pattern useful for other types that you can't do the same, but yes, I'd recommend @Wainage answer for performance in this case. – Josh Bowden Aug 07 '19 at 15:54
  • 1
    I have verified that the uuid_or_null function works on the system that I use. Though somewhat simple, it really is very needed. – JosephDoggie Jan 13 '22 at 16:23
16

You could check with a regular expression:

SELECT *
FROM user
WHERE ('jsdfhiureeirh' ~ E'^[[:xdigit:]]{8}-([[:xdigit:]]{4}-){3}[[:xdigit:]]{12}$'
       AND id = 'jsdfhiureeirh')
      OR uid = 'jsdfhiureeirh';
Laurenz Albe
  • 209,280
  • 17
  • 206
  • 263
  • 3
    This answer should definitely be considered by more people. Stored procedures are not as confortable to work with for everyone. In my situation, I'd strongly prefer to be able to just modify a query. My problem was that I had a text column, where sometimes I'd have UUIDs, but sometimes, other data formats. When I have UUIDs, I know that the join should work. This answer was perfect in my situation, because it avoids the casting error completely – vlad-ardelean Mar 07 '18 at 10:07
  • 2
    In my case, this solution was much faster, thanks for the help! – pilec Jan 26 '21 at 04:28
  • 2
    Execution order isn't guaranteed in Postgres, so you can't be certain it will short-circuit in the event that the Regex doesn't match: https://www.postgresql.org/docs/10/sql-expressions.html#SYNTAX-EXPRESS-EVAL You could use a CASE statement instead to guarantee it happens in the right order. – Ecksters Jun 30 '21 at 18:57
4

In stead of using ::text, use cast(uid as text), and build an index on that expression then Postgres recignozied it for very fast querying. We do that for sharding/partitioning building an index like this

Create index idx_partition256 on using btree ((right(cast(id as text), 2)));

You can use ::text, but then it is not possible to use I.e JPA since it will be confused with parameter replacement.

Query in JPA could be

“… where function(‘right’ cast(id as text), 2) in (…) …”

neko
  • 51
  • 5
  • 2
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 08 '21 at 03:00
  • 2
    i'm interested in this answer, but don't totally understand it. I could use a more complete example. I don't understand what `right` is above, or what the `2` argument is. – jrochkind Nov 29 '21 at 19:22