//  home   //  advanced search   //  news   //  categories   //  sql build chart   //  downloads   //  statistics
 ASP FAQ 
Home
ASP FAQ Tutorials

   8000XXXX Errors
   Alerts
   ASP.NET 2.0
   Classic ASP 1.0
   Databases
      Access DB & ADO
      General SQL Server & Access Articles
      MySQL
      Other Articles
      Schema Tutorials
      Sql Server 2000
      Sql Server 2005
   General Concepts
   Search Engine Optimization (SEO)

Contact Us
Site Map

Search

Web
aspfaq.com
tutorials.aspfaq.com
databases.aspfaq.com

ASP FAQ Tutorials :: Databases :: Other Articles :: How do I create a store locator feature?


How do I create a store locator feature?

Many people wish to emulate a "nearest dealer" or "closest store" function. Let's assume you have a Stores table like this: 
 
CREATE TABLE dbo.Stores 

    StoreID INT IDENTITY(1,1) 
        PRIMARY KEY, 
    Storename VARCHAR(32) NOT NULL, 
    Zip CHAR(5) NOT NULL 

GO 
 
SET NOCOUNT ON 
INSERT Stores(Address, Zip) SELECT 'Adelphia Fireworks        ', '07710' 
INSERT Stores(Address, Zip) SELECT 'West Milford BottleRockets', '07480' 
INSERT Stores(Address, Zip) SELECT 'Explosives of Ogdensburg  ', '07439' 
INSERT Stores(Address, Zip) SELECT 'Pompton Cherry Bombs, Inc.', '07442'
 
So, you can imagine a user will enter their Zip code on a web page, let's say it is 07608 (Teterboro, NJ). You can't necessarily determine which store is closest to their Zip based on the value of the Zip code alone, and a relational database doesn't have a very good facility for making such a comparison anyway. You could convert to an INT and take the absolute value of the lowest difference, but that is not a very scientific method at all, since zip codes do not represent equal parts of a grid across the nation. 
 
The first thing you need to find is a high-quality database that contains all the ZIP codes, and correlates them to their coordinates via latitude and longitude. You get what you pay for, so if you find a free version, you will likely spend a long time cleaning it up. One example I found was horribly out of date, had missing/corrupt data, and some values were just plain wrong. It is used later in this article, and I cleaned it up as best as I could; if you want accuracy, you'll want to buy the premium version of one of these products: 
 
    ZipCodeWorld ($40) 
 
    Zip Code Download ($30) 
 
Or search around for similar products from other vendors. 
 
Now, why is it so important to have a database that contains latitude and longitude? Primarily because there are quick and dirty ways to determine the distance between two points, given their lat and long coordinates. The user-defined function (UDF) I built for SQL Server 2000 looks like this: 
 
CREATE FUNCTION dbo.CalculateDistance 

    @zip1 CHAR(5), 
    @zip2 CHAR(5) 

RETURNS DECIMAL(12,3) 
AS 
BEGIN 
    DECLARE 
        @lat1 DECIMAL(10,6), 
        @lon1 DECIMAL(10,6), 
        @lat2 DECIMAL(10,6), 
        @lon2 DECIMAL(10,6), 
        @rads DECIMAL(10,8), 
        @dist DECIMAL(12,3), 
        @calc DECIMAL(10,8) 
 
    SELECT 
        @rads = 57.29577951, 
        @lat1 = lat, 
        @lon1 = long 
        FROM Zips 
        WHERE Zip = @zip1 
 
    SELECT  
        @lat2 = lat, 
        @lon2 = long 
        FROM Zips 
        WHERE Zip = @zip2 
 
    SELECT 
        @lat1 = @lat1 / @rads, 
        @lon1 = @lon1 / @rads, 
        @lat2 = @lat2 / @rads, 
        @lon2 = @lon2 / @rads 
 
    IF @lat1 = @lat2 AND @lon1 = @lon2 
        SET @dist = 0.00 
    ELSE 
    BEGIN 
        SET @calc = SIN(@lat1) * SIN(@lat2) + COS(@lat1) 
            * COS(@lat2) * COS(@lon1 - @lon2) 
        IF (@calc) > 1.0 
            SET @calc = 1.0 
        SET @dist = 3963.1 * ACOS(@calc) 
    END 
 
    RETURN (@dist) 
END 
GO
 
Go ahead and try it with some Zip codes you know are close together, and some you know are far apart: 
 
SELECT dbo.CalculateDistance('02100', '02840') -- Boston, MA to Newport, RI 
SELECT dbo.CalculateDistance('02100', '90210') -- Boston, MA to Beverly Hills, CA
 
Results: 
 
  50.950 
2602.772
 
The calculation is based on this discussion and isn't perfect, of course; it doesn't take into account the fact that Earth isn't a perfect sphere, and it also calculates distances as the crow flies. So the exact mileage won't always be 100% accurate, and occasionally you will find that a store directly across a 4-mile-long, 1-mile-wide lake is deemed closer than the store 1.5 miles the other way. 
 
So now, we need a repository so that we can look up any ZIP in the US, and compare it to our stores. We're going to create a table called Zips: 
 
CREATE TABLE dbo.Zips 

    ZIP CHAR(5) PRIMARY KEY CLUSTERED, 
    City VARCHAR(24) NOT NULL, 
    State CHAR(2) NOT NULL, 
    Lat DECIMAL(10,6) NOT NULL, 
    Long DECIMAL(10,6) NOT NULL 
)
 
And then populate it with Zip codes. The format of the population script will look like this: 
 
SET NOCOUNT ON 
INSERT Zips SELECT '26151', 'MOUNT ZION  ', 'WV', 38.877455, -81.119470 
INSERT Zips SELECT '26152', 'MUNDAY      ', 'WV', 39.021423, -81.204050 
INSERT Zips SELECT '26153', 'MURRAYSVILLE', 'WV', 39.059143, -81.771370 
...
 
However, rather than post it all within the article (it's over 3 MB!), I decided to zip it up so you can download and extract it on your own machine. You can download it here: 
 
    zipsInsert.zip (~645K) 
 
Once it's in place, you can use a stored procedure like this. The initial query is used to find the closest value match for a Zip code if it is not in our Zips table (sometimes a user makes a typo, or sometimes the Zip code database is out of date or incorrect). Usually, this should still yield a relatively accurate measurement and ranking. Feel free to change TOP 3 to whatever number of matches you think might be relevant. 
 
CREATE PROCEDURE dbo.FindNearestStores 
    @zip CHAR(5) 
AS 
BEGIN 
    SET NOCOUNT ON 
 
    IF ISNUMERIC(@zip) = 1 
    BEGIN 
 
        SELECT TOP 1 
            @zip = RIGHT('00000'+Zip, 5) 
        FROM 
            Zips 
        WHERE 
            Zip <= CONVERT(INT, @zip) 
        ORDER BY 
            Zip DESC 
 
 
        IF @zip IS NOT NULL 
        BEGIN 
 
            SELECT TOP 3 
                Miles = dbo.CalculateDistance(Zip, @zip), 
                Storename, 
                Zip 
            FROM 
                Stores 
            ORDER BY 
                Miles, Storename 
 
        END 
    END 
END 
GO
 
This should perform quite well as long as your Stores table is not ultra-huge. If you find performance is a problem because of the UDF against the stores table, you might want to add a WHERE clause: 
 
WHERE State = (SELECT State FROM Zips WHERE Zip = @zip)
 
This will make the query only operate against a single state, rather than all ~40,000 rows. 
 
Here is a sample execution and result set: 
 
EXEC dbo.FindNearestStores '07608' 
 
-- results: 
 
Miles   Storename                   Zip  
------  --------------------------  -----  
15.461  Pompton Cherry Bombs, Inc.  07442 
23.088  West Milford BottleRockets  07480 
32.064  Explosives of Ogdensburg    07439
 
The distance is not calculated based on the numeric value of the zip code, it is just a label here. So the same technique will work for a system based on UK post codes, Canadian postal codes, etc. Provided you can obtain a database which maps the mailing code to a latitude and longitude, the only thing you will do differently is the name of the tables/columns.

Related Articles

How do I build a query with optional parameters?
How do I calculate the median in a table?
How do I deal with MEMO, TEXT, HYPERLINK, and CURRENCY columns?
How do I deal with multiple resultsets from a stored procedure?
How do I debug my SQL statements?
How do I determine if a column exists in a given table?
How do I enable or disable connection pooling?
How do I enumerate through the DSNs on a machine?
How do I find a stored procedure containing <text>?
How do I get a list of Access tables and their row counts?
How do I get the latest version of the JET OLEDB drivers?
How do I handle alphabetic paging?
How do I handle BIT / BOOLEAN columns?
How do I handle error checking in a stored procedure?
How do I ignore common words in a search?
How do I page through a recordset?
How do I present one-to-many relationships in my ASP page?
How do I prevent duplicates in a table?
How do I prevent my ASP pages from waiting for backend activity?
How do I prevent NULLs in my database from mucking up my HTML?
How do I protect my Access database (MDB file)?
How do I protect my stored procedure code?
How do I protect myself against the W32.Slammer worm?
How do I remove duplicates from a table?
How do I rename a column?
How do I retrieve a random record?
How do I return row numbers with my query?
How do I send a database query to a text file?
How do I simulate an array inside a stored procedure?
How do I solve 'Could not find installable ISAM' errors?
How do I solve 'Operation must use an updateable query' errors?
How do I temporarily disable a trigger?
How do I use a SELECT list alias in the WHERE or GROUP BY clause?
How do I use a variable in an ORDER BY clause?
Should I index my database table(s), and if so, how?
Should I store images in the database or the filesystem?
Should I use a #temp table or a @table variable?
Should I use a view, a stored procedure, or a user-defined function?
Should I use recordset iteration, or GetRows(), or GetString()?
What are all these dt_ stored procedures, and can I remove them?
What are the limitations of MS Access?
What are the limitations of MSDE?
What are the valid styles for converting datetime to string?
What datatype should I use for my character-based database columns?
What datatype should I use for numeric columns?
What does "ambiguous column name" mean?
What is this 'Multiple-step OLE DB' error?
What is wrong with 'SELECT *'?
What naming convention should I use in my database?
What should I choose for my primary key?
What should my connection string look like?
When should I use CreateObject to create my recordset objects?
Where can I get this 'Books Online' documentation?
Where do I get MSDE?
Which database platform should I use for my ASP application?
Which tool should I use: Enterprise Manager or Query Analyzer?
Why are there gaps in my IDENTITY / AUTOINCREMENT column?
Why can I not 'open a database created with a previous version...'?
Why can't I access a database or text file on another server?
Why can't I use the TOP keyword?
Why do I get 'Argument data type text is invalid for argument [...]'?
Why do I get 'Not enough space on temporary disk' errors?
Why does ASP give me ActiveX errors when connecting to a database?
Should I use COALESCE() or ISNULL()?
Where can I get basic info about using stored procedures?

 

 


Created: 6/10/2004 | Last Updated: 6/10/2004 | broken links | helpful | not helpful | statistics
© Copyright 2006, UBR, Inc. All Rights Reserved. (228)

 

Copyright 1999-2006, All rights reserved.
Finding content
Finding content.  An error has occured...