Well, even though you didn’t ask for it, I decided to create a *k*-nearest neighbor analysis in PostGRES/PostGIS. This has lots of potential: find the 5 nearest ATMs to every fast food restuarant; find the 3 closest medical clinics to each school; or find the 10 closest bars to each college.

Now, the **LIMIT** clause does allow us to find the *k* closest features to a particular feature. But in this case, I’m not interested in a particular feature, I want the results for **all **features.

To do this in Postgres, I’m going to show you two things that I haven’t used until today:** rank()** and **partition.**

This entire query falls under a heading of a **Window** function in Postgres.

Modified from Postgres help:

* A window function performs a calculation across a set of table rows that are somehow related to the current row. This is comparable to the type of calculation that can be done with an aggregate function. But unlike regular aggregate functions, use of a window function does not cause rows to become grouped into a single output row — the rows retain their separate identities. *

Here is the code we’ll talk about:

SELECT * FROM (SELECT park_no1, park_no2, dist, rank() over (partition by park_no1 order by dist asc) as rank FROM ( SELECT a.name AS park_no1, b.name AS park_no2, ST_Distance(a.geometry, b.geometry) AS dist FROM parks AS a , parks AS b WHERE a.name &lt;&gt; b.name ) AS T1 ) AS T2 WHERE rank &lt; 4

And our results are here:

Now let’s talk about it…

Getting the distances

Lines 6-10 is your basic SELECT statement where I get the distances from each park to every other park, and sort it in ASCENDING order (that way, we get the closest parks first). I add to that two little tricks I’ve shown you countless times:

- renaming the parks table
*A*and*B*to trick the SQL engine into thinking there are two tables - not performing the distance calculations when the names are the same (to avoid 0 distances).

Partitioning the data

This one was new to me. Partitioning is really cool and refers to splitting the resulting table returned by the query into smaller physical pieces. This is part of the Window capabilities in Postgres. In this case, I am partitioning the data into groups by the park_no1 value (the park name).

Rank the data

So, we’ve grouped out the data based on the park_no, so now we take those interesting grouped pieces and put a rank on them. The rank() function does just that. But Postgres wants to know what we want to rank. In this case, we need to use the OVER clause. The `OVER` clause determines exactly how the rows of the query are split up for processing by the Window function we are using. So, for each group of parks, we now have a rank from 1..N for the group like the following:

So now we have this really big (nxn) X 4 table. But remember, we want the closest three parks. So, the thing to do is wrap the entire query in parenthesis, and then select all records with a rank column less than 4. The resulting table is shown below.

Don’t worry if you don’t understand this. Like they say at the end of the TV show Dragnet: *the names have been changed to protect the innocent*. So, just copy this code in, and start changing names. If you are working with burglaries and not parks, then simply change the name of the parks table to burglaries. You can also change my unique name for the park with whatever your unique name is. The, when you have it working for your data, you’ll probably have a better shot at understanding it.

Want to learn more about writing SQL? Use a coupon code to take my course on Spatial SQL for under $30 right **here**.

Also, tell your friends. If I can get 100 people to take the course in the next month I’ll ramp up more posts like this :-)

The database product is called PostgreSQL or Postgres – but not PostGRES