url 的 postgresql 唯一非连续 id
postgresql unique non-sequential id for url
我看过一些在 Whosebug 上创建字母数字 ID 的方法,但它们都有自己的弱点,有些没有检查冲突,有些使用的序列在使用逻辑复制时不是一个好的选择。
经过一些谷歌搜索后,我发现 this 网站具有以下检查冲突且不使用序列的脚本。然而,当一行插入 table.
时,这是作为触发器完成的
-- Create a trigger function that takes no arguments.
-- Trigger functions automatically have OLD, NEW records
-- and TG_TABLE_NAME as well as others.
CREATE OR REPLACE FUNCTION unique_short_id()
RETURNS TRIGGER AS $$
-- Declare the variables we'll be using.
DECLARE
key TEXT;
qry TEXT;
found TEXT;
BEGIN
-- generate the first part of a query as a string with safely
-- escaped table name, using || to concat the parts
qry := 'SELECT id FROM ' || quote_ident(TG_TABLE_NAME) || ' WHERE id=';
-- This loop will probably only run once per call until we've generated
-- millions of ids.
LOOP
-- Generate our string bytes and re-encode as a base64 string.
key := encode(gen_random_bytes(6), 'base64');
-- Base64 encoding contains 2 URL unsafe characters by default.
-- The URL-safe version has these replacements.
key := replace(key, '/', '_'); -- url safe replacement
key := replace(key, '+', '-'); -- url safe replacement
-- Concat the generated key (safely quoted) with the generated query
-- and run it.
-- SELECT id FROM "test" WHERE id='blahblah' INTO found
-- Now "found" will be the duplicated id or NULL.
EXECUTE qry || quote_literal(key) INTO found;
-- Check to see if found is NULL.
-- If we checked to see if found = NULL it would always be FALSE
-- because (NULL = NULL) is always FALSE.
IF found IS NULL THEN
-- If we didn't find a collision then leave the LOOP.
EXIT;
END IF;
-- We haven't EXITed yet, so return to the top of the LOOP
-- and try again.
END LOOP;
-- NEW and OLD are available in TRIGGER PROCEDURES.
-- NEW is the mutated row that will actually be INSERTed.
-- We're replacing id, regardless of what it was before
-- with our key variable.
NEW.id = key;
-- The RECORD returned here is what will actually be INSERTed,
-- or what the next trigger will get if there is one.
RETURN NEW;
END;
$$ language 'plpgsql';
我有一个 table 已经包含数据,我添加了一个名为 pid
的新列是否可以修改它并使用函数调用作为默认值以便我之前的所有数据获得短 ID?
假设你有一个 table test
:
DROP TABLE IF EXISTS test;
CREATE TABLE test (foo text, bar int);
INSERT INTO test (foo, bar) VALUES ('A', 1), ('B', 2);
您可以向其中添加一个 id
列:
ALTER TABLE test ADD COLUMN id text;
并附加触发器:
DROP TRIGGER IF EXISTS unique_short_id_on_test ON test;
CREATE TRIGGER unique_short_id_on_test
BEFORE INSERT ON test
FOR EACH ROW EXECUTE PROCEDURE unique_short_id();
现在制作一个临时的table,temp
,结构与测试相同(但没有数据):
DROP TABLE IF EXISTS temp;
CREATE TABLE temp (LIKE test INCLUDING ALL);
CREATE TRIGGER unique_short_id_on_temp
BEFORE INSERT ON temp
FOR EACH ROW EXECUTE PROCEDURE unique_short_id();
将test
倒入temp
:
INSERT INTO temp (foo, bar)
SELECT foo, bar
FROM test
RETURNING *
产生类似的东西:
| foo | bar | id |
|------------+-----+----------|
| A | 1 | 9yt9XQwm |
| B | 2 | LCeiA-P8 |
如果其他 table 在 test
table 上有外键引用,或者如果 test
必须保持在线,
可能无法删除 test
并将 temp
重命名为 test
。
相反,使用 temp
.
中的 id
更新 test
更安全
假设test
有一个主键(为了具体起见,我们称它为testid
),那么
您可以使用 temp
中的 id
更新 test
使用:
UPDATE test
SET id = temp.id
FROM temp
WHERE test.testid = temp.testid;
然后你可以删除 temp
table:
DROP TABLE temp;
我看过一些在 Whosebug 上创建字母数字 ID 的方法,但它们都有自己的弱点,有些没有检查冲突,有些使用的序列在使用逻辑复制时不是一个好的选择。
经过一些谷歌搜索后,我发现 this 网站具有以下检查冲突且不使用序列的脚本。然而,当一行插入 table.
时,这是作为触发器完成的-- Create a trigger function that takes no arguments.
-- Trigger functions automatically have OLD, NEW records
-- and TG_TABLE_NAME as well as others.
CREATE OR REPLACE FUNCTION unique_short_id()
RETURNS TRIGGER AS $$
-- Declare the variables we'll be using.
DECLARE
key TEXT;
qry TEXT;
found TEXT;
BEGIN
-- generate the first part of a query as a string with safely
-- escaped table name, using || to concat the parts
qry := 'SELECT id FROM ' || quote_ident(TG_TABLE_NAME) || ' WHERE id=';
-- This loop will probably only run once per call until we've generated
-- millions of ids.
LOOP
-- Generate our string bytes and re-encode as a base64 string.
key := encode(gen_random_bytes(6), 'base64');
-- Base64 encoding contains 2 URL unsafe characters by default.
-- The URL-safe version has these replacements.
key := replace(key, '/', '_'); -- url safe replacement
key := replace(key, '+', '-'); -- url safe replacement
-- Concat the generated key (safely quoted) with the generated query
-- and run it.
-- SELECT id FROM "test" WHERE id='blahblah' INTO found
-- Now "found" will be the duplicated id or NULL.
EXECUTE qry || quote_literal(key) INTO found;
-- Check to see if found is NULL.
-- If we checked to see if found = NULL it would always be FALSE
-- because (NULL = NULL) is always FALSE.
IF found IS NULL THEN
-- If we didn't find a collision then leave the LOOP.
EXIT;
END IF;
-- We haven't EXITed yet, so return to the top of the LOOP
-- and try again.
END LOOP;
-- NEW and OLD are available in TRIGGER PROCEDURES.
-- NEW is the mutated row that will actually be INSERTed.
-- We're replacing id, regardless of what it was before
-- with our key variable.
NEW.id = key;
-- The RECORD returned here is what will actually be INSERTed,
-- or what the next trigger will get if there is one.
RETURN NEW;
END;
$$ language 'plpgsql';
我有一个 table 已经包含数据,我添加了一个名为 pid
的新列是否可以修改它并使用函数调用作为默认值以便我之前的所有数据获得短 ID?
假设你有一个 table test
:
DROP TABLE IF EXISTS test;
CREATE TABLE test (foo text, bar int);
INSERT INTO test (foo, bar) VALUES ('A', 1), ('B', 2);
您可以向其中添加一个 id
列:
ALTER TABLE test ADD COLUMN id text;
并附加触发器:
DROP TRIGGER IF EXISTS unique_short_id_on_test ON test;
CREATE TRIGGER unique_short_id_on_test
BEFORE INSERT ON test
FOR EACH ROW EXECUTE PROCEDURE unique_short_id();
现在制作一个临时的table,temp
,结构与测试相同(但没有数据):
DROP TABLE IF EXISTS temp;
CREATE TABLE temp (LIKE test INCLUDING ALL);
CREATE TRIGGER unique_short_id_on_temp
BEFORE INSERT ON temp
FOR EACH ROW EXECUTE PROCEDURE unique_short_id();
将test
倒入temp
:
INSERT INTO temp (foo, bar)
SELECT foo, bar
FROM test
RETURNING *
产生类似的东西:
| foo | bar | id |
|------------+-----+----------|
| A | 1 | 9yt9XQwm |
| B | 2 | LCeiA-P8 |
如果其他 table 在 test
table 上有外键引用,或者如果 test
必须保持在线,
可能无法删除 test
并将 temp
重命名为 test
。
相反,使用 temp
.
id
更新 test
更安全
假设test
有一个主键(为了具体起见,我们称它为testid
),那么
您可以使用 temp
中的 id
更新 test
使用:
UPDATE test
SET id = temp.id
FROM temp
WHERE test.testid = temp.testid;
然后你可以删除 temp
table:
DROP TABLE temp;