This question is directly related to your earlier one, making it a duplicate - since this one is also addressing an unexpected outcome further downstream the core issue of both posts, I'll answer here.
The core issue with your attempt is the implicit CROSS JOIN
, which creates a cartesian product between both tables:
[...]
FROM
<table_a>, -- implicit CROSS JOIN using , (comma) notation
<table_b>
[...]
which translates to:
Join each row in <table_a>
to each row of <table_b>
.
and returns COUNT(<table_a>) * COUNT(<table_b>)
rows in its result set.
Now, the attempted DISTINCT ON (p.id)
successfully reduces this cartesian product of both tables to the distinct p.id
values of points AS p
, but since there are COUNT(lines)
rows for every p.id
in the result set, PostgreSQL has to choose one of them to satisfy the DISTINCT
command - and it does that by picking the first row that is given by the inherent order of the lines
table, per p.id
; in your case this is the same lines.geom
for every distinct p.id
.
The requirement for a (k) Nearest Neighbor search can be expressed as
For every row in <table_a>
find the k closest row(s) in <table_b>
.
which is logically equivalent to a procedural FOR LOOP
- and which can be realized with a LATERAL
join within PostgreSQL:
SELECT
ST_ClosestPoint(n.geom, p.geom) AS geom
ST_Distance(n.geom, p.geom) AS dist
FROM
points AS p
CROSS JOIN LATERAL (
SELECT
l.geom
FROM
lines AS l
ORDER BY
l.geom <-> p.geom
LIMIT
1
) AS n
;
I recommend to further read on multiple topics concerning (K)NN queries, to understand LATERAL
queries, distances and index usage.
Related: