Как я могу создать уникальную строку для каждой записи в таблице в Postgres?

Скажем, у меня есть стол, подобный сообщениям, который имеет типичные столбцы, такие как id, body, created_at. Я хотел бы создать уникальную строку с созданием каждого сообщения, для использования в чем-то вроде укорачивания URL. Так может быть, 10-значная буквенно-цифровая строка. Он должен быть уникальным в таблице, как первичный ключ.

В идеале для Postgres был бы способ решить обе эти проблемы:

  • создать строку
  • обеспечить его уникальность

И они должны идти рука об руку, потому что моя цель состоит в том, чтобы не беспокоиться о каком-либо кодексе, обеспечивающем уникальность в моем приложении.

Ответы

Ответ 1

Я не утверждаю, что следующее эффективно, но мы так делали это в прошлом.

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'), за исключением того, что некоторые символы, используемые в базе-64, не являются дружественными URL.

Ответ 2

Используйте сеть Feistel. Этот метод эффективно работает для создания уникальных случайных строк в постоянное время без какого-либо столкновения.

Для версии с примерно 2 миллиардами возможных строк (2^31) из 6 букв см. этот ответ.

Для версии с 63 битами, основанной на bigint (9223372036854775808 различных возможных значениях), см. этот другой ответ.

Вы можете изменить круглую функцию, как объяснялось в первом ответе, чтобы ввести секретный элемент, чтобы иметь собственную серию строк (не догадаться).

Ответ 3

Посмотрите блог от Брюса. Это дает вам часть пути. Вам нужно будет убедиться, что он еще не существует. Может быть, примирить первичный ключ?

Генерация случайных данных через 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

Самый простой способ, вероятно, использовать последовательность, чтобы гарантировать уникальность (поэтому после 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;

Однако это будет тратить огромное количество записей. (Примечание: max bigInt - 9223372036854775807, если вы используете 8-значное случайное число в конце, вы можете иметь только записи 922337203. Возможно, вам не нужно 8 цифр. Также проверьте максимальное количество для вашей среды программирования!)

В качестве альтернативы вы можете использовать varchar для идентификатора и даже преобразовать вышеуказанный номер с помощью to_hex() или изменить на base36, как показано ниже (но для base36, не пытайтесь разоблачить его для клиента, чтобы избежать появления какой-нибудь забавной строки! ):

PostgreSQL: есть ли функция, которая преобразует base-10 int в строку base-36?

Ответ 5

Используйте первичный ключ в своих данных. Если вам действительно нужна алфавитно-цифровая уникальная строка, вы можете использовать кодировку base-36. В PostgreSQL вы можете использовать эту функцию.

Пример:

select base36_encode(generate_series(1000000000,1000000010));

GJDGXS
GJDGXT
GJDGXU
GJDGXV
GJDGXW
GJDGXX
GJDGXY
GJDGXZ
GJDGY0
GJDGY1
GJDGY2