I am writing a table valued function to geocode an address based on its Suburb, State and Postcode. I try to geocode the address using different methods, in order of decreasing accuracy:
- Exact match on unique suburb-postcode-state combination
- Exact match on unique suburb-postcode combination
- Exact match on unique suburb-state combination
- Exact match on unique postcode
- Approximate match by non-unique Postcode, where all Suburbs with this Postcode are within 5 km of one another.
(I am working with a geographical region where Suburb-Postcode-State relationships are all many-to-many. In other words, one Suburb can have multiple postcodes; one postcode can have multiple suburbs and may exist in different states.)
Following is an extract from the table-valued function:
ALTER FUNCTION [geocode].[tvfn_Customer_Suburb_From_Address]
(
@Suburb NVARCHAR(100),
@State NVARCHAR(100),
@Postcode NVARCHAR(100),
@Country NVARCHAR(100)
)
RETURNS TABLE
AS
RETURN
(
SELECT TOP 1 *
FROM (
-- Unique suburb-postcode-state combinations
SELECT s.Suburb_DID
,s.Suburb
,s.State
,s.Postcode
,Geocode_DID = 4 -- Exact match by unique Postcode, Suburb and State
,s.Geocode_Latitude
,s.Geocode_Longitude
FROM geocode.tSuburbs_XX s
INNER JOIN [geocode].[tGeocode_Methods] gm
ON s.Geocode_DID = gm.Geocode_DID
WHERE s.[Is_Active] = 1
AND s.[Suburb] = @Suburb
AND s.[State] = @State
AND s.[Postcode] = @Postcode
-- Only suburbs that are geocoded with methods that can be used for geocoding customers
AND gm.[Can_Use_For_VIP] = 1
UNION ALL
-- -- Unique suburb-postcode combinations
SELECT s.Suburb_DID
,s.Suburb
,s.State
,s.Postcode
,Geocode_DID = 3 -- Exact match by unique Postcode & Suburb
,s.Geocode_Latitude
,s.Geocode_Longitude
FROM geocode.tSuburbs_XX s
INNER JOIN [geocode].[tGeocode_Methods] gm
ON s.Geocode_DID = gm.Geocode_DID
WHERE EXISTS ( SELECT *
FROM geocode.tSuburbs_XX
WHERE Is_Active = 1
AND Suburb = s.Suburb AND Postcode = s.Postcode
GROUP BY Postcode, Suburb
HAVING COUNT(*) = 1
)
AND s.Is_Active = 1
AND s.[Suburb] = @Suburb
AND s.[Postcode] = @Postcode
-- Only suburbs that are geocoded with methods that can be used for geocoding customers
AND gm.[Can_Use_For_VIP] = 1
UNION ALL
-- Exact match by unique Suburb and State
SELECT s.Suburb_DID
,s.Suburb
,s.State
,s.Postcode
,Geocode_DID = 6 -- Exact match by unique Suburb and State
,s.Geocode_Latitude
,s.Geocode_Longitude
FROM geocode.tSuburbs_XX s
INNER JOIN [geocode].[tGeocode_Methods] gm
ON s.Geocode_DID = gm.Geocode_DID
WHERE EXISTS ( SELECT *
FROM geocode.tSuburbs_XX
WHERE Is_Active = 1 AND Is_PO_Box = 0 -- Exclude PO Boxes
AND Suburb = s.Suburb AND Postcode = s.Postcode
GROUP BY Suburb, Postcode
HAVING COUNT(*) = 1
)
AND s.Is_Active = 1
AND s.[Suburb] = @Suburb
AND s.[Postcode] = @Postcode
-- Only suburbs that are geocoded with methods that can be used for geocoding customers
AND gm.[Can_Use_For_VIP] = 1
UNION ALL
-- Exact match by unique Postcode
SELECT s.Suburb_DID
,s.Suburb
,s.State
,s.Postcode
,Geocode_DID = 2 -- Exact match by unique Postcode
,s.Geocode_Latitude
,s.Geocode_Longitude
FROM geocode.tSuburbs_XX s
INNER JOIN [geocode].[tGeocode_Methods] gm
ON s.Geocode_DID = gm.Geocode_DID
WHERE EXISTS ( SELECT *
FROM geocode.tSuburbs_XX
WHERE Is_Active = 1
AND Postcode = s.Postcode
GROUP BY Postcode
HAVING COUNT(*) = 1
)
AND s.Is_Active = 1
AND s.[Postcode] = @Postcode
-- Only suburbs that are geocoded with methods that can be used for geocoding customers
AND gm.[Can_Use_For_VIP] = 1
-- Perform this extra check to make sure we don't match a postcode in a wrong country
AND ( @Country IN ('AAA', 'BBB', 'CCC')
OR @State IN ('MMM', 'NNN', 'OOO', 'PPP')
)
UNION ALL
-- Approximate match by non-unique Postcode, where all Suburbs with this Postcode are within 5 km of one another.
SELECT s.Suburb_DID
,s.Suburb
,s.State
,s.Postcode
,Geocode_DID = 5
,s.Geocode_Latitude
,s.Geocode_Longitude
FROM [geocode].[tPostcode_Distances] pd
INNER JOIN geocode.tSuburbs_XX s
ON pd.Approx_Suburb_DID = s.Suburb_DID
INNER JOIN [geocode].[tGeocode_Methods] gm
ON s.Geocode_DID = gm.Geocode_DID
WHERE s.Is_Active = 1
AND pd.[Postcode] = @Postcode
-- Only suburbs that are geocoded with methods that can be used for geocoding customers
AND gm.[Can_Use_For_VIP] = 1
-- Perform this extra check to make sure we don't match a postcode in a wrong country
AND ( @Country IN ('AAA', 'BBB', 'CCC')
OR @State IN ('MMM', 'NNN', 'OOO', 'PPP')
)
AND pd.Max_Distance <= 5000 -- within 5 km
) t
)
The above function works, but I would like to know if it can be improved. In particular, is it possible to force SQL Server to stop processing the SELECT
statements after the first SELECT
that returns a result-set (because we're interested in the first matching result only - TOP 1
)?
Update
Thank you for suggestions thus far. I will add a [Priority]
column as suggested in several answers and comments and an ORDER BY
clause to ensure I get the top result.
I will also add a WITH SCHEMABINDING
to the TVFN so SQL Server can parallelise the plan. While we're on this subject, the using a multi-statement table-valued function is a good idea (Thank you to Paul White), but multi-statement TVFN's always force a serial plan.
I will now try Lennart's answer, in which he suggested using a CTE.