Calculating Distances in Microsoft SQL Server

One feature of almost every modern commercial website is the location finder. Enter in a search address, and site displays the closest locations to it with the distances (as the crow flies).

I ran into a couple of peculiar bugs trying to calculate distances in SQL. Floating point math is never easy, and these are the issues I faced using our Microsoft SQL Server database.

Using Google APIs, I was able to get the latitude and longitude for each of our agency locations and added them to the database table as float. The formula for calculating the distance between two latitude/longitude pairs is the Spherical Law of Cosines.

After geolocating the search address (again using Google APIs), getting the closest locations and the distance in miles is relatively easy.

return $mssql_dbh->exec_select( <<DISTANCE_SQL );
select top 10
	rtrim(name01) as name01, rtrim(name02) as name02, rtrim(street) as street,
	rtrim(city) as city, state, zip, rtrim(phone) as phone,
	rtrim(email) as email, rtrim(web) as web,
	latitude, longitude,
	acos(sin(radians($lat)) * sin(radians(latitude)) + cos(radians($lat))
		* cos(radians(latitude)) * cos(radians(longitude) - radians($lon)))
		* 3959
		as distance
from agent_finder_info
	where (latitude > 40 and longitude < 0)
order by distance
DISTANCE_SQL

The where clause ignores any entries in the table that might not have a latitude and longitude association with it.

This appeared to work fine in production for years. One day an agent called and complained that the search for zip code 02185 (Braintree MA) wasn’t working. The agents furthest east of that location (which happened to be in Maine and on Cape Cod) were displayed - and reported as being hundreds of miles away!

Screenshot

The problem is for that area code, the longitude returned by Google is exactly 71 degrees North. Perl treats this as an integer and the distance formula looks like this:

acos(sin(radians(42.22)) * sin(radians(latitude)) + cos(radians(42.22))
	* cos(radians(latitude)) * cos(radians(longitude) - <b>radians(71)</b>))
	* 3959

In the SQL statement, radians(71.0) = -1.23918, but radians(71) = -1! Given an integer argument, radians returns an integer. Who the heck would want that??

My first fix was to explicitly cast the arguments to the radians() function as floats. Of course, I could have also done that in Perl.

return $mssql_dbh->exec_select( <<DISTANCE_SQL );
select top 10
	rtrim(name01) as name01, rtrim(name02) as name02, rtrim(street) as street,
	rtrim(city) as city, state, zip, rtrim(phone) as phone,
	rtrim(email) as email, rtrim(web) as web,
	latitude, longitude,
	acos(sin(radians(<b>cast($lat as float)</b>)) * sin(radians(latitude))
		+ cos(radians(<b>cast($lat as float)</b>)) * cos(radians(latitude))
		* cos(radians(longitude) - radians(<b>cast($lon as float)</b>)))
		* 3959
		as distance
from agent_finder_info
	where (latitude > 40 and longitude < 0)
order by distance
DISTANCE_SQL

This worked great.