如何在Postgres的表中为每条记录生成唯一的字符串?

时间:2013-10-23 00:32:33

标签: postgresql

假设我有一个像帖子一样的表,它有典型的列,如id,body,created_at。我希望通过创建每个帖子生成一个唯一的字符串,用于类似url shortener的东西。也许是一个10个字符的字母数字字符串。它必须在表中是唯一的,就像主键一样。

理想情况下,Postgres有办法处理这两个问题:

  1. 生成字符串
  2. 确保其独特性
  3. 而且它们必须齐头并进,因为我的目标是不必担心我的应用程序中任何具有唯一性的代码。

6 个答案:

答案 0 :(得分:10)

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

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

答案 1 :(得分:5)

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

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

对于基于bigint9223372036854775808不同的可能值)的63位版本,请参阅this other answer

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

答案 2 :(得分:4)

最简单的方法可能是使用序列来保证唯一性 (所以在seq之后添加一个修复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: Is there a function that will convert a base-10 int into a base-36 string?

答案 3 :(得分:3)

查看布鲁斯的博客。这可以帮助你实现目标。您必须确保它尚不存在。也许连接它的主键?

Generating Random Data Via 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);

答案 4 :(得分:3)

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

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

答案 5 :(得分:0)

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

示例:

select base36_encode(generate_series(1000000000,1000000010));

GJDGXS
GJDGXT
GJDGXU
GJDGXV
GJDGXW
GJDGXX
GJDGXY
GJDGXZ
GJDGY0
GJDGY1
GJDGY2