Manage spatial data with IBM DB2 Spatial Extender, Part 2
Implementing typical spatial use cases
Tips and techniques for developing efficient spatial applications
This content is part # of # in the series: Manage spatial data with IBM DB2 Spatial Extender, Part 2
This content is part of the series:Manage spatial data with IBM DB2 Spatial Extender, Part 2
Stay tuned for additional content in this series.
For more than 10 years, the DB2 Spatial Extender has provided the capability of managing and analyzing spatial data stored in DB2 relational tables. Table columns can be defined with spatial types for points, lines, and polygons, which can represent objects such as the following:
- Point locations, such as customers, retail stores, electrical transformers, cell towers
- Lines, such as highways, coastline, delivery routes, electrical transmission lines
- Polygons, such as sales or service territories, flood or fire risk, states, provinces, counties
SQL queries can incorporate spatial functions to analyze spatial relationships like finding customers within a flood zone. Listing 1 shows an example of code that incorporates spatial functions.
Query that incorporates spatial functions
SELECT cust_name, cust_addr FROM customers, floodzones WHERE ST_Within(cust_loc, flood_loc) = 1
The result of this query produces a set of customers names and addresses whose houses can be at risk of flooding. This information is useful in calculating insurance rates or sending out an offer to purchase flood insurance.
About this series
This series of tutorials takes you through common tasks that work with spatial data in the DB2 Spatial Extender. These tasks include importing and creating spatial data, constructing and executing spatial queries, working with IBM spatial tools, working with IBM third-party and open source software, improving application performance, and considering special circumstances in a data warehouse environment.
Although the focus of this series is DB2 Spatial Extender on Linux®, UNIX®, and Windows®, many concepts are applicable to other IBM database offerings with spatial capability, including:
- Spatial Support for DB2 for z/OS®
- IBM dashDB
About this tutorial
There are a number of typical use cases in spatial application that include distance, nearest-n, point in polygon, spatial joins, and window queries. This tutorial provides examples for implementing these use cases efficiently.
This tutorial is for application architects and developers who have a working knowledge of DB2 and SQL.
IBM DB2 Server with Spatial Extender
To run the examples in this tutorial, you need a working installation of DB2 with Spatial Extender. If you already have it, you can skip the rest of this section.
Starting with DB2 10.1, Spatial Extender is included in all DB2 server installation images.
To include the spatial functionality, select the Custom option during the DB2 installation and Spatial Extender under both the server and client selections.
IBM Data Studio
The examples were developed with IBM Data Studio 3.1.1. IBM Data Studio is an Eclipse-based environment you can use to develop and test SQL queries, highlight SQL syntax, and show the results of the query execution in tabular form. There is no charge for Data Studio. For details about how to set up a development project, see Data Studio overview.
To simplify the syntax of the spatial functions and predicates,
the schema DB2GSE has not been explicitly prefixed to the spatial
function names. The schema DB2GSE can be implicitly included by
DB2GSE in the default function path. You can
specify this in the dialog when the Data Development project is created in
Data Studio with a list like
The default function path can also be specified in the project properties
in the Database Connection tab. It is important to set
the default function path before creating the queries for it to
be applied to the query when executed.
Other query execution environments
You can run the queries in any environment that lets you execute SQL queries, whether as scripts executed from a command line or in another GUI tool.
To run the queries without specifying the DB2GSE schema, include in your script the command to set the function path with a
FUNCTION PATH = CURRENT FUNCTION PATH, DB2GSE;.
Tutorial sample scripts and data
You can download the sample SQL scripts and data from the Downloads section. Download and unzip these files to a convenient directory to continue.
This tutorial uses the same data files as Part 1: point data for hospitals
and and polygon data for counties. Note that this is sample data and is
not necessarily current or accurate. In your applications, the point data
might represent customer, branch office, ATM, or other locations. Polygon
data might represent service or marketing territories, fire or flood risk
regions, or lakes. The sample SQL scripts include the explicit schema
DB2GSE in the spatial function references. To install the
sample data used in the tutorials, run the setup.sql script from a DB2
command window with the command
db2 -tvf setup.sql.
If you are going to use a database other than the SAMPLE database, modify the setup.sql script to specify the name of the database you want to use.
Point(s) in polygon queries
Two of the most common types of spatial queries are to find what polygon contains a particular point location and to find what points are contained within a particular polygon. With DB2 Spatial Extender you can issue SQL queries from your application for queries like the following examples:
- Find the service territory containing a specified location
- Find the risk (fire, flood, hurricane, earthquake) polygon containing a customer location
- Find retail outlets contained within a specified polygon
Typically, spatial functions have one parameter for the spatial column in a table and another parameter for a spatial constant or a spatial column in a table. Here are some examples of spatial functions you can use for point(s) in polygon queries:
Returns '1' for geom2 values are completely contained by the polygon(s) identified by geom1. This does not include geom2 values, which touch or intersect the boundary of the polygon.
- ST_Within(geom1, geom2)
This is the inverse of ST_Contains, returning '1' for geom1 values are within the polygon(s) identified by geom2.
Returns '1' if geom1 and geom2 spatially interact in any way, touching, crossing or containing each other. For point and polygon values, this is generally the same as ST_Contains and ST_Within, but will also include points which fall exactly on the boundary of the polygon. It is important to note this difference if you get unexpected results.
The examples in this section use a table with a spatial column for one parameter and a spatial constant for the other parameter. The following section on spatial joins provides examples where both parameters are spatial columns.
All spatial functions are defined in the schema DB2GSE, which
must be prefixed to the function name or specified in the DB2
FUNCTION PATH. In the examples that follow, the
FUNCTION PATH has been set in Data Studio order to
make the queries more readable.
Which polygon contains this point?
You can use the following queries to determine the county for a specified location. These queries return equivalent results except for the potential boundary condition mentioned above. If a point happens to fall exactly on a boundary that forms adjacent polygons, ST_Intersects will return polygons and ST_Contains and ST_Within will return neither.
The location is defined by a longitude and latitude value, which in a typical application might have been derived by a user clicking on a location on a map in a web browser. The screen location would need to be converted by application software to the longitude and latitude values.
Contents of pip_query1.sql
SELECT name FROM test.counties WHERE ST_Contains(shape, ST_Point(-74.237, 42.037, 1)) = 1 SELECTIVITY 0.0001 ; SELECT name FROM test.counties WHERE ST_Within(ST_Point(-74.237, 42.037, 1), shape) = 1 SELECTIVITY 0.0001 ; SELECT name FROM test.counties WHERE ST_Intersects(shape, ST_Point(-74.237, 42.037, 1)) = 1 SELECTIVITY 0.0001 ;
These all result in the single county with the name Ulster being returned.
Query results for pip_query1.sql
What is in this polygon?
You can use the following queries to find the hospitals that are located
within the specified polygon. The polygon is defined as a constant using
the well-known text (WKT) representation. The polygon definition consists
of the character string
POLYGON followed by pairs of
x,y coordinates separated by a comma. The individual
x and y values are separated by a space. The entire
list of coordinate pairs must be in parentheses.
The x and y values in this example are longitude and latitude values describing a rectangle one degree on each side for the sake of simplicity. A polygon definition might contain any number of coordinate pairs (well, less than about 500,000) and can represent a complex shape such as a lake or county boundary.
Contents of pip_query2.sql
SELECT name FROM test.hospitals WHERE ST_Contains(ST_Polygon( 'POLYGON ((-74.0 42.0, -73.0 42.0, -73.0 43.0, -74.0 43.0, -74.0 42.0))', 1) , location) = 1 SELECTIVITY 0.0001 ; SELECT name FROM test.hospitals WHERE ST_Within(location, ST_Polygon( 'POLYGON ((-74.0 42.0, -73.0 42.0, -73.0 43.0, -74.0 43.0, -74.0 42.0))' , 1)) = 1 SELECTIVITY 0.0001 ; SELECT name FROM test.hospitals WHERE ST_Intersects(location, ST_Polygon( 'POLYGON ((-74.0 42.0, -73.0 42.0, -73.0 43.0, -74.0 43.0, -74.0 42.0))' , 1)) = 1 SELECTIVITY 0.0001 ;
These queries return 21 hospitals, only nine of which are shown in the result window.
Query results for pip_query2.sql
Spatial join queries
Just as queries can join two tables based on the relationship of values in character and numeric columns, spatial functions can be used to join tables based on the relationship of spatial values in table columns. This capability is very important for business intelligence and spatial analytic applications such as risk analysis and demographic analysis. The following examples use the customers and hospitals tables based on common customer cases.
What's in a polygon that's defined in another spatial table?
You can use the following query to find the hospitals located within a
specified county. The polygon representing the county is selected by the
c.name = 'Dutchess'.
Contents of join_query1.sql
SELECT h.name, c.name FROM test.hospitals AS h, test.counties AS c WHERE ST_Intersects(h.location, c.shape) = 1 SELECTIVITY 0.0001 AND c.name = 'Dutchess' ;
This query returns eight hospitals.
Query results for join_query1.sql
The application developer also can use SQL
... ON notation, which is
equivalent to a spatial predicate in the
Contents of join_query2.sql
SELECT h.name, c.name FROM test.hospitals AS h JOIN test.counties AS c ON ST_Intersects(h.location, c.shape) = 1 SELECTIVITY 0.0001 AND c.name = 'Dutchess' ;
Note that specifying the
JOIN is equivalent to specifying the
INNER JOIN clause.
OUTER JOIN operations are not
supported for spatial predicates.
If you have tables that do not contain a spatial column but that contain existing longitude and latitude columns, you can use these values to dynamically create point values in the query, although it might not be as efficient as having the point spatial values stored in the table.
In this query, the longitude and latitude columns in the hospitals table are used to dynamically create point values, which can be tested against the county polygon.
Contents of join_query3.sql
SELECT h.name, c.name FROM test.hospitals AS h, test.counties AS c WHERE ST_Intersects(ST_Point(longitude, latitude,1), c.shape) = 1 SELECTIVITY 0.0001 AND c.name = 'Dutchess' ;
Conversely, an application can determine which county a particular hospital is in by slightly modifying the query to identify the hospital and return the name of the county it is in as follows.
Contents of join_query4.sql
SELECT h.name, c.name FROM test.hospitals AS h, test.counties AS c WHERE ST_Intersects(h.location, c.shape) = 1 SELECTIVITY 0.0001 AND h.name = 'Vassar Brothers Hospital' ;
Query results for join_query4.sql
If this type of query is required on a regular basis and is relatively static, it might be more efficient to add a column to the hospitals table and populate it with the corresponding county name. The following SQL statements alter the hospitals table to add a county name column and update each row with the county name.
Contents of update_hospitals.sql
ALTER TABLE test.hospitals ADD COLUMN county_name VARCHAR(32); UPDATE test.hospitals AS h SET h.county_name = ( SELECT c.name FROM test.counties AS c WHERE ST_Intersects(h.location, c.shape) = 1 SELECTIVITY 0.0001 ) ; SELECT h.name, h.county_name FROM test.hospitals AS h WHERE h.name = 'Vassar Brothers Hospital' ;
Results of update_hospitals.sql
Analysis with spatial joins
This section provides examples of queries that use spatial joins in conjunction with additional predicates and aggregation, which can address business problems. These examples continue to use the hospitals and counties tables, but you should be able to use your imagination to consider how they could be applied to different tables of business data.
This example queries the hospitals within each county in New York state, qualifying by the state name in the counties table.
Contents of join_query5.sql
SELECT h.name AS hospital_name, c.name AS county_name FROM test.hospitals AS h, test.counties AS c WHERE ST_Intersects(h.location, c.shape) = 1 SELECTIVITY 0.0001 AND c.state_name = 'New York' ORDER BY c.name, h.name
The result shown is the first nine of the 230 hospitals in New York.
Results of join_query5.sql
You can also use the state information in the hospitals table and obtain the same results.
Contents of join_query6.sql
SELECT h.name AS hospital_name, c.name AS county_name FROM test.hospitals AS h, test.counties AS c WHERE ST_Intersects(h.location, c.shape) = 1 SELECTIVITY 0.0001 AND h.state = 'NY' ORDER BY c.name, h.name
This example summarizes the number of hospitals in each county in New York.
Contents of join_query7.sql
SELECT c.name AS county_name, count(h.name) AS hospital_count FROM test.hospitals AS h, test.counties AS c WHERE ST_Intersects(h.location, c.shape) = 1 SELECTIVITY 0.0001 AND c.state_name = 'New York' GROUP BY c.name
Results of join_query7.sql
If you are interested in knowing which counties don't have any hospitals,
NOT EXISTS expression, which checks for the hospitals
in each county. The previous query can only identify the counties that
have one or more hospitals because the query will not return any rows to
count if a county does not contain any hospitals.
Contents of join_query8.sql
SELECT c.name AS county_name FROM test.counties AS c WHERE c.state_name = 'New York' AND NOT EXISTS ( SELECT h.id FROM test.hospitals AS h WHERE ST_Intersects(h.location, c.shape) = 1 SELECTIVITY 0.0001) ORDER BY c.name
The result shows that 32 New York counties have no hospitals.
Results of join_query8.sql
To identify counties where the population is underserved by hospitals, an interesting metric might be the number of people per hospital in each county. Using the population for each county in the year 2000, you can calculate this number.
Contents of join_query9.sql
SELECT c.name AS county_name, count(h.name) AS hospital_count ,c.pop2000 AS "2000 population", c.pop2000 / count(h.name) AS people_per_hospital FROM test.hospitals AS h, test.counties AS c WHERE ST_Intersects(h.location, c.shape) = 1 SELECTIVITY 0.0001 AND c.state_name = 'New York' GROUP BY c.name, c.pop2000 ORDER BY people_per_hospital DESC
Results of join_query9.sql
With additional details about the number of beds or the number of doctors in each hospital, you can determine a better measure of health care capability.
A common use case for mapping applications and in particular for web
mapping applications is to select objects that fall within a rectangular
region. This can be done by creating a polygon to represent the rectangle
ST_Intersects spatial predicate. However, there is
another spatial predicate,
EnvelopesIntersect, which can be
used to select objects whose envelope intersect a rectangular region. This
spatial predicate is simpler to use and is more efficient in most cases.
One factor to consider is that since this predicate only compares the envelope of a geometry, it might select line and polygon geometries that are outside the specified window. This is generally not a problem with mapping applications, which will discard geometries outside the display window. This predicate is accurate for point data.
EnvelopesIntersect takes as parameters the name of a spatial
DOUBLE values representing the lower-left and
upper-right corners of a rectangle, and an
INTEGER value of
the spatial reference system.The following query shows an example that
Contents of join_query9.sql
SELECT name FROM test.hospitals WHERE ST_Intersects(location, ST_Polygon( 'POLYGON ((-74.0 42.0, -73.0 42.0, -73.0 43.0, -74.0 43.0, -74.0 42.0))' , 1)) = 1 SELECTIVITY 0.0001 ;
This query can be rewritten as follows.
Contents of window_query1.sql
SELECT name FROM test.hospitals WHERE EnvelopesIntersect(location, -74.0, 42.0, -73.0, 43.0, 1) = 1 SELECTIVITY 0.0001 ;
When building web-mapping applications with widely used web-mapping APIs from providers such as Google Maps, Yahoo! Maps, Bing Maps, and others, it is necessary to provide the longitude and latitude values to be used in placing custom markers on the map. Obtain this information with a query like the following.
Contents of window_query2.sql
SELECT name, location..ST_X AS longitude, location..ST_Y AS latitude FROM test.hospitals WHERE EnvelopesIntersect(location, -74.0, 42.0, -73.0, 43.0, 1) = 1 SELECTIVITY 0.0001 ;
Results of window_query2.sql
Another common type of spatial query is to find things within a specified distance of a particular particular location. You have probably used web mapping applications to get this kind of information. With DB2 Spatial Extender, you can issue SQL queries from your application for queries like:
- Finding customers within 10 miles of a store
- Finding ATMs within 500 meters of the current location
- Finding competitive stores within 10 kilometers of a proposed store location
There are a number of ways to do this with different performance characteristics.
The primary spatial function is ST_Distance, which computes the distance between spatial values and which support an optional units parameter. The default units returned by ST_Distance are the units of the coordinate system. This means that if you are using the NAD83 or WGS84 coordinate systems, which use degrees of longitude and latitude, the distances are returned in degrees which are not the most user-friendly. The example use case is to find the hospitals within 30 miles of the specified location (Woodstock, NY).
Don't do this: Contents of distance_query1.sql
SELECT name FROM test.hospitals WHERE ST_Distance(location, ST_Point(-74.237, 42.037, 1)) < 30.0 ORDER BY name ;
This query has a surprising result, with 500 rows returned when there are only eight hospitals within 30 miles. The query is actually finding all the hospitals within a distance of 30 degrees, which are the coordinate units of the data; 30 degrees is equivalent to more than 1,000 miles.
Query results for distance_query1.sql
Correct result but slow
Changing the query to specify a unit parameter of
STATUTE MILE returns the correct results, but takes a
surprisingly long time — more than 1 second to return just three
rows. The reason for this is that the spatial index can not be exploited
units parameter is specified on the
ST_Distance predicate. If the spatial index is not exploited,
the distance is calculated from the specified point to every location in
the table, which is computationally intensive.
Contents of distance_query2.sql
SELECT name FROM test.hospitals WHERE ST_Distance(location, ST_Point(-74.237, 42.037, 1), 'STATUTE MILE') < 30.0 ORDER BY name ;
Query results for distance_query2.sql
Correct result but fast
The spatial index can be exploited by including an additional predicate on the query, which approximates the linear distance in degrees. We calculate this by dividing the desired distance in miles by the number of miles in a degree. Unfortunately, the number of miles in a degree decreases as one increases the latitude. At the equator, a degree of longitude is about 69.2 miles, while at 50 degrees latitude, a degree of longitude is about 44.6 miles.
The length of a degree of longitude at a particular latitude can be
computed with a query like
(st_distance(st_point(0.0, 50.0, 1),st_point(1.0, 50.0, 1),'STATUTE MILE'));,
where 50.0 is the latitude.
Fifty degrees is the northern boundary of the continental United States, which makes 44.6 a useful factor for queries against this region. For other regions, you need to consider the northern-most or southern-most (below the equator) latitude boundary of your data.
You can influence the DB2 query optimizer to exploit the spatial index by
SELECTIVITY clause, resulting in the following
Contents of distance_query3.sql
SELECT name FROM test.hospitals WHERE ST_Distance(location, ST_Point(-74.237, 42.037, 1), 'STATUTE MILE') < 30.0 AND DB2GSE.ST_Distance(location, DB2GSE.ST_Point(-74.237, 42.037, 1)) < (30.0 / 44.6) SELECTIVITY 0.0001 ORDER BY name ;
This query provides the correct results in a fraction of a second.
A simpler approach
In the previous query, the constructor for the point using longitude and latitude was duplicated in the query. It is preferable to create the point only once both in terms of performance and query complexity. A common table expression can be used to construct the point once and reference it in both predicates where it is needed.
Contents of distance_query4.sql
WITH current_location(point) AS ( VALUES(ST_Point(-74.237, 42.037, 1)) ) SELECT name FROM test.hospitals, current_location WHERE ST_Distance(location, point , 'STATUTE MILE') < 30.0 AND ST_Distance(location, point) < 30.0 / 44.6 SELECTIVITY 0.0001 ORDER BY name ;
An even-simpler approach
If you use DB2 9.7 fixpack 5 or later, or DB2 10.1 and are working with
point data, you can use a different approach. Finding objects within a
specified distance is equivalent to finding the objects whose location
intersects a circular buffer of the specified distance around the desired
ST_Buffer function takes as parameters a spatial
value and a distance to buffer around the spatial value. An optional third
parameter specifies a unit value. In the following query an accurate
buffer in degrees is constructed around the specified point which
corresponds to a linear distance of 30 statute miles.
Contents of distance_query5.sql
SELECT name FROM test.hospitals WHERE ST_Intersects(location, ST_Buffer(ST_Point(-74.237, 42.037, 1), 30.0, 'STATUTE MILE')) = 1 SELECTIVITY 0.0001 ORDER BY NAME ;
Returning the distances to each object
Continuing the previous example using
distance from the specified point to each object within 30 miles can be
found with this query. Since the application also needs the actual
distance returned as well as testing within the distance specified, we use
another common table expression, which does the approximate distance
selection in degrees, selecting the name and distance. The final
SELECT qualifies by the distance in miles. The
DECIMAL function is applied to the distance to make the
output more readable. The result set is then ordered by the distance
Contents of distance_query6.sql
WITH current_location(point) AS ( VALUES(ST_Point(-74.237, 42.037, 1)) ), work_tab(name, distance) AS ( SELECT name ,ST_Distance(location, point , 'STATUTE MILE') AS distance FROM test.hospitals, current_location WHERE ST_Distance(location, point) < (30.0 / 44.6) SELECTIVITY 0.0001 ) SELECT name, DECIMAL(distance, 8, 2) AS distance FROM work_tab WHERE distance < 30.0 ORDER BY distance ;
Query results for distance_query6.sql
Similarly, continuing on the previous example using
ST_Buffer, the distance from the specified point to each
object within 30 miles can be found with the following query.
Calculate distance from specified point to each object within 30 miles
WITH current_location(point) AS ( VALUES(ST_Point(-74.237, 42.037, 1)) ) SELECT name ,DECIMAL(ST_Distance(location, point , 'STATUTE MILE'), 8, 2) AS distance FROM test.hospitals, current_location WHERE ST_Intersects(location, ST_Buffer(point, 30.0, 'STATUTE MILE')) = 1 SELECTIVITY 0.0001 ORDER BY distance ;
Since the examples using
ST_Buffer are simpler, you might
wonder why the more complex examples using
shown previously. There are two main reasons:
- If you want to find the distance from a spatial type other than a
point, for example a line or a polygon, the
ST_Bufferfunction can not be used in this case.
- Depending on your specific application, one approach might provide better performance than the other. The computation of the buffer is computationally intensive. If this is being done only a few times and the table being queried is fairly large, this computation time might not be significant. However, if the application needs to perform this query for a large number of points, the time to construct the buffer for each point might become a significant portion of the processing time.
Nearest and distance ranking queries
Although an application is often interested in all the objects within a specified location, other common use cases related to distance are:
- Return the nearest object
- Return the nearest n objects
- Return the nearest n objects with rank order
Returning the nearest n objects
A common use case is to find the nearest or nearest n objects to
a specified location. The number of rows returned can be limited by adding
FETCH FIRST expression to the SQL query. In the example
below, the first five rows are being returned. If you only wanted the
first row, you could specify
FETCH FIRST ROW ONLY
Contents of nearest_query1.sql
WITH current_location(point) AS ( VALUES(ST_Point(-74.237, 42.037, 1)) ) SELECT name ,DECIMAL(ST_Distance(location, point , 'STATUTE MILE'), 8, 2) AS distance FROM test.hospitals, current_location WHERE ST_Intersects(location, ST_Buffer(point, 30.0, 'STATUTE MILE')) = 1 SELECTIVITY 0.0001 ORDER BY distance FETCH FIRST 5 ROWS ONLY ;
Query results for nearest_query1.sql
Although the query works well in this case, there are two important considerations:
- With the buffer distance specified as 30 miles, there might not be enough rows within this distance to satisfy the application need for a particular number of rows, if for example, the specified location is in a remote area.
- Within the specified distance, there might be a large number of rows within this distance, for example, if the specified location is in a metropolitan area like New York City.
In the first case, it might be adequate to have a fixed radius to search. If there are fewer objects than the limit specified, this is an acceptable response.
An approach to ensure that the application will always get the number of rows desired is to iteratively reissue the query with larger distance values until the desired number of rows are returned. This presumes that the SQL query is being issued by application logic written in a language such as Java™, C, Perl, etc. Or it can be implemented as a DB2 stored procedure using the DB2 SQL PL language, which is shown in the next example.
Returning the nearest n objects with a stored procedure
In this example, a stored procedure incorporates the logic required to find the nearest objects to a given location specified in latitude and longitude. The stored procedure starts with a search distance specified as an input parameter. If insufficient rows are returned, the distance value is doubled and the query reissued. This process is continued until the desired number of rows are returned. Although there is obviously additional cost in issuing the query multiple times, if you pick a reasonable starting distance, the desired results will come back on the first query. If they are not, the first query will have executed quickly to process a small number of rows and subsequent queries will re-use data cached in the DB2 buffer pool.
Contents of nearest_sp1.sql
-- ####################################################################### -- # Leaves cursor open to return nearest row(s) -- # Input parameters are specified location in longitude, latitude -- # and a starting target distance in miles for nearest search. -- # Although not necessary, this stored procedure returns as values -- # the final distance to satisfy the nearest requirement and -- # the number of rows within that distance. -- ####################################################################### CREATE PROCEDURE TEST.HOSPITAL_NEAREST (IN P_LONGITUDE DOUBLE, IN P_LATITUDE DOUBLE , IN P_STARTDISTANCE DOUBLE , OUT FINALDISTANCE DOUBLE , OUT FOUNDCOUNT INT) DYNAMIC RESULT SETS 1 P1: BEGIN DECLARE V_DISTANCE DOUBLE; DECLARE V_FOUNDCOUNT INT DEFAULT 0; DECLARE V_ROWSNEEDED INT DEFAULT 5; -- Query to return data rows to application DECLARE RETURN_CURSOR CURSOR WITH RETURN FOR WITH CURRENT_LOCATION(POINT) AS ( VALUES(ST_POINT(P_LONGITUDE, P_LATITUDE, 1)) ) SELECT NAME ,DECIMAL(ST_DISTANCE(LOCATION, POINT , 'STATUTE MILE'), 8, 2) AS DISTANCE FROM TEST.HOSPITALS, CURRENT_LOCATION WHERE ST_INTERSECTS(LOCATION, ST_BUFFER(POINT, V_DISTANCE, 'STATUTE MILE')) = 1 SELECTIVITY 0.0001 ORDER BY DISTANCE FETCH FIRST 5 ROWS ONLY ; -- Query to check number of rows within current distance DECLARE COUNT_CURSOR CURSOR FOR WITH CURRENT_LOCATION(POINT) AS ( VALUES(ST_POINT(P_LONGITUDE, P_LATITUDE, 1)) ) SELECT COUNT(*) FROM TEST.HOSPITALS, CURRENT_LOCATION WHERE ST_INTERSECTS(LOCATION, ST_BUFFER(POINT, V_DISTANCE, 'STATUTE MILE')) = 1 SELECTIVITY 0.0001; SET V_DISTANCE = P_STARTDISTANCE; FETCH_LOOP: LOOP OPEN COUNT_CURSOR; FETCH COUNT_CURSOR INTO V_FOUNDCOUNT; CLOSE COUNT_CURSOR WITH RELEASE; IF V_FOUNDCOUNT >= V_ROWSNEEDED THEN LEAVE FETCH_LOOP; END IF; SET V_DISTANCE = V_DISTANCE * 2; END LOOP FETCH_LOOP; SET FINALDISTANCE = V_DISTANCE; SET FOUNDCOUNT = V_FOUNDCOUNT; -- Open cursor to fetch rows for client application OPEN RETURN_CURSOR; END P1
Testing in Data Studio
You can easily create the stored procedure in the Data studio Data Project Explorer by right-clicking on the Stored Procedures folder under the project name and selecting New. Copy and paste the above code and save the stored procedure. In order to use the stored procedure, you must first deploy the stored procedure to the database by right-clicking on the stored procedure name and selecting Deploy. You can then execute the stored procedure by right-clicking on the name and selecting Run, which will allow you to specify the input parameters before running the stored procedure.
Testing in a command-line environment
You can also deploy the stored procedure by executing the sample SQL with
db2 -td@ -vf nearest_sp1.sql. Since the stored
; as the statement end character, the
@ needs to be at the end of the file to mark the end of the
stored procedure, and the
-td@ option must be specified when
executing the SQL.
You can then execute the stored procedure with
call test.hospital_nearest(-74.237, 42.037, 5.0, ?, ?).
In this sample run, the location is in a rural area (Woodstock, N.Y.) and starting with a starting search radius of 5 miles, the radius needs to be doubled three times to 40 miles in order to find at least five objects. It actually finds 14 objects within 40 miles.
Sample execution 1
Sample execution 2
In this sample run, the location is in a major city (Manhattan) and starting at the same starting radius of 5 miles, 36 objects are found at this radius, satisfying the requirement to find the five nearest.
Sample execution 3
Sample execution 4
Returning the nearest n objects with ranking
Another common use case is to not only find the nearest objects but to rank them numerically with an integer value starting with 1. This example finds the three nearest hospitals to the specified location. Other typical use cases would be to find the nearest fire stations for insurance purposes or to find the nearest stores.
Contents of rank_query1.sql
WITH current_location(point) AS ( VALUES(ST_Point(-73.976, 40.740, 1)) ) SELECT * from ( SELECT name ,DECIMAL(ST_Distance(point, location, 'STATUTE MILE'), 8, 3) ,ROW_NUMBER() OVER (ORDER BY ST_Distance(point, location, 'STATUTE MILE') ASC) FROM test.hospitals, current_location WHERE ST_Intersects(location, ST_Buffer(point, 10.0, 'STATUTE MILE')) = 1 ) temp (name, miles, rownumber) WHERE rownumber <= 3 ;
This query is similar to previous distance queries, but instead of
ordering by distance and select the first n, it uses the
ROW_NUMBER() OVER expression to assign row numbers based on
the distance ordering.
Query results from rank_query1.sqlH
This tutorial has shown examples of typical spatial use cases and how to implement them efficiently in DB2 SQL queries using spatial functions. Subsequent tutorials will cover tuning for the best performance, working in a data warehouse environment and using visualization tools.
I thank Amyris Rada for her review and suggestions for improving this tutorial.
- DB2 Knowledge Center
- Esri shapefile specification
- Download DB2 Express-C
- Download IBM DB2 Spatial Extender
- Download IBM Data Studio