Constraint on existing table fails
-
28-02-2021 - |
Question
I want to add the below constraint to an existing table:
ALTER TABLE ONLY daily_summary_202004
ADD CONSTRAINT odo_no_overlapping_202004
EXCLUDE USING gist (((vehicle_gid)::text) WITH =, numrange(odo_start, odo_end) WITH &&)
WHERE (start_date >= '2020-04-01'
AND start_date < ' 2020-05-01'
AND active) ;
But there are few entries in the table that fail the constraint in production.
Is there an SQL query that I can write to get all the faulty rows so I can delete them before I add the constraint?
The DDL for the table
create table if not exists ifta.daily_summary_202004
(
id uuid default uuid_generate_v4() not null primary key,
distance numeric(10, 3) default 0 not null,
fuel_code varchar(128) not null,
state varchar not null,
odo_start numeric(10, 3) not null,
odo_end numeric(10, 3),
start_date timestamp not null,
end_date timestamp,
vehicle_gid uuid
);
Essentially, there should not be overlapping odo_start and odo_end entries, hence the constraint. But because of an existing bug, we've some overlapping entries, I want to find them and get rid of them.
Solution
I think that the previously given query is closed to what you need:
select *
from daily_summary_202004 ds1
WHERE ds1.start_date >= date '2020-04-01'
AND ds1.start_date < date '2020-05-01'
AND ds1.active
AND exists (select *
from daily_summary_202004 ds2
where ds1.vehicle_gid = ds2.vehicle_gid
and numrange(ds1.odo_start, ds1.odo_end) &&
numrange(ds2.odo_start, ds2.odo_end)
and ds1.id <> ds2.id
);
It selects the rows that cannot exist in the table with the given constraint. However it cannot give the one(s) that should be rejected: it gives only the candidates. You need to choose.
If I simulate with some data. I get:
select * from daily_summary_202004 ;
id | vehicle_gid | odo_start | odo_end | start_date | active
----+-------------+-----------+---------+---------------------+--------
3 | 10 | 10 | 20 | 2020-04-15 00:00:00 | t
2 | 10 | 3 | 4 | 2020-04-15 00:00:00 | t
1 | 10 | 2 | 6 | 2020-04-15 00:00:00 | t
(3 rows)
---
select *
from daily_summary_202004 ds1
WHERE ds1.start_date >= date '2020-04-01'
AND ds1.start_date < date '2020-05-01'
AND ds1.active
AND exists (select *
from daily_summary_202004 ds2
where ds1.vehicle_gid = ds2.vehicle_gid
and numrange(ds1.odo_start, ds1.odo_end) && numrange(ds2.odo_start, ds2.odo_end)
and ds1.id <> ds2.id
);
id | vehicle_gid | odo_start | odo_end | start_date | active
----+-------------+-----------+---------+---------------------+--------
2 | 10 | 3 | 4 | 2020-04-15 00:00:00 | t
1 | 10 | 2 | 6 | 2020-04-15 00:00:00 | t
(2 rows)
alter table only daily_summary_202004
add constraint odo_no_overlapping
exclude using gist
(
((vehicle_gid)::text) WITH =,
numrange(odo_start, odo_end) WITH &&)
WHERE (start_date >= '2020-04-01'
AND start_date < ' 2020-05-01'
AND active
) ;
psql:tc.sql:39: ERROR: could not create exclusion constraint "odo_no_overlapping"
DETAIL: Key ((vehicle_gid::text), numrange(odo_start::numeric, odo_end::numeric))=(10, [3,4)) conflicts with key ((vehicle_gid::text), numrange(odo_start::numeric, odo_end::numeric))=(10, [2,6)).
Here PostgreSQL reports the involved rows but you also have to choose which one to keep and which one to reject.