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 users (
id TEXT PRIMARY KEY DEFAULT generate_uid(10),
name TEXT NOT NULL,
...
);
And then when inserting data, like so:
INSERT INTO users (name) VALUES ('ian');
INSERT INTO users (name) VALUES ('victor');
SELECT * FROM users;
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 users (
id TEXT PRIMARY KEY DEFAULT ('user_' || generate_uid(10)),
name TEXT NOT NULL,
...
);
INSERT INTO users (name) VALUES ('ian');
INSERT INTO users (name) VALUES ('victor');
SELECT * FROM users;
id | name | ...
---------------+--------+-----
user_wABNZRD5Zk | ian |
user_ISzGcTVj8f | victor |
PostgreSQL Generating Alphanumeric ID from Integer
1) Change int
to BigInt
CREATE OR REPLACE FUNCTION public.generate_uid(
id BigInt,
min_length integer,
salt text)
...
2) Change this line:
output := output || substr(shuffle_alphabet, (id % char_length) + 1, 1);
to:
output := output || substr(shuffle_alphabet::text, ((id % char_length) + 1)::int, 1);
3) And then:
SELECT generate_uid(ceil(extract(epoch from now())*100000)::bigint, 8, '20');
return:
"wExwM7pR"
Generate unique random strings in plpgsql
There is no repeat
statement in plpgsql. Use simple loop
.
CREATE OR REPLACE FUNCTION random_token(_table TEXT, _column TEXT, _length INTEGER) RETURNS text AS $$
DECLARE
alphanum CONSTANT text := 'abcdefghijkmnopqrstuvwxyz23456789';
range_head CONSTANT integer := 25;
range_tail CONSTANT integer := 33;
random_string text;
ct int;
BEGIN
LOOP
SELECT substring(alphanum from trunc(random() * range_head + 1)::integer for 1) ||
array_to_string(array_agg(substring(alphanum from trunc(random() * range_tail + 1)::integer for 1)), '')
INTO random_string FROM generate_series(1, _length - 1);
EXECUTE FORMAT('SELECT count(*) FROM %I WHERE %I = %L', _table, _column, random_string) INTO ct;
EXIT WHEN ct = 0;
END LOOP;
RETURN random_string;
END
$$ LANGUAGE plpgsql;
Note, random_string
should be a parameter to format()
.
Update. According to the accurate hint from Abelisto, this should be faster for a large table:
DECLARE
dup boolean;
...
EXECUTE FORMAT('SELECT EXISTS(SELECT 1 FROM %I WHERE %I = %L)', _table, _column, random_string) INTO dup;
EXIT WHEN NOT dup;
...
Generating an Instagram- or Youtube-like unguessable string ID in ruby/ActiveRecord
You could do something like this:
random_attribute.rb
module RandomAttribute
def generate_unique_random_base64(attribute, n)
until random_is_unique?(attribute)
self.send(:"#{attribute}=", random_base64(n))
end
end
def generate_unique_random_hex(attribute, n)
until random_is_unique?(attribute)
self.send(:"#{attribute}=", SecureRandom.hex(n/2))
end
end
private
def random_is_unique?(attribute)
val = self.send(:"#{attribute}")
val && !self.class.send(:"find_by_#{attribute}", val)
end
def random_base64(n)
val = base64_url
val += base64_url while val.length < n
val.slice(0..(n-1))
end
def base64_url
SecureRandom.base64(60).downcase.gsub(/\W/, '')
end
end
Raw
user.rb
class Post < ActiveRecord::Base
include RandomAttribute
before_validation :generate_key, on: :create
private
def generate_key
generate_unique_random_hex(:key, 32)
end
end
Postgres: Get next free id inside a table in a specific range (not using sequences)
It's a classic case of using generate_series and an outer join:
SELECT i FROM t_user RIGHT JOIN generate_series(0,1000) as i ON (c_id=i)
WHERE c_id is null;
ASP.NET Gridview with Select and its ID being visible or hidden
for this purpose you can use DataKeyNames and DataKeys properties of grid view
Related Topics
Dynamic Pivot Queries with Dynamic Dates as Column Header in SQL Server
How to Iterate Over a Date Range in Pl/Sql
Mysql: Optimizing Finding Super Node in Nested Set Tree
Tuples Are Not Inserted Sequentially in Database Table
Compress Rows with Nulls and Duplicates into Single Rows
Oracle Insert via Select from Multiple Tables Where One Table May Not Have a Row
SQL Like Search String Starts With
SQL Server If Exists Then 1 Else 2
Randomly Select a Row with SQL in Access
Misnamed Field in Subquery Leads to Join
When to Use Grouping Sets, Cube and Rollup
Finding All Children in a Hierarchy SQL
Why Does No Database Fully Support Ansi or Iso SQL Standards
Oracle Query Sequential Summation Per Rows
Trim Spaces in String - Ltrim Rtrim Not Working
SQL Different Between Left Join On... and Left Join On..Where