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

Quick explanation:

**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

then

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;