问题
I need to ensure my database only contains entries where two or more of its columns are unique. This can easily be achieved with a UNIQUE
constraint over those columns.
In my case, I need to forbid duplication only for overlapping time ranges. The table has valid_from
and valid_to
columns. In some cases one might first need to expire the active entry to by setting valid_to = now
, and then inserting a new entry adjusted to valid_from = now
and valid_to = infinity
.
I seem to be able to expire the prior entry without any problems using UPDATE
, but inserting the new entry seems to be troublesome since my base columns are currently UNIQUE
, and therefore can't be added again.
I thought of adding valid_from
and valid_to
as part of the UNIQUE
constraint, but that would just make the constraint more loose, and allow
duplicates and overlapping time ranges to exist.
How do I make a constraint to ensure that duplicates don't exist with overlapping valid_from
and valid_to
tsrange
?
I seem to be looking for EXCLUDE USING GIST
, but it does not seem to support multiple columns? This does not seem to work for me:
ALTER TABLE registration
DROP Constraint IF EXISTS registration_{string.Join('_', listOfAttributes)}_key,
ADD Constraint registration_{string.Join('_', listOfAttributes)}_key EXCLUDE USING GIST({string.Join(',', listOfAttributes)} WITH =, valid WITH &&);
回答1:
You were on the right track. But the syntax for exclusion constraints is slightly different.
Depending on the undisclosed table definition, you may need to install the extension (additional module) btree_gist
first. Once per db. It's needed for my example since the required operator class is not installed for type integer
by default:
CREATE EXTENSION btree_gist;
See:
- PostgreSQL EXCLUDE USING error: Data type integer has no default operator class
- How to use (install) dblink in PostgreSQL?
Then:
CREATE TABLE registration (
tbl_id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
, col_a integer NOT NULL
, col_b integer NOT NULL
, valid_from timestamp
, valid_to timestamp
, CONSTRAINT no_overlap
EXCLUDE USING gist (col_a with =, col_b with =, tsrange(valid_from, valid_to) WITH &&)
);
Each column needs to be listed with its respective operator.
And you need a range type. You mention separate columns valid_from
and valid_to
. And you also mention tsrange
and valid
in the failed command. That's confusing. Assuming two timestamp
columns, an expression index with the expression tsrange(valid_from, valid_to)
would do it.
Related:
- Perform this hours of operation query in PostgreSQL
- Non-overlap, continuous timestamp ranges (tstzrange) for opening hours
- Postgresql 9.4 query gets progressively slower when joining TSTZRANGE with &&
- Store the day of the week and time?
Typically, timestamptz
(tstzrange
) should be chosen over timestamp
(tsrange
). See:
- Ignoring time zones altogether in Rails and PostgreSQL
Maybe, a superior design would be a one-to-many relationship between your registration
table and 1-N entries in a new registration_range
table. And some logic to determine the currently valid entry (for any given point in time). Depends on more undisclosed information.
来源:https://stackoverflow.com/questions/61716095/how-to-ensure-entries-with-non-overlapping-time-ranges