假设我有一个类似帖子的表,其中包含 id、body、created_at 等典型列。我想在创建每个帖子时生成一个唯一的字符串,用于类似 url 缩短器之类的东西。所以可能是一个 10 个字符的字母数字字符串。它需要在表中是唯一的,就像主键一样。
理想情况下,Postgres 将有一种方法来处理这两个问题:
- 生成字符串
- 确保其唯一性
它们必须齐头并进,因为我的目标是不必担心我的应用程序中的任何唯一性强制代码。
假设我有一个类似帖子的表,其中包含 id、body、created_at 等典型列。我想在创建每个帖子时生成一个唯一的字符串,用于类似 url 缩短器之类的东西。所以可能是一个 10 个字符的字母数字字符串。它需要在表中是唯一的,就像主键一样。
理想情况下,Postgres 将有一种方法来处理这两个问题:
它们必须齐头并进,因为我的目标是不必担心我的应用程序中的任何唯一性强制代码。
我不认为以下是有效的,但这是我们过去做这类事情的方式。
CREATE FUNCTION make_uid() RETURNS text AS $$
DECLARE
new_uid text;
done bool;
BEGIN
done := false;
WHILE NOT done LOOP
new_uid := md5(''||now()::text||random()::text);
done := NOT exists(SELECT 1 FROM my_table WHERE uid=new_uid);
END LOOP;
RETURN new_uid;
END;
$$ LANGUAGE PLPGSQL VOLATILE;
make_uid()
可以用作 中列的默认值my_table
。就像是:
ALTER TABLE my_table ADD COLUMN uid text NOT NULL DEFAULT make_uid();
md5(''||now()::text||random()::text)
可以根据口味调整。您可以考虑encode(...,'base64')
,除了 base-64 中使用的某些字符不是 URL 友好的。
所有现有答案都是错误的,因为它们基于SELECT,同时为每个表记录生成唯一索引。让我们假设在插入时我们需要每条记录的唯一代码:想象两个并发 INSERT 奇迹般地同时发生(这比您想象的经常发生),因为在 SELECT 时刻该代码不存在,因此生成了相同的代码在表中。一个实例将插入,而另一个实例将失败。
首先让我们创建带有代码字段的表并添加唯一索引
CREATE TABLE my_table
(
code TEXT NOT NULL
);
CREATE UNIQUE INDEX ON my_table (lower(code));
然后我们应该有函数或过程(您也可以使用内部代码作为触发器),其中我们1. 生成新代码,2. 尝试使用新代码插入新记录,以及 3. 如果插入失败,请从步骤 1 重试
CREATE OR REPLACE PROCEDURE my_table_insert()
AS $$
DECLARE
new_code TEXT;
BEGIN
LOOP
new_code := LOWER(SUBSTRING(MD5(''||NOW()::TEXT||RANDOM()::TEXT) FOR 8));
BEGIN
INSERT INTO my_table (code) VALUES (new_code);
EXIT;
EXCEPTION WHEN unique_violation THEN
END;
END LOOP;
END;
$$ LANGUAGE PLPGSQL;
与此线程上的其他解决方案不同,这是保证无错误的解决方案
最简单的方法可能是使用序列来保证唯一性(所以在序列之后添加一个固定的 x 位随机数):
CREATE SEQUENCE test_seq;
CREATE TABLE test_table (
id bigint NOT NULL DEFAULT (nextval('test_seq')::text || (LPAD(floor(random()*100000000)::text, 8, '0')))::bigint,
txt TEXT
);
insert into test_table (txt) values ('1');
insert into test_table (txt) values ('2');
select id, txt from test_table;
然而,这将浪费大量的记录。(注意:最大 bigInt 为 9223372036854775807 如果最后使用 8 位随机数,则只能有 922337203 条记录。您可能不需要 8 位。还要检查您的编程环境的最大数!)
或者,您可以使用 varchar 作为 id,甚至使用 to_hex() 转换上述数字或更改为 base36,如下所示(但对于 base36,尽量不要将其暴露给客户,以避免出现一些有趣的字符串!):
查看 Bruce 的博客。这会让你分道扬镳。您必须确保它不存在。也许连接它的主键?
“曾经需要生成随机数据吗?您可以在客户端应用程序和服务器端函数中轻松完成,但可以在 sql 中生成随机数据。以下查询生成五行 40 个字符长度的小写字母字符串:”
SELECT
(
SELECT string_agg(x, '')
FROM (
SELECT chr(ascii('a') + floor(random() * 26)::integer)
FROM generate_series(1, 40 + b * 0)
) AS y(x)
)
FROM generate_series(1,5) as a(b);
在数据中使用主键。如果您确实需要字母数字唯一字符串,则可以使用 base-36 编码。在 PostgreSQL 中你可以使用这个函数。
例子:
select base36_encode(generate_series(1000000000,1000000010));
GJDGXS
GJDGXT
GJDGXU
GJDGXV
GJDGXW
GJDGXX
GJDGXY
GJDGXZ
GJDGY0
GJDGY1
GJDGY2