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.

Was it helpful?

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with dba.stackexchange
scroll top