PostgreSQL 11
What is the best way to generate default values for identity columns on partition tables.
E.g
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);
If I do:
INSERT INTO data.foo_log (epoch_millis, message)
VALUES (1000000, 'hello');
I get:
ERROR: null value in column "id" violates not-null constraint
DETAIL: Failing row contains (null, 1000000, hello).
SQL state: 23502
because the default generated value is not applied to the partition UNLESS I insert it into the root table like this:
INSERT INTO data.log (epoch_millis, message)
VALUES (1000000, 'hello');
There are times though that I want to insert directly into a specific partition for performance reasons (like doing bulk COPY).
The only way I can get this to work is to create the partition while knowing about the sequence that was implicitly created for the identity column like this:
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);
Is there a better way to do this and if so how?
Best Answer
I don't know of a better solution in general. A few minor things, though:
pg_get_serial_sequence()
If you don't know the name of the parent's implicit sequence, use
pg_get_serial_sequence()
.You might even use the expression in the
CREATE TABLE
script directly, but that would impose a very minor additional cost to compute the actual name for the default (once per transaction, I think), and since this is about performance optimization ...COPY
overridesGENERATED ALWAYS
, but not triggerDefining your
id
column asGENERATED ALWAYS AS IDENTITY
has the effect that you are never allowed to provide user values for the columnid
inINSERT
statements, even when using the override clause like:It would have to be
GENERATED BY DEFAULT
for this to work, or omitid
from theINSERT
completely. The manual:COPY
still overrides in any case. The manual:But while writing to a partition directly, with your solution,
INSERT
also overrides, so it will be your responsibility to avoid providing user values for theid
column directly. An alternative would be to use a trigger instead of the default value in the partition:This assigns a number from the sequence in any case, more closely emulating the
GENERATED ALWAYS
behavior of the parent - stricter, even, also preventingCOPY
from violating your rule. The manual:But the trigger is a bit more expensive than a plain default value. And it would burn an extra serial number per row for regular inserts via the parent table. (It should be possible to distinguish cases in the trigger, didn't try now.)