PostgreSQL 如何默认分区标识列?

aka*_*xer 6 postgresql partitioning identity postgresql-11

PostgreSQL 11
为分区表上的标识列生成默认值的最佳方法是什么。
例如

CREATE TABLE data.log
(
  id              BIGINT GENERATED ALWAYS AS IDENTITY
                  (
                    INCREMENT BY 1
                    MINVALUE -9223372036854775808
                    MAXVALUE 9223372036854775807
                    START WITH -9223372036854775808
                    RESTART WITH -9223372036854775808
                    CYCLE
                  ),
  epoch_millis    BIGINT NOT NULL,
  message         TEXT NOT NULL

) PARTITION BY RANGE (epoch_millis);

CREATE TABLE data.foo_log
PARTITION OF data.log
(
  PRIMARY KEY (id)
)
FOR VALUES FROM (0) TO (9999999999);
Run Code Online (Sandbox Code Playgroud)

如果我做:

INSERT INTO data.foo_log (epoch_millis, message)
VALUES (1000000, 'hello');
Run Code Online (Sandbox Code Playgroud)

我得到:

错误:“id”列中的空值违反了非空约束
详细信息:失败的行包含(null,1000000,hello)。
SQL状态:23502

因为默认生成的值不会应用于分区,除非我将其插入到根表中,如下所示:

INSERT INTO data.log (epoch_millis, message)
VALUES (1000000, 'hello');
Run Code Online (Sandbox Code Playgroud)

有时,出于性能原因(例如进行批量复制),我想直接插入特定分区。
我可以让它工作的唯一方法是创建分区,同时了解为标识列隐式创建的序列,如下所示:

CREATE TABLE data.foo_log
PARTITION OF data.log
(
  id DEFAULT nextval('data.log_id_seq'),
  PRIMARY KEY (id)
)
FOR VALUES FROM (0) TO (9999999999);
Run Code Online (Sandbox Code Playgroud)

有没有更好的方法来做到这一点,如果有的话怎么做?

Erw*_*ter 5

一般来说,我不知道有更好的解决方案。不过,有一些小事情:

pg_get_serial_sequence()

如果您不知道父级隐式序列的名称,请使用pg_get_serial_sequence()

SELECT pg_get_serial_sequence('data.log', 'id');
Run Code Online (Sandbox Code Playgroud)

您甚至可以直接在脚本中使用表达式CREATE TABLE,但这会产生非常小的额外成本来计算默认值的实际名称(我认为每个事务一次),并且因为这是关于性能优化的......

COPY覆盖GENERATED ALWAYS,但触发器不覆盖

id将列定义为GENERATED ALWAYS AS IDENTITY的效果是不允许您idINSERT语句中为该列提供用户值,除非添加“覆盖”子句,例如:

INSERT INTO data.log (epoch_millis, message)覆盖用户值
VALUES (1000000, 'hello');

手册:

OVERRIDING USER VALUE

如果指定此子句,则忽略为标识列提供的任何值,并应用默认序列生成的值。

例如,在表之间复制值时,此子句很有用。写入INSERT INTO tbl2 OVERRIDING USER VALUE SELECT * FROM tbl1 将从tbl1所有不是标识列的列进行复制, tbl2而标识列的值tbl2将由与 关联的序列生成tbl2

COPY在任何情况下都会覆盖。手册:

对于标识列,该COPY FROM命令将始终写入输入数据中提供的列值,如INSERT选项 OVERRIDING SYSTEM VALUE

但是,当使用您的解决方案直接写入分区时,INSERT也会覆盖,因此您有责任避免id直接为列提供用户值。另一种方法是使用触发器而不是分区中的默认值:

CREATE OR REPLACE FUNCTION trg_log_default_id()
  RETURNS trigger
  LANGUAGE plpgsql AS
$func$
BEGIN
   NEW.id := nextval('data.log_id_seq')
   RETURN NEW;
END
$func$;

CREATE TRIGGER insbef_default_id
  BEFORE INSERT ON data.foo_log  -- the partition
  FOR EACH ROW
  EXECUTE PROCEDURE trg_log_default_id();
Run Code Online (Sandbox Code Playgroud)

无论如何,这都会从序列中分配一个数字,更接近地模仿GENERATED ALWAYS父母的行为 - 更严格,甚至还可以防止COPY违反您的规则。手册:

COPY FROM将调用任何触发器并检查目标表上的约束。

触发器比普通的默认值要贵一些。并且它会在每行刻录一个额外的序列号,以便通过父表进行常规插入。(触发器中应该可以区分大小写,现在没有尝试。)