【问题标题】:postgresql unique non-sequential id for urlurl的postgresql唯一的非顺序ID
【发布时间】:2019-05-10 16:56:28
【问题描述】:

我已经查看了一些在 Stackoverflow 上创建字母数字 ID 的方法,但它们都有自己的弱点,有些没有检查冲突,有些使用的序列在使用逻辑复制时不是一个好的选择。

经过一番谷歌搜索后,我发现this 网站具有以下脚本,该脚本检查冲突并且不使用序列。但是,当将行插入表中时,这是作为触发器完成的。

-- 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';

我有一个已经包含数据的表,我添加了一个名为 pid 的新列,是否可以修改它并使用函数调用作为默认值,以便我之前的所有数据都有一个短 ID?

【问题讨论】:

  • 您可以使用相同的密钥生成更新现有行。总和是replace(replace(encode(gen_random_bytes(6), 'base64'), '/', '_'), '+', '-')

标签: postgresql postgresql-11


【解决方案1】:

假设你有一张桌子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();

现在创建一个临时表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 |

如果其他表在test 表上有外键引用,或者test 必须保持在线, 可能无法删除 test 并将 temp 重命名为 test。 相反,使用来自tempids 更新test 会更安全。

假设test 有一个主键(为了具体起见,我们称之为testid),那么 您可以使用来自tempids 更新test

UPDATE test
SET id = temp.id
FROM temp
WHERE test.testid = temp.testid;

然后你可以删除temp 表:

DROP TABLE temp;

【讨论】:

    猜你喜欢
    • 2017-04-20
    • 2011-08-28
    • 1970-01-01
    • 1970-01-01
    • 2017-02-23
    • 1970-01-01
    • 2017-04-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多