To solve this problem a number of possible solutions may come to mind, usually this are not very straightforward and/or very slow.
It turns out that the right (and spectacularly fast) solution is:
SELECT ST_GeometryN(unnest(ST_ClusterWithin(f.geom, 0.4)),1) AS geom FROM points AS f
- ST_ClusterWithin creates a cluster/GeometryCollection where each GeometryCollection represents a set of geometries separated by no more than the specified distance. All the GeometryCollection are placed into an array
- unnest splits the array and puts each element into a separate row
- ST_GeometryN extracts the Nth geometry from a GeometryCollection
In the example above the distance used for this analysis is 0.4 meters, and with ST_GeometryN we extract always the 1st geometry from the cluster/GeometryCollection of points, because for this problem a) is not important what point we keep (in the cluster) and b) the first geometry of a GeometryCollection always exist.
If is needed to know how many points are in each cluster/GeometryCollection the ST_NumGeometries can be used:
SELECT unnest(ST_ClusterWithin(f.geom, 0.4)) AS geom, ST_NumGeometries(unnest(ST_ClusterWithin(f.geom, 0.4))) AS ng FROM dados.points AS f
A different approach, not as fast as the above, could be:
CREATE TABLE points_clusters AS WITH temp_table AS (SELECT (ST_Dump(ST_Union(ST_Buffer(geom, 0.2, 'quad_segs=100')))).geom::geometry(POLYGON,3047) AS geom FROM points) SELECT row_number() over() AS gid, geom AS geom FROM temp_table
CREATE TABLE points_without_closest AS SELECT DISTINCT ON (geom) id_point, geom FROM ( SELECT a.gid AS id_point,b.gid AS id_buffer,a.geom AS geom, row_number() OVER (PARTITION BY b.gid ORDER BY a.gid) AS rownum FROM points a, points_clusters b WHERE ST_Within(a.geom,b.geom)) tmp WHERE rownum < 2;