如何根据行创建日期在 PostgreSQL 中创建自定义序列?

Posted

技术标签:

【中文标题】如何根据行创建日期在 PostgreSQL 中创建自定义序列?【英文标题】:How do I create custom sequence in PostgreSQL based on date of row creation? 【发布时间】:2014-07-30 15:32:19 【问题描述】:

我正在为我的雇主更换旧的订单管理应用程序。新系统的规格之一是订单编号系统保持不变。现在,我们的订单号格式如下:

前四位数字是当前年份 接下来的两位数字是当前月份 下一个(也是最后一个)四位数字是一个计数器,每次在该月下订单时递增一。

例如,2014 年 6 月下的第一个订单的订单号为 2014060001。下一个订单的订单号为 2014060002,依此类推。

此订单号必须是订单表中的主要 ID。看来我需要为 PostgreSQL 设置一个自定义序列来分配主键,但是我能找到的用于创建自定义序列的唯一文档是非常基本的(如何增加 2 而不是 1 等)。

如何根据上述日期创建自定义序列?

【问题讨论】:

您不能为此使用序列,因为交易失败,序列可能会在您的数字中留下空白:序列不会回滚。 我一直使用主键作为序列,很少但发生主键值跳跃几百或一千。 @FrankHeikens 以获得更好的方法,恕我直言:***.com/a/69546178/124486 【参考方案1】:

您可以使用EXTRACT() 函数将手动创建的序列设置为特定值:

setval('my_sequence',
  (EXTRACT(YEAR FROM now())::integer * 1000000) +
  (EXTRACT(MONTH FROM now())::integer * 10000)
);

输入的下一个订单将采用序列中的下一个值,即 YYYYMM0001 等。

诀窍在于何时更新序列值。您可以在 PG 中执行此操作,并在您的订单表上编写一个 BEFORE INSERT 触发器,以检查这是否是新月份的第一条记录:

CREATE FUNCTION before_insert_order() RETURNS trigger AS $$
DECLARE
  base_val  integer;
BEGIN
  -- base_val is the minimal value of the sequence for the current month: YYYYMM0000
  base_val := (EXTRACT(YEAR FROM now())::integer * 1000000) +
              (EXTRACT(MONTH FROM now())::integer * 10000);

  -- So if the sequence is less, then update it
  IF (currval('my_sequence') < base_val)
    setval('my_sequence', base_val);
  END IF;

  -- Now assign the order id and continue with the insert
  NEW.id := nextval('my_sequence');
  RETURN NEW;
END; $$ LANGUAGE plpgsql;

CREATE TRIGGER tr_bi_order
  BEFORE INSERT ON order_table
  FOR EACH ROW EXECUTE PROCEDURE before_insert_order();

为什么这么难?因为您在每次插入时检查序列的值。如果您每天只有少量插入,并且您的系统不是很忙,那么这是一种可行的方法。

如果您无法节省所有这些 CPU 周期,您可以安排 cron 作业在每月第一天的 00:00:01 运行,以通过 psql 执行 PG 函数以更新序列,然后只需使用序列作为新订单记录的默认值(因此不需要触发器)。

【讨论】:

【参考方案2】:

另一个我更喜欢的想法是,

    创建一个函数,根据时间戳和发票号生成您的 ID, 创建常规表, foo_id:简单序列(递增 int) ts_created 字段。 在需要时在查询中生成您的发票 ID,

这是它的样子,首先我们创建一个函数来从biginttimestamp生成acme_id

CREATE FUNCTION acme_id( seqint bigint, seqts timestamp with time zone )
RETURNS char(10)
AS $$
  SELECT format(
    '%04s%02s%04s',
    EXTRACT(year FROM seqts),
    EXTRACT(month from seqts),
    to_char(seqint, 'fm0000')
  );
$$ LANGUAGE SQL
IMMUTABLE;

然后我们创建一个表。

CREATE TABLE foo (
  foo_id        int  PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
  data          text,
  ts_created    timestamp with time zone DEFAULT NOW()
);
CREATE INDEX ON foo(ts_created, foo_id);

现在您可以使用简单的window function. 生成您要查找的内容

SELECT acme_id(
  ROW_NUMBER() OVER (
    PARTITION BY date_trunc('MONTH', ts_created)
    ORDER BY ts_created
  ),
  ts_created
), *
FROM foo;

我会构建我的系统,以便在内部使用foo_id。只要您没有从 foo 中删除,您将始终能够从该行呈现相同的发票 ID,您就不必存储它。

您甚至可以使用 [物化] 视图缓存渲染和发票 ID。

CREATE MATERIALIZED VIEW acme_invoice_view
AS
    SELECT acme_id(
      ROW_NUMBER() OVER (
        PARTITION BY date_trunc('MONTH', ts_created)
        ORDER BY ts_created
      ),
      ts_created
    ), *
    FROM foo;
;

SELECT * FROM acme_invoice_view;
  acme_id   | foo_id | insert_date | data 
------------+--------+-------------+------
 2021100001 |      1 | 2021-10-12  | bar
(1 row)

请记住这种方法的缺点:

发票表中的行永远不能被删除,(您可以添加一个布尔值来禁用它们), foo_idts_created 应该是不可变的(永远不会更新),否则您可能会获得一个新的发票 ID。代理键(foo_id 无论如何都不应该改变定义)。

这种方法的好处:

存储在发票上可能非常有用的真实时间戳 真正的代理键(我将在所有情况下使用它而不是发票 ID),简化了与其他表的链接,并且更加高效和快速。 发票日期的单一真实来源 轻松发布新的发票 ID 方案,甚至可以将其映射到旧方案。

【讨论】:

以上是关于如何根据行创建日期在 PostgreSQL 中创建自定义序列?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 postgresql 中创建只读视图,类似于 oracle?

如何使用当前日期在 FTP 服务器中创建目录(文件夹)?

如何在 PostgreSQL 中创建临时函数?

如何使用 init.sql 在 Postgresql 中创建数据库、模式和表

如何在Postgresql中创建序列数并应用在数据表

如何在Postgresql中创建序列数并应用在数据表