27

假设我有一个类似帖子的表,其中包含 id、body、created_at 等典型列。我想在创建每个帖子时生成一个唯一的字符串,用于类似 url 缩短器之类的东西。所以可能是一个 10 个字符的字母数字字符串。它需要在表中是唯一的,就像主键一样。

理想情况下,Postgres 将有一种方法来处理这两个问题:

  1. 生成字符串
  2. 确保其唯一性

它们必须齐头并进,因为我的目标是不必担心我的应用程序中的任何唯一性强制代码。

4

6 回答 6

17

我不认为以下是有效的,但这是我们过去做这类事情的方式。

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 友好的。

于 2013-10-23T03:15:16.847 回答
13

所有现有答案都是错误的,因为它们基于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;

与此线程上的其他解决方案不同,这是保证无错误的解决方案

于 2018-11-05T13:16:13.360 回答
7

使用 Feistel 网络。这种技术可以有效地在恒定时间内生成独特的随机字符串,而不会发生任何冲突。

2^31对于包含6 个字母的大约 20 亿个可能字符串 () 的版本,请参阅此答案

对于基于bigint9223372036854775808不同的可能值)的 63 位版本,请参阅其他答案

您可以按照第一个答案中的说明更改轮函数,以引入一个秘密元素以拥有自己的一系列字符串(不可猜测)。

于 2014-12-16T21:40:16.503 回答
5

最简单的方法可能是使用序列来保证唯一性(所以在序列之后添加一个固定的 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,尽量不要将其暴露给客户,以避免出现一些有趣的字符串!):

PostgreSQL:有没有一个函数可以将 base-10 int 转换为 base-36 字符串?

于 2016-08-09T08:59:01.607 回答
4

查看 Bruce 的博客。这会让你分道扬镳。您必须确保它不存在。也许连接它的主键?

通过 Sql 生成随机数据

“曾经需要生成随机数据吗?您可以在客户端应用程序和服务器端函数中轻松完成,但可以在 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);
于 2013-10-23T01:14:12.667 回答
0

在数据中使用主键。如果您确实需要字母数字唯一字符串,则可以使用 base-36 编码。在 PostgreSQL 中你可以使用这个函数。

例子:

select base36_encode(generate_series(1000000000,1000000010));

GJDGXS
GJDGXT
GJDGXU
GJDGXV
GJDGXW
GJDGXX
GJDGXY
GJDGXZ
GJDGY0
GJDGY1
GJDGY2
于 2013-10-23T13:03:30.110 回答