我在StackOverflow上看到了一堆different solutions多年来和许多Postgres版本,但有一些较新的功能,如gen_random_bytes
我想再问一下,看看是否有在较新版本中是一个更简单的解决方案。
鉴于包含a-zA-Z0-9
的ID,并根据他们使用的位置而变化,例如......
bTFTxFDPPq
tcgHAdW3BD
IIo11r9J0D
FUW5I8iCiS
uXolWvg49Co5EfCo
LOscuAZu37yV84Sa
YyrbwLTRDb01TmyE
HoQk3a6atGWRMCSA
HwHSZgGRStDMwnNXHk3FmLDEbWAHE1Q9
qgpDcrNSMg87ngwcXTaZ9iImoUmXhSAv
RVZjqdKvtoafLi1O5HlvlpJoKzGeKJYS
3Rls4DjWxJaLfIJyXIEpcjWuh51aHHtK
(与IDs that Stripe uses一样。)
如何在Postgres 9.6 +中为随机安全地生成它们(尽可能减少冲突并降低可预测性),并为不同的用例指定不同的长度,
我认为理想情况下解决方案的签名类似于:
generate_uid(size integer) returns text
size
可根据您自己的权衡来定制,以降低碰撞机会与减少字符串大小以实现可用性。
据我所知,它必须使用gen_random_bytes()
代替random()
才能获得真正的随机性,以减少猜测它们的可能性。
谢谢!
我知道UUID有gen_random_uuid()
,但我不想在这种情况下使用它们。我正在寻找能够为我提供类似于Stripe(或其他人)使用的ID的内容,它们看起来像:"id": "ch_19iRv22eZvKYlo2CAxkjuHxZ"
尽可能短,同时仍然只包含字母数字字符。
这个要求也是encode(gen_random_bytes(), 'hex')
在这种情况下不正确的原因,因为它减少了字符集,因此迫使我增加字符串的长度以避免冲突。
我目前正在应用层中执行此操作,但我希望将其移至数据库层以减少相互依赖性。以下是在应用程序层中执行此操作的Node.js代码可能如下所示:
var crypto = require('crypto');
var set = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
function generate(length) {
var bytes = crypto.randomBytes(length);
var chars = [];
for (var i = 0; i < bytes.length; i++) {
chars.push(set[bytes[i] % set.length]);
}
return chars.join('');
}
答案 0 :(得分:3)
评分,
[a-z]
[A-Z]
[0-9]
[a-zA-Z0-9]
(base62)substring(string [from int] [for int])
看起来很有用。所以它看起来像这样。首先,我们证明我们可以采用随机范围并从中拉出来。
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
1, -- 1 is 'a', 62 is '9'
1,
);
现在我们需要1
和63
SELECT trunc(random()*62+1)::int+1
FROM generate_series(1,1e2) AS gs(x)
这让我们在那里..现在我们只需加入这两个......
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1
1
)
FROM generate_series(1,1e2) AS gs(x);
然后我们将它包装在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)
);
并且,我们致电array_to_string()
获取文字。
SELECT array_to_string(
ARRAY(
SELECT substring(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
trunc(random()*62)::int+1,
1
)
FROM generate_series(1,1e2) AS gs(x)
)
, ''
);
从这里我们甚至可以把它变成一个函数..
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 SQL
RETURNS NULL ON NULL INPUT
VOLATILE LEAKPROOF;
然后
SELECT * FROM random_string(10);
答案 1 :(得分:1)
此查询生成所需的字符串。只需更改generate_series的第二个寄生计,即可选择随机字符串的长度。
SELECT
string_agg(c, '')
FROM (
SELECT
chr(r + CASE WHEN r > 25 + 9 THEN 97 - 26 - 9 WHEN r > 9 THEN 64 - 9 ELSE 48 END) AS c
FROM (
SELECT
i,
(random() * 60)::int AS r
FROM
generate_series(0, 62) AS i
) AS a
ORDER BY i
) AS A;
答案 2 :(得分:1)
我正在寻找能够提供“短代码”(类似于Youtube用于视频ID的内容)的内容,它们尽可能短,同时仍然只包含字母数字字符。
这与你刚才提出的问题有着根本不同的问题。那么你想要的是在表格上放置serial
类型,并使用hashids.org code for PostgreSQL。
[a-zA-Z0-9]
代码看起来像这样,
SELECT id, hash_encode(foo.id)
FROM foo; -- Result: jNl for 1001
SELECT hash_decode('jNl') -- returns 1001
该模块也支持盐。
答案 3 :(得分:1)
想出这个,这里有一个功能:
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;
然后运行它只需:
generate_uid(10)
-- '3Rls4DjWxJ'
执行此操作时,您需要确保您创建的ID的长度足以避免随着您创建的对象数量的增加而发生冲突,这可能会因为{而违反{ {3}}。 因此,对于任何合理常用的对象,您可能需要比10
更长(或更大)的长度,我只使用10
作为一个简单示例。
定义了函数后,您可以在表定义中使用它,如下所示:
CREATE TABLE collections (
id TEXT PRIMARY KEY DEFAULT 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
值:
id | name | ...
-----------+--------+-----
owmCAx552Q | ian |
ZIofD6l3X9 | victor |
或者,您可能希望在查看日志或调试器中的单个ID(类似于Birthday Paradox)时为方便起见添加前缀,如下所示:
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 |
答案 4 :(得分:1)
感谢Evan Carroll的回答,我看了hashids.org。 对于Postgres,您必须编译extension或运行一些TSQL functions。 但出于我的需要,我根据哈希值的想法(简短,难以猜测,唯一,自定义字母,避免使用诅咒词)创建了一些更简单的东西。
随机播放字母:
CREATE OR REPLACE FUNCTION consistent_shuffle(alphabet TEXT, salt TEXT) RETURNS TEXT AS $$
DECLARE
SALT_LENGTH INT := length(salt);
integer INT = 0;
temp TEXT = '';
j INT = 0;
v INT := 0;
p INT := 0;
i INT := length(alphabet) - 1;
output TEXT := alphabet;
BEGIN
IF salt IS NULL OR length(LTRIM(RTRIM(salt))) = 0 THEN
RETURN alphabet;
END IF;
WHILE i > 0 LOOP
v := v % SALT_LENGTH;
integer := ASCII(substr(salt, v + 1, 1));
p := p + integer;
j := (integer + v + p) % i;
temp := substr(output, j + 1, 1);
output := substr(output, 1, j) || substr(output, i + 1, 1) || substr(output, j + 2);
output := substr(output, 1, i) || temp || substr(output, i + 2);
i := i - 1;
v := v + 1;
END LOOP;
RETURN output;
END;
$$ LANGUAGE plpgsql VOLATILE;
主要功能:
CREATE OR REPLACE FUNCTION generate_uid(id INT, min_length INT, salt TEXT) RETURNS TEXT AS $$
DECLARE
clean_alphabet TEXT := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
curse_chars TEXT := 'csfhuit';
curse TEXT := curse_chars || UPPER(curse_chars);
alphabet TEXT := regexp_replace(clean_alphabet, '[' || curse || ']', '', 'gi');
shuffle_alphabet TEXT := consistent_shuffle(alphabet, salt);
char_length INT := length(alphabet);
output TEXT := '';
BEGIN
WHILE id != 0 LOOP
output := output || substr(shuffle_alphabet, (id % char_length) + 1, 1);
id := trunc(id / char_length);
END LOOP;
curse := consistent_shuffle(curse, output || salt);
output := RPAD(output, min_length, curse);
RETURN output;
END;
$$ LANGUAGE plpgsql VOLATILE;
使用方法示例:
-- 3: min-length
select generate_uid(123, 3, 'salt'); -- output: "0mH"
-- or as default value in a table
CREATE SEQUENCE IF NOT EXISTS my_id_serial START 1;
CREATE TABLE collections (
id TEXT PRIMARY KEY DEFAULT generate_uid(CAST (nextval('my_id_serial') AS INTEGER), 3, 'salt')
);
insert into collections DEFAULT VALUES ;
答案 5 :(得分:0)
因此,对于类似这样的事情,我有自己的用例。我不是要提出一个最重要的问题的解决方案,但是如果您正在寻找类似我的东西,请尝试一下。
我的用例是,我需要创建一个随机的外部UUID(作为主键),并使用尽可能少的字符。值得庆幸的是,该方案并不需要大量的这些需求(可能只有数千个)。因此,一个简单的解决方案是结合使用generate_uid()
和检查以确保没有使用下一个序列的方法。
这是我将其组合在一起的方式:
CREATE OR REPLACE FUNCTION generate_id (
in length INT
, in for_table text
, in for_column text
, OUT next_id TEXT
) AS
$$
DECLARE
id_is_used BOOLEAN;
loop_count INT := 0;
characters TEXT := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
loop_length INT;
BEGIN
LOOP
next_id := '';
loop_length := 0;
WHILE loop_length < length LOOP
next_id := next_id || substr(characters, get_byte(gen_random_bytes(length), loop_length) % length(characters) + 1, 1);
loop_length := loop_length + 1;
END LOOP;
EXECUTE format('SELECT TRUE FROM %s WHERE %s = %s LIMIT 1', for_table, for_column, quote_literal(next_id)) into id_is_used;
EXIT WHEN id_is_used IS NULL;
loop_count := loop_count + 1;
IF loop_count > 100 THEN
RAISE EXCEPTION 'Too many loops. Might be reaching the practical limit for the given length.';
END IF;
END LOOP;
END
$$
LANGUAGE plpgsql
STABLE
;
这是表格用法的示例:
create table some_table (
id
TEXT
DEFAULT generate_id(6, 'some_table', 'id')
PRIMARY KEY
)
;
并进行测试以了解其破坏方式:
DO
$$
DECLARE
loop_count INT := 0;
BEGIN
-- WHILE LOOP
WHILE loop_count < 1000000
LOOP
INSERT INTO some_table VALUES (DEFAULT);
loop_count := loop_count + 1;
END LOOP;
END
$$ LANGUAGE plpgsql
;