Get row number of row to be inserted in Postgres trigger that gives no collisions when inserting multiple rows

341 Views Asked by At

Given the following (simplified) schema:

CREATE TABLE period (
    id          UUID NOT NULL DEFAULT uuid_generate_v4(),
    name        TEXT
);

CREATE TABLE course (
    id          UUID NOT NULL DEFAULT uuid_generate_v4(),
    name        TEXT
);

CREATE TABLE registration (
    id          UUID NOT NULL DEFAULT uuid_generate_v4(),
    period_id   UUID NOT NULL REFERENCES period(id),
    course_id   UUID NOT NULL REFERENCES course(id),
    inserted_at timestamptz NOT NULL DEFAULT now()
);

I now want to add a new column client_ref, which identifies a registration unique within a period, but consists of only a 4-character string. I want to use pg_hashids - which requires a unique integer input - to base the column value on.

I was thinking of setting up a trigger on the registration table that runs on inserting a new row. I came up with the following:

CREATE OR REPLACE FUNCTION set_client_ref()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
DECLARE
    next_row_number integer;
BEGIN
        WITH rank AS (
            SELECT
                period.id AS period_id,
                row_number() OVER (PARTITION BY period.id ORDER BY registration.inserted_at)
            FROM
                registration
                JOIN period ON registration.period_id = period.id ORDER BY
                    period.id,
                    row_number
)
SELECT
    COALESCE(rank.row_number, 0) + 1 INTO next_row_number
FROM
    period
    LEFT JOIN rank ON (rank.period_id = period.id)
WHERE
    period.id = NEW.period_id
ORDER BY
    rank.row_number DESC
LIMIT 1;
    NEW.client_ref = id_encode (next_row_number);
    RETURN NEW;
END
$function$
;

The trigger is set-up like: CREATE TRIGGER set_client_ref BEFORE INSERT ON registration FOR EACH ROW EXECUTE FUNCTION set_client_ref();

This works as expected when inserting a single row to registration, but if I insert multiple within one statement, they end up having the same client_ref. I can reason about why this happens (the rows don't know about each other's existence, so they assume they're all just next in line when retrieving their row_order), but I am not sure what a way is to prevent this. I tried setting up the trigger as an AFTER trigger, but it resulted in the same (duplicated) behaviour.

What would be a better way to get the lowest possible, unique integer for the rows to be inserted (to base the hash function on) that also works when inserting multiple rows?

0

There are 0 best solutions below