I have a standard postgresql OpenStreetMap database that was filled with data via osm2pgsql. There are different tables for different types (points, lines, polygons, relations) that need to get combined for my specific query:
I want to select all relations with the tags boundary=administrative
and admin_level=6
. The results need to be searched for a member with the role admin_centre
.
Here are my steps by now:
Select member-role list:
SELECT unnest(members)
FROM planet_osm_rels
WHERE ARRAY['admin_level','6']<@tags AND ARRAY['boundary','administrative']<@tags;
Result for one example:
unnest
--------------
n240045827
admin_centre
w185481610
outer
w191987461
outer
w166286278
outer
w166290573
outer
w65481875
outer
w166278211
outer
w166278224
outer
w166278225
outer
w166278227
outer
w185148605
outer
[...]
The n
stands for nodes, the w
for ways, followed by the unique id that can be found within the respective table (planet_osm_nodes or planet_osm_line). In this example, the admin_centre is the only node, so we can find members that are nodes with LIKE:
SELECT name
FROM planet_osm_point
JOIN (
SELECT ltrim(member, 'n')::bigint AS osm_id
FROM (
SELECT unnest(members) AS member
FROM planet_osm_rels
WHERE ARRAY['boundary','administrative']<@tags AND ARRAY['admin_level','6']<@tags) u
WHERE member LIKE 'n%') x
USING(osm_id);
Here my question: It is possible that there are several nodes, but with different roles. How could I manage to get only the node that has the role admin_centre
? Here it would be n240045827
.
The result from above should be sufficient for my question without going too deep into detail about the database structure. The solution would be to take every nth and nth-1 element as a 2D array in order to have the mapping between member and role.
Here is the named relation with its admin_center just for clarification:
https://www.openstreetmap.org/relation/62383
Best Answer
If we can make the assumption that the
unnest()
operation will always return a set where a member "id" is listed followed by its role in a single column oftext
entries, then I've written up a quick solution for you.I made a quick data set as:
Assuming we can return this, then the script
will return only
n240045827
.How does it work? Well, I just took a straightforward approach to assigning order to the returned list by using
row_number() OVER()
. In this example, I've used a CTEWITH...
to add the row after the fact, but in your case you could add it into your query like so:Finally, I simply join this result to itself, comparing one row to the next by its row number, and looking for the
'admin_centre'
text entry.Here's a SQL Fiddle for testing.
For your perusal:
Common Table Expressions
Window Functions
Edit
If you want to return all rows as column pairs, then use the following approach:
Also, here's a SQL Fiddle #2 for testing.