Another idea, which I would prefer is this,
- Create a function which generates your id from from the timestamp and your invoice number,
- Create regular table with,
- a
foo_id
: simple sequence (incrementing int)
- a
ts_created
field.
- Generate your invoice ids on the query when required,
Here is how it looks, first we create the function to generate an acme_id
from a bigint
and a timestamp
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;
And then we create a table.
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);
Now you can generate what you're looking for with a simple window function.
SELECT acme_id(
ROW_NUMBER() OVER (
PARTITION BY date_trunc('MONTH', ts_created)
ORDER BY ts_created
),
ts_created
), *
FROM foo;
I would build my system such that the foo_id
is used internally. So long as you don't have deletions from foo
you'll always be able to render the same invoice id from the row, you just won't have to store it.
You can even cache the rendering and invoice ids with a [materialized] view.
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)
Keep in mind the drawbacks to this approach:
- Rows in the invoice table can never be deleted, (you could add a bool to deactivate them),
- The
foo_id
and ts_created
should be immutable (never updated) or you may get a new Invoice ID. Surrogate keys (foo_id
should never change by definition anyway).
The benefits of this approach:
- Storing a real timestamp which is likely very useful on an invoice
- Real surrogate key (which I would use in all contexts instead of an invoice ID), simplifies linking to other tables and is more efficient and fast.
- Single source of truth for the invoice date
- Easy to issue a new invoice-id scheme and to even map it to an older scheme.