问题
I have a table that stores latitude and longitude data. Something like the following:
CREATE TABLE geo_sample
(
id uuid DEFAULT uuid_generate_v4 (),
latitude FLOAT NOT NULL,
longitude FLOAT NOT NULL,
PRIMARY KEY (id)
);
After a while, we realized that we didn't want to allow geographical coordinates to be entered that are too close to one another, so we thought we could use a constraint to help enforce this. My thinking was that if we rounded the coordinates to n decimal places, that would ensure that the geographical coordinates would never be piled too closely on top of one another.
I tried this:
ALTER TABLE geo_sample
ADD CONSTRAINT unique_areas UNIQUE (ROUND(latitude::numeric, 3), ROUND(longitude::numeric, 3));
But that doesn't work -- it's a syntax error.
In this case, I was able to achieve what I wanted by using a unique index:
CREATE UNIQUE INDEX unique_areas ON geo_sample (ROUND(latitude::numeric, 3), ROUND(longitude::numeric, 3));
Note that I had to force the latitude and longitude to be numeric, otherwise it got an error about the function signature (?). FYI: this will fail with a very generic error message "could not create unique index" if there are rows present which violate the condition.
My question is: is it possible to do this using a constraint instead of an index? And more broadly, when is it ok to use modifying functions (like LOWER() to avoid duplicate email addresses) to help enforce data integrity?
回答1:
You can create a function to be used by the constraint.
CREATE FUNCTION no_similar_XY_in_my_data(x float, y float)
RETURNS boolean AS $$
SELECT NOT EXISTS (
SELECT 1 FROM geo_sample
WHERE round(geo_sample.latitude::numeric,3) = round(y::numeric,3) AND round(geo_sample.longitude::numeric,3) = round(x::numeric,3)
);
$$ LANGUAGE sql;
ALTER TABLE geo_sample ADD CONSTRAINT no_similar_XY CHECK (no_similar_XY_in_my_data(longitude,latitude));
insert into geo_sample(longitude,latitude) values (1.23456, 9.876543);
INSERT 0 1
insert into geo_sample(longitude,latitude) values (1.23456, 9.876543);
ERROR: new row for relation "geo_sample" violates check constraint "no_similar_xy"
DETAIL: Failing row contains (2, 9.876543, 1.23456).
That being said, rounding the coordinated will not ensure that two locations are not near each others. Using rounding at 3 decimals, 0.12349999 will round to 0.123 while 0.12350000000 will round to 0.124, despite the fact that the two points are almost at the same place.
The proper way of doing it would be to use the PostGIS extension, to save the points as a geometry and to compute the real distance between the points. This function can also be used in the check constraint.
CREATE FUNCTION no_nearby_point_in_my_data(g geometry)
RETURNS boolean AS $$
SELECT NOT EXISTS (
SELECT 1 FROM geo_sample
WHERE ST_DWithin( my_data.geom, g, 0.001));
$$ LANGUAGE sql;
来源:https://stackoverflow.com/questions/58547751/is-it-possible-to-add-a-constraint-to-a-postgres-table-using-rounded-values-for