How to generate a random, unique, alphanumeric ID of length N in Postgres 9.6+? How to generate a random, unique, alphanumeric ID of length N in Postgres 9.6+? database database

How to generate a random, unique, alphanumeric ID of length N in Postgres 9.6+?


Figured this out, here's a function that does it:

CREATE OR REPLACE FUNCTION generate_uid(size INT) RETURNS TEXT AS $$DECLARE  characters TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';  bytes BYTEA := gen_random_bytes(size);  l INT := length(characters);  i INT := 0;  output TEXT := '';BEGIN  WHILE i < size LOOP    output := output || substr(characters, get_byte(bytes, i) % l + 1, 1);    i := i + 1;  END LOOP;  RETURN output;END;$$ LANGUAGE plpgsql VOLATILE;

And then to run it simply do:

generate_uid(10)-- '3Rls4DjWxJ'

Warning

When doing this you need to be sure that the length of the IDs you are creating is sufficient to avoid collisions over time as the number of objects you've created grows, which can be counter-intuitive because of the Birthday Paradox. So you will likely want a length greater (or much greater) than 10 for any reasonably commonly created object, I just used 10 as a simple example.


Usage

With the function defined, you can use it in a table definition, like so:

CREATE TABLE collections (  id TEXT PRIMARY KEY DEFAULT generate_uid(10),  name TEXT NOT NULL,  ...);

And then when inserting data, like so:

INSERT INTO collections (name) VALUES ('One');INSERT INTO collections (name) VALUES ('Two');INSERT INTO collections (name) VALUES ('Three');SELECT * FROM collections;

It will automatically generate the id values:

    id     |  name  | ...-----------+--------+-----owmCAx552Q | ian    |ZIofD6l3X9 | victor |

Usage with a Prefix

Or maybe you want to add a prefix for convenience when looking at a single ID in the logs or in your debugger (similar to how Stripe does it), like so:

CREATE TABLE collections (  id TEXT PRIMARY KEY DEFAULT ('col_' || generate_uid(10)),  name TEXT NOT NULL,  ...);INSERT INTO collections (name) VALUES ('One');INSERT INTO collections (name) VALUES ('Two');INSERT INTO collections (name) VALUES ('Three');SELECT * FROM collections;      id       |  name  | ...---------------+--------+-----col_wABNZRD5Zk | ian    |col_ISzGcTVj8f | victor |


I'm looking for something that gives me "shortcodes" (similar to what Youtube uses for video IDs) that are as short as possible while still containing only alphanumeric characters.

This is a fundamentally different question from what you first asked. What you want here then is to put a serial type on the table, and to use hashids.org code for PostgreSQL.

  • This returns 1:1 with the unique number (serial)
  • Never repeats or has a chance of collision.
  • Also base62 [a-zA-Z0-9]

Code looks like this,

SELECT id, hash_encode(foo.id)FROM foo; -- Result: jNl for 1001SELECT hash_decode('jNl') -- returns 1001

This module also supports salts.


Review,

  1. 26 characters in [a-z]
  2. 26 characters in [A-Z]
  3. 10 characters in [0-9]
  4. 62 characters in [a-zA-Z0-9] (base62)
  5. The function substring(string [from int] [for int]) looks useful.

So it looks something like this. First we demonstrate that we can take the random-range and pull from it.

SELECT substring(  'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',  1, -- 1 is 'a', 62 is '9'  1,);

Now we need a range between 1 and 63

SELECT trunc(random()*62+1)::int+1FROM generate_series(1,1e2) AS gs(x)

This gets us there.. Now we just have to join the two..

SELECT substring(  'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',  trunc(random()*62)::int+1  1)FROM generate_series(1,1e2) AS gs(x);

Then we wrap it in an ARRAY constructor (because this is fast)

SELECT ARRAY(  SELECT substring(    'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',    trunc(random()*62)::int+1,    1  )  FROM generate_series(1,1e2) AS gs(x));

And, we call array_to_string() to get a text.

SELECT array_to_string(  ARRAY(      SELECT substring(        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',        trunc(random()*62)::int+1,        1      )      FROM generate_series(1,1e2) AS gs(x)  )  , '');

From here we can even turn it into a function..

CREATE FUNCTION random_string(randomLength int)RETURNS text AS $$SELECT array_to_string(  ARRAY(      SELECT substring(        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',        trunc(random()*62)::int+1,        1      )      FROM generate_series(1,randomLength) AS gs(x)  )  , '')$$ LANGUAGE SQLRETURNS NULL ON NULL INPUTVOLATILE LEAKPROOF;

and then

SELECT * FROM random_string(10);