Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Applies to: ✅ SQL analytics endpoint and Warehouse in Microsoft Fabric
String similarity functions compare two strings and return how different, or how similar, they are. Use them to find values that don't match exactly because of spelling errors, transposed or missing characters, abbreviations, regional spelling differences, or different spellings of names that sound the same. Typical scenarios include deduplicating customer records, matching product names across sources, and cleaning manually entered data.
Warehouse and SQL analytics endpoint support two groups of string similarity functions, which you can use together:
- Fuzzy string matching functions compare the characters in two strings:
EDIT_DISTANCE,EDIT_DISTANCE_SIMILARITY,JARO_WINKLER_DISTANCE, andJARO_WINKLER_SIMILARITY. - Phonetic functions compare how two strings sound when spoken in English:
SOUNDEXandDIFFERENCE.
Supported functions
The following scalar functions are available in Warehouse and and SQL analytics endpoint.
| Function | Return type | Description |
|---|---|---|
| EDIT_DISTANCE | int |
Returns the minimum number of insertions, deletions, substitutions, and transpositions needed to transform one string into the other. A lower value means the strings are more similar. |
| EDIT_DISTANCE_SIMILARITY | int |
Returns a similarity score based on edit distance, from 0 (no similarity) to 100 (exact match). |
| JARO_WINKLER_DISTANCE | float |
Returns the Jaro-Winkler distance between two strings, giving preference to strings that match from the beginning. A lower value means the strings are more similar. |
| JARO_WINKLER_SIMILARITY | int |
Returns a Jaro-Winkler similarity score from 0 (no similarity) to 100 (exact match). |
| SOUNDEX | varchar |
Returns a four-character code that represents how a string sounds when spoken in English. Strings that sound alike return the same code. |
| DIFFERENCE | int |
Compares the SOUNDEX codes of two strings and returns a value from 0 (no similarity) to 4 (the same or very similar sound). |
Syntax
EDIT_DISTANCE ( character_expression, character_expression [ , maximum_distance ] )
EDIT_DISTANCE_SIMILARITY ( character_expression, character_expression )
JARO_WINKLER_DISTANCE ( character_expression, character_expression )
JARO_WINKLER_SIMILARITY ( character_expression, character_expression )
SOUNDEX ( character_expression )
DIFFERENCE ( character_expression, character_expression )
Choose a function
- EDIT_DISTANCE and EDIT_DISTANCE_SIMILARITY implement the restricted Damerau-Levenshtein algorithm, also known as Optimal String Alignment (OSA). Each edit is an insertion, deletion, or substitution of one character, or a transposition of two adjacent characters. Use them when every character change counts equally.
- JARO_WINKLER_DISTANCE and JARO_WINKLER_SIMILARITY use the Jaro-Winkler algorithm, which gives extra weight to strings that share the same prefix. Use them for person names, company names, and other values where the beginning of the string is usually entered correctly.
- SOUNDEX and DIFFERENCE implement the Soundex phonetic algorithm, which ignores vowels and groups consonants that sound alike. Use them for English names and words that are spelled differently but pronounced the same, such as
SmithandSmythe. Don't use them for codes, identifiers, or non-English text. BecauseSOUNDEXkeeps the first letter as-is and ignores vowels, it can miss names that sound the same, such asCatherineandKatherine, and match names that are different, such asRobertandRupert. - Use the distance functions (
EDIT_DISTANCEandJARO_WINKLER_DISTANCE) when you want to filter by an absolute threshold of changes. Use the similarity functions (EDIT_DISTANCE_SIMILARITYandJARO_WINKLER_SIMILARITY) when you want a normalized0-100score that's easy to compare across values of different lengths. UseDIFFERENCEwhen you want a phonetic score from0to4, and compareSOUNDEXcodes when you want an exact phonetic match, for example as a join key.
Alternative: compare with a collation
If values differ only by case, accents, Japanese kana type, or character width, such as Café and CAFE, you don't need a similarity function. Compare the values with the COLLATE clause and a collation that ignores the difference, for example Latin1_General_100_CI_AI, to get an exact match. An exact comparison is simpler and faster than a similarity score. In Warehouse and SQL analytics endpoint, the COLLATE clause in a string expressions accepts any collation. For more information, see COLLATE (Transact-SQL) and Data Warehouse collation.
Considerations
- The character expressions can be a constant, variable, or column.
- If any input of a fuzzy string matching function is
NULL, the function returnsNULL. - The optional
maximum_distanceargument ofEDIT_DISTANCElets the function stop processing early. If the actual distance is greater thanmaximum_distance, the function returnsmaximum_distance + 1. Use it to improve performance when you only need to know whether values are within a threshold. - Comparison behavior, including case sensitivity, is determined by the collation of the input expressions. With the default case-sensitive collation,
'Contoso'and'contoso'aren't an exact match. To compare values regardless of case, normalize them withUPPER()orLOWER()first.SOUNDEXis also collation sensitive.
Examples
The examples use inline data, so you can run them in any warehouse or SQL analytics endpoint.
A. Compare two strings
The following example compares the UK and US spelling of the same word with all the string similarity functions.
SELECT EDIT_DISTANCE('Colour', 'Color') AS ED_Distance,
EDIT_DISTANCE_SIMILARITY('Colour', 'Color') AS ED_Similarity,
JARO_WINKLER_DISTANCE('Colour', 'Color') AS JW_Distance,
JARO_WINKLER_SIMILARITY('Colour', 'Color') AS JW_Similarity,
SOUNDEX('Colour') AS Soundex1,
SOUNDEX('Color') AS Soundex2,
DIFFERENCE('Colour', 'Color') AS SoundDiff;
Here's the result set.
ED_Distance ED_Similarity JW_Distance JW_Similarity Soundex1 Soundex2 SoundDiff
----------- ------------- ------------------ ------------- -------- -------- ---------
1 83 0.0333333333333333 97 C460 C460 4
Colour becomes Color with one deletion, so the edit distance is 1. Both strings start with the same four characters, so the Jaro-Winkler similarity is higher. The removed u is a vowel, which SOUNDEX ignores, so both words return the code C460 and DIFFERENCE returns 4.
B. Compare a value with similar words
The following example compares one value, stored in the @word variable, with a set of similar words defined in a common table expression (CTE). Each word differs from Warehouse by a known number and type of edits, so you can see how each kind of change affects the result of each function. The comment next to each word describes how it differs from Warehouse.
DECLARE @word VARCHAR(50) = 'Warehouse';
WITH Words AS (
SELECT Word
FROM (VALUES
('Warehouse'), -- Exact match
('Warehouses'), -- One insertion at the end
('Warehousse'), -- One insertion in the middle
('Warehous'), -- One deletion at the end
('Warehuse'), -- One deletion in the middle
('Xarehouse'), -- One substitution of the first character
('Warehiuse'), -- One substitution in the middle
('Warehousx'), -- One substitution of the last character
('Wraehouse'), -- One transposition near the beginning
('Warehuose'), -- One transposition near the end
('Wrehous'), -- Two deletions
('Lakehouse'), -- Two substitutions at the beginning
('DataWarehouse'), -- Four insertions at the beginning
('house') -- Four deletions at the beginning
) AS w (Word)
)
SELECT Word,
EDIT_DISTANCE(@word, Word) AS ED_Dist,
EDIT_DISTANCE_SIMILARITY(@word, Word) AS ED_Similarity,
ROUND(JARO_WINKLER_DISTANCE(@word, Word), 4) AS JW_Dist,
JARO_WINKLER_SIMILARITY(@word, Word) AS JW_Similarity,
SOUNDEX(Word) AS SoundCode,
DIFFERENCE(@word, Word) AS SoundDiff
FROM Words
ORDER BY ED_Distance, JW_Distance;
Here's the result set.
Word ED_Dist ED_Similarity JW_Dist JW_Similarity SoundCode SoundDiff
-------------- ------- ------------- -------- ------------- --------- ---------
Warehouse 0 100 0 100 W620 4
Warehousse 1 90 0.02 98 W620 4
Warehouses 1 90 0.02 98 W622 3
Warehous 1 89 0.0222 98 W620 4
Warehuse 1 89 0.0222 98 W620 4
Warehuose 1 89 0.0222 98 W620 4
Wraehouse 1 89 0.0333 97 W620 4
Warehousx 1 89 0.0444 96 W620 4
Warehiuse 1 89 0.0444 96 W620 4
Xarehouse 1 89 0.0741 93 X620 3
Wrehous 2 78 0.0667 93 W620 4
Lakehouse 2 78 0.1481 85 L220 2
DataWarehouse 4 69 0.1396 86 D362 2
house 4 56 0.563 44 H200 2
The results show the following behavior:
- One edit of any kind counts as distance
1. An insertion (Warehouses), a deletion (Warehous), a substitution (Warehiuse), and a transposition of two adjacent characters (Wraehouse) each return anED_Distanceof1. Two edits (Wrehous,Lakehouse) return2. - Edit distance similarity depends on string length. The same single edit returns
90when the longer string has 10 characters (Warehouses) and89when it has 9 characters (Warehous). - Jaro-Winkler depends on where the change is.
Warehousx,Warehiuse, andXarehouseall have an edit distance of1, but the change in the first character inXarehousegives a Jaro-Winkler distance of0.0741, while a change after the first four characters inWarehousxandWarehiusegives0.0444. The Jaro-Winkler algorithm gives a bonus to strings that share the same first characters (up to four). - Edit distance and Jaro-Winkler can rank results differently.
DataWarehousehas a larger edit distance (4) thanLakehouse(2), but a smaller Jaro-Winkler distance, because it contains the whole wordWarehouse.househas the same edit distance asDataWarehouse, but it's the least similar word by every other measure, because it's shorter and doesn't share the first characters ofWarehouse. - SOUNDEX ignores vowels. Most of the words return the same code as
Warehouse,W620, and aDIFFERENCEof4, evenWrehouswith two deletions, because the edits change only vowels or add a consonant that sounds the same as its neighbor. A change to the first letter (Xarehouse) or to the consonants (Lakehouse) returns a different code and a lowerDIFFERENCE. This makesSOUNDEXa poor choice for detecting typos. - The comparison follows the collation. With a case-sensitive collation, which is the default in a warehouse,
WAREHOUSEreturns anED_Distanceof8, because every lowercase letter counts as a substitution. To ignore case, compareUPPER(@word)withUPPER(Word).
C. Compare names that sound alike
The following example compares pairs of English first and last names by using the phonetic and the fuzzy string matching functions.
SELECT Name1,
Name2,
SOUNDEX(Name1) AS Soundex1,
SOUNDEX(Name2) AS Soundex2,
DIFFERENCE(Name1, Name2) AS SoundDiff,
EDIT_DISTANCE(Name1, Name2) AS ED_Distance,
JARO_WINKLER_SIMILARITY(Name1, Name2) AS JW_Similarity
FROM (VALUES
('Smith', 'Smythe'),
('Jon', 'John'),
('Steven', 'Stephen'),
('Catherine', 'Katherine'),
('Robert', 'Rupert')
) AS n (Name1, Name2);
Here's the result set.
Name1 Name2 Soundex1 Soundex2 SoundDiff ED_Distance JW_Similarity
--------- --------- -------- -------- --------- ----------- -------------
Smith Smythe S530 S530 4 2 86
Jon John J500 J500 4 1 93
Steven Stephen S315 S315 4 2 89
Catherine Katherine C365 K365 3 1 93
Robert Rupert R163 R163 4 2 80
The results show the following behavior:
- SOUNDEX matches different spellings of the same sound.
SmithandSmythe, andStevenandStephen, return the same code, even though they're two edits apart. - SOUNDEX keeps the first letter.
CatherineandKatherinesound the same, but return different codes because they start with different letters. The fuzzy string matching functions rate them as very similar, with an edit distance of1. - SOUNDEX can match different names.
RobertandRupertare different names, but they return the same code becauseSOUNDEXignores vowels and groupsbandptogether.
D. Match records from two sources
The following example matches customer names from a CRM system with customer names from a billing system, where the same customer is often entered with small differences. The join condition uses EDIT_DISTANCE with the maximum_distance argument set to 3, so the function stops calculating as soon as a pair is more than three edits apart. Both names are converted with UPPER() so that the comparison ignores case.
WITH Crm AS (
SELECT CustomerName
FROM (VALUES
('Contoso Ltd'),
('Fabrikam Inc.'),
('Northwind Traders'),
('Adventure Works'),
('Wide World Importers'),
('Tailspin Toys')
) AS c (CustomerName)
),
Billing AS (
SELECT CustomerName
FROM (VALUES
('Contoso, Ltd.'),
('Fabrikam Inc'),
('Nortwind Traders'),
('Adventure-Works'),
('Wide World Importer'),
('Wingtip Toys'),
('Northwind Trading')
) AS b (CustomerName)
)
SELECT c.CustomerName AS CrmName,
b.CustomerName AS BillingName,
EDIT_DISTANCE(UPPER(c.CustomerName), UPPER(b.CustomerName)) AS EditDistance,
EDIT_DISTANCE_SIMILARITY(UPPER(c.CustomerName), UPPER(b.CustomerName)) AS Similarity
FROM Crm AS c
LEFT JOIN Billing AS b
ON EDIT_DISTANCE(UPPER(c.CustomerName), UPPER(b.CustomerName), 3) <= 3
ORDER BY CrmName, Similarity DESC;
Here's the result set.
CrmName BillingName EditDistance Similarity
-------------------- ------------------- ------------ ----------
Adventure Works Adventure-Works 1 93
Contoso Ltd Contoso, Ltd. 2 85
Fabrikam Inc. Fabrikam Inc 1 92
Northwind Traders Nortwind Traders 1 94
Northwind Traders Northwind Trading 3 82
Tailspin Toys NULL NULL NULL
Wide World Importers Wide World Importer 1 95
Northwind Traders has two candidates within three edits, and Tailspin Toys has no match in the billing system. To keep only the best candidate for each CRM name, rank the candidates with ROW_NUMBER():
WITH Crm AS (
SELECT CustomerName
FROM (VALUES ('Contoso Ltd'), ('Fabrikam Inc.'), ('Northwind Traders'), ('Adventure Works'), ('Wide World Importers'), ('Tailspin Toys')) AS c (CustomerName)
),
Billing AS (
SELECT CustomerName
FROM (VALUES ('Contoso, Ltd.'), ('Fabrikam Inc'), ('Nortwind Traders'), ('Adventure-Works'), ('Wide World Importer'), ('Wingtip Toys'), ('Northwind Trading')) AS b (CustomerName)
),
Candidates AS (
SELECT c.CustomerName AS CrmName,
b.CustomerName AS BillingName,
EDIT_DISTANCE_SIMILARITY(UPPER(c.CustomerName), UPPER(b.CustomerName)) AS Similarity
FROM Crm AS c
INNER JOIN Billing AS b
ON EDIT_DISTANCE(UPPER(c.CustomerName), UPPER(b.CustomerName), 3) <= 3
)
SELECT CrmName, BillingName, Similarity
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY CrmName ORDER BY Similarity DESC) AS MatchRank
FROM Candidates
) AS ranked
WHERE MatchRank = 1
ORDER BY CrmName;
Here's the result set.
CrmName BillingName Similarity
-------------------- ------------------- ----------
Adventure Works Adventure-Works 93
Contoso Ltd Contoso, Ltd. 85
Fabrikam Inc. Fabrikam Inc 92
Northwind Traders Nortwind Traders 94
Wide World Importers Wide World Importer 95
SOUNDEX and DIFFERENCE aren't a good fit for this scenario. A SOUNDEX code represents only the first letter and the next three consonant sounds, so it ignores most of a multi-word name such as a company name.
Comparing every row of one table with every row of another can be expensive for large tables. To reduce the number of compared pairs, add a filter or equality condition on a column that both sources share.