You can achieve this with an "exclusion constraint", which is like a generalised unique constraint which can check any operator. See this question for some background on the syntax.
In particular, we can say that no two rows A and B should exist where the following conditions hold:
A.name equals B.name
A.type equals B.type
A.date is between B.date and B.date + 6 months
Note that you don't also need to check the 6 months before B.date, because that will checked by looking at the rows the other way around: B.date will be between A.date and A.date + 6 months.
To make the last condition implementable with a single operator, we can express it in terms of ranges:
- The range
A.date to A.date + 6 months overlaps the range B.date to B.date + 6 months
We can then write an exclusion constraint which analyses using the && (range overlap) operator, which looks like this:
Alter Table entries
Add Constraint name_and_type_within_6_months
Exclude Using Gist (
name with =,
type with =,
tsrange(date, date + interval '6 months') with &&
);
(Hat tip to Philipe Fatio for this gist showing a date range exclusion.)
Here is an interactive demo showing that constraint in action: https://dbfiddle.uk/?rdbms=postgres_13&fiddle=83181388416d1e5905e088532839ad79