Question Details

No question body available.

Tags

postgresql

Answers (2)

February 11, 2026 Score: 0 Rep: 30,417 Quality: Medium Completeness: 100%

You can use overlapping &&, containment @> and difference - daterange operators to target specific records based on their relation to the incoming range.
The example you showed suggests you're following these rules:

  • If an existing range is fully contained in the incoming one, you simply update its price.
    Example (2, '[2027-01-01,2028-01-01)', 234)
    becomes (2, '[2027-01-01,2028-01-01)', 567)
  • If an existing range partially overlaps the incoming one, you split it so that the overlap becomes a new record with the new price, while the old one with the old price gets shortened. Example (2, '[2026-04-01,2027-01-01)', 123)
    splits into (2, '[2026-04-01,2026-04-15)', 123) (initial record, shortened to non-overlapping part)
    and (2, '[2026-04-15,2027-01-01)', 567) (overlap with the new price)
  • It's not specified or demonstrated, but safe to assume that if the incoming range doesn't intersect any existing record, you just add it as a new one.

I'm using a prepared statement, but you can also wrap it in a routine to make it shareable and re-usable between sessions. demo at dbfiddle

prepare addnewpricedaterange(daterange,decimal(12,2))as with incomingchange(validat,price)as(values($1,$2) ),updatecontained as( update contract as c set price=i.price from incomingchange as i where i.validat @> c.validat returning new.* ),updateandreturnoverlaps as( update contract as c set validat=c.validat-i.validat from incomingchange as i where c.validat && i.validat and c.validat-i.validat 'empty' returning c.id , old.validat-new.validat as remainderrange , i.price ),insertindividualremainders as( insert into contract table updateandreturnoverlaps ),allintroducedranges as( select id , rangeagg(remainderrange) as allindividualremainders , price from(table updateandreturnoverlaps union all table updatecontained) group by id,price ),addpreviouslyundefinedranges as( insert into contract select id , unnest(datemultirange(validat)-allindividualremainders) , price from allintroducedranges join incomingchange using(price) returning* )select 'completely new' as "source",* from addpreviouslyundefinedranges union all select 'fully covered', from update_contained union all select 'remainder from overlapped', from updateandreturnoverlaps;

execute addnewpricedaterange('[2026-04-15,2028-02-01)',567);

source id validat price
fully covered 2 [2027-01-01,2028-01-01) 567.00
remainder from overlapped 2 [2026-04-15,2027-01-01) 567
remainder from overlapped 2 [2028-01-01,2028-02-01) 567

select ctid,* from contract order by validat;
ctid id validat price
(0,5) 2 [2026-04-01,2026-04-15) 123.00
(0,8) 2 [2026-04-15,2027-01-01) 567.00
(0,7) 2 [2027-01-01,2028-01-01) 567.00
(0,9) 2 [2028-01-01,2028-02-01) 567.00
(0,6) 2 [2028-02-01,) 345.00

Example covering a daterange without an existing price record:

execute addnewpricedaterange('[2025-01-01,2026-04-02)',42.42);
source id validat price
completely new 2 [2025-01-01,2026-04-01) 42.42
remainder from overlapped 2 [2026-04-01,2026-04-02) 42.42

select ctid,* from contract order by validat;
ctid id valid_at price
(0,11) 2 [2025-01-01,2026-04-01) 42.42
(0,12) 2 [2026-04-01,2026-04-02) 42.42
(0,10) 2 [2026-04-02,2026-04-15) 123.00
(0,8) 2 [2026-04-15,2027-01-01) 567.00
(0,7) 2 [2027-01-01,2028-01-01) 567.00
(0,9) 2 [2028-01-01,2028-02-01) 567.00
(0,6) 2 [2028-02-01,) 345.00
February 11, 2026 Score: 0 Rep: 30,417 Quality: Medium Completeness: 80%

Posting separately, as this proposes a schema change: if there's no real business logic requiring you to split records, or if you plan to merge them back together later, then you can make the PK deferrable:

PRIMARY KEY (id, validat WITHOUT OVERLAPS) deferrable initially deferred

That lets you simply delete contained ranges and shrink overlapped, adding the new one separately:
demo at dbfiddle

prepare addnewpricedaterange(int,daterange,decimal(12,2))as
with incomingchange(id,validat,price)as(values($1,$2,$3)
),removecontained as(
    delete from contract as c
    using incomingchange as i
    where i.validat @> c.validat
      and i.id=c.id
),updateoverlapping as(  
    update contract as c
    set validat=c.validat-i.validat
    from incomingchange as i 
    where c.validat && i.validat
      and c.validat-i.validat  'empty'
      and i.id=c.id
)insert into contract table incomingchange;

execute addnewpricedaterange(2,'[2026-04-15,2028-02-01)',567); select ctid,* from contract order by validat;

ctid id validat price
(0,5) 2 [2026-04-01,2026-04-15) 123.00
(0,4) 2 [2026-04-15,2028-02-01) 567.00
(0,6) 2 [2028-02-01,) 345.00

Note that deferrability disables the option to make that PK a conflict arbiter, meaning that you'll be no longer able to do an insert..on conflict..-style upserts, or conflict-skipping inserts.

Both answers can also be rewritten as MERGE operations, possibly removing the need for deferrability.