{"id":22,"date":"2008-08-03T22:29:25","date_gmt":"2008-08-03T21:29:25","guid":{"rendered":"http:\/\/simoncpage.co.uk\/blog\/?p=22"},"modified":"2009-01-22T14:26:09","modified_gmt":"2009-01-22T13:26:09","slug":"excel-fuzzy-logic-vba-for-removing-duplicates","status":"publish","type":"post","link":"https:\/\/simoncpage.co.uk\/blog\/2008\/08\/excel-fuzzy-logic-vba-for-removing-duplicates\/","title":{"rendered":"Excel fuzzy logic VBA for removing duplicates"},"content":{"rendered":"<p>A solution I use is to sweep an export of the database for duplicates using the following fuzzy logic match Excel vb macro. Filemaker is my database of choice these days but one of the major issues of database design I find difficult is the balance between freedom of entry and stopping duplicates being created and this helps greatly.<\/p>\n<p><a href=\"https:\/\/simoncpage.co.uk\/blog\/wp-content\/uploads\/2008\/10\/text_only_fuzzylookup_vb_code.txt\">Text file of the vb macros below<\/a><\/p>\n<blockquote><p>Option Explicit<br \/>\nType RankInfo<br \/>\nOffset As Integer<br \/>\nPercentage As Single<br \/>\nEnd Type<\/p>\n<p>Function FuzzyPercent(ByVal String1 As String, _<br \/>\nByVal String2 As String, _<br \/>\nOptional Algorithm As Integer = 3, _<br \/>\nOptional Normalised As Boolean = False) As Single<br \/>\n&#8216;*************************************<br \/>\n&#8216;** Return a % match on two strings **<br \/>\n&#8216;*************************************<br \/>\nDim intLen1 As Integer, intLen2 As Integer<br \/>\nDim intCurLen As Integer<br \/>\nDim intTo As Integer<br \/>\nDim intPos As Integer<br \/>\nDim intPtr As Integer<br \/>\nDim intScore As Integer<br \/>\nDim intTotScore As Integer<br \/>\nDim intStartPos As Integer<br \/>\nDim strWork As String<\/p>\n<p>&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br \/>\n&#8216;&#8211; If strings havent been normalised, normalise them &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br \/>\nIf Normalised = False Then<br \/>\nString1 = LCase$(Application.Trim(String1))<br \/>\nString2 = LCase$(Application.Trim(String2))<br \/>\nEnd If<\/p>\n<p>&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br \/>\n&#8216;&#8211; Give 100% match if strings exactly equal &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br \/>\nIf String1 = String2 Then<br \/>\nFuzzyPercent = 1<br \/>\nExit Function<br \/>\nEnd If<\/p>\n<p>intLen1 = Len(String1)<br \/>\nintLen2 = Len(String2)<\/p>\n<p>&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br \/>\n&#8216;&#8211; Give 0% match if string length &lt; 2 &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br \/>\nIf intLen1 &lt; 2 Then<br \/>\nFuzzyPercent = 0<br \/>\nExit Function<br \/>\nEnd If<\/p>\n<p>intTotScore = 0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &#8216;initialise total possible score<br \/>\nintScore = 0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &#8216;initialise current score<\/p>\n<p>&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n&#8216;&#8211; If Algorithm = 1 or 3, Search for single characters &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\nIf (Algorithm And 1) &lt;&gt; 0 Then<br \/>\nFuzzyAlg1 String1, String2, intScore, intTotScore<br \/>\nIf intLen1 &lt; intLen2 Then FuzzyAlg1 String2, String1, intScore, intTotScore<br \/>\nEnd If<\/p>\n<p>&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n&#8216;&#8211; If Algorithm = 2 or 3, Search for pairs, triplets etc. &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\nIf (Algorithm And 2) &lt;&gt; 0 Then<br \/>\nFuzzyAlg2 String1, String2, intScore, intTotScore<br \/>\nIf intLen1 &lt; intLen2 Then FuzzyAlg2 String2, String1, intScore, intTotScore<br \/>\nEnd If<\/p>\n<p>FuzzyPercent = intScore \/ intTotScore<\/p>\n<p>End Function<br \/>\nPrivate Sub FuzzyAlg1(ByVal String1 As String, _<br \/>\nByVal String2 As String, _<br \/>\nByRef Score As Integer, _<br \/>\nByRef TotScore As Integer)<br \/>\nDim intLen1 As Integer, intPos As Integer, intPtr As Integer, intStartPos As Integer<\/p>\n<p>intLen1 = Len(String1)<br \/>\nTotScore = TotScore + intLen1\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &#8216;update total possible score<br \/>\nintPos = 0<br \/>\nFor intPtr = 1 To intLen1<br \/>\nintStartPos = intPos + 1<br \/>\nintPos = InStr(intStartPos, String2, Mid$(String1, intPtr, 1))<br \/>\nIf intPos &gt; 0 Then<br \/>\nIf intPos &gt; intStartPos + 3 Then\u00a0\u00a0\u00a0\u00a0 &#8216;No match if char is &gt; 3 bytes away<br \/>\nintPos = intStartPos<br \/>\nElse<br \/>\nScore = Score + 1\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &#8216;Update current score<br \/>\nEnd If<br \/>\nElse<br \/>\nintPos = intStartPos<br \/>\nEnd If<br \/>\nNext intPtr<br \/>\nEnd Sub<br \/>\nPrivate Sub FuzzyAlg2(ByVal String1 As String, _<br \/>\nByVal String2 As String, _<br \/>\nByRef Score As Integer, _<br \/>\nByRef TotScore As Integer)<br \/>\nDim intCurLen As Integer, intLen1 As Integer, intTo As Integer, intPtr As Integer, intPos As Integer<br \/>\nDim strWork As String<\/p>\n<p>intLen1 = Len(String1)<br \/>\nFor intCurLen = 2 To intLen1<br \/>\nstrWork = String2\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &#8216;Get a copy of String2<br \/>\nintTo = intLen1 &#8211; intCurLen + 1<br \/>\nTotScore = TotScore + Int(intLen1 \/ intCurLen)\u00a0 &#8216;Update total possible score<br \/>\nFor intPtr = 1 To intTo Step intCurLen<br \/>\nintPos = InStr(strWork, Mid$(String1, intPtr, intCurLen))<br \/>\nIf intPos &gt; 0 Then<br \/>\nMid$(strWork, intPos, intCurLen) = String$(intCurLen, &amp;H0) &#8216;corrupt found string<br \/>\nScore = Score + 1\u00a0\u00a0\u00a0\u00a0 &#8216;Update current score<br \/>\nEnd If<br \/>\nNext intPtr<br \/>\nNext intCurLen<\/p>\n<p>End Sub<\/p>\n<p>Function FuzzyVLookup(ByVal LookupValue As String, _<br \/>\nByVal TableArray As Range, _<br \/>\nByVal IndexNum As Integer, _<br \/>\nOptional NFPercent As Single = 0.05, _<br \/>\nOptional Rank As Integer = 1, _<br \/>\nOptional Algorithm As Integer = 3, _<br \/>\nOptional AdditionalCols As Integer = 0) As Variant<br \/>\n&#8216;********************************************************************************<br \/>\n&#8216;** Function to Fuzzy match LookupValue with entries in\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** column 1 of table specified by TableArray.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** TableArray must specify the top left cell of the range to be searched\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** The function stops scanning the table when an empty cell in column 1\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** is found.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** For each entry in column 1 of the table, FuzzyPercent is called to match\u00a0\u00a0 **<br \/>\n&#8216;** LookupValue with the Table entry.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** &#8216;Rank&#8217; is an optional parameter which may take any value &gt; 0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (default 1) and causes the function to return the &#8216;nth&#8217; best\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 match (where &#8216;n&#8217; is defined by &#8216;Rank&#8217; parameter)\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** If the &#8216;Rank&#8217; match percentage &lt; NFPercent (Default 5%), #N\/A is returned. **<br \/>\n&#8216;** IndexNum is the column number of the entry in TableArray required to be\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** returned, as follows:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** If IndexNum &gt; 0 and the &#8216;Rank&#8217; percentage match is &gt;= NFPercent\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (Default 5%) the column entry indicated by IndexNum is\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 returned.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** if IndexNum = 0 and the &#8216;Rank&#8217; percentage match is &gt;= NFPercent\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (Default 5%) the offset row (starting at 1) is returned.\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 This value can be used directly in the &#8216;Index&#8217; function.\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** Algorithm can take one of the following values:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** Algorithm = 1:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 This algorithm is best suited for matching mis-spellings.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 For each character in &#8216;String1&#8217;, a search is performed on &#8216;String2&#8217;.\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 The search is deemed successful if a character is found in &#8216;String2&#8217;\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 within 3 characters of the current position.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 A score is kept of matching characters which is returned as a\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 percentage of the total possible score.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** Algorithm = 2:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 This algorithm is best suited for matching sentences, or\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 &#8216;firstname lastname&#8217; compared with &#8216;lastname firstname&#8217; combinations\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 A count of matching pairs, triplets, quadruplets etc. in &#8216;String1&#8217; and **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 &#8216;String2&#8217; is returned as a percentage of the total possible.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** Algorithm = 3: Both Algorithms 1 and 2 are performed.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;********************************************************************************<br \/>\nDim R As Range<\/p>\n<p>Dim strListString As String<br \/>\nDim strWork As String<\/p>\n<p>Dim sngMinPercent As Single<br \/>\nDim sngWork As Single<br \/>\nDim sngCurPercent\u00a0 As Single<br \/>\nDim intBestMatchPtr As Integer<br \/>\nDim intRankPtr As Integer<br \/>\nDim intRankPtr1 As Integer<br \/>\nDim I As Integer<\/p>\n<p>Dim lEndRow As Long<\/p>\n<p>Dim udRankData() As RankInfo<\/p>\n<p>Dim vCurValue As Variant<\/p>\n<p>&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n&#8216;&#8211;\u00a0\u00a0\u00a0 Validation\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<\/p>\n<p>LookupValue = LCase$(Application.Trim(LookupValue))<\/p>\n<p>If IsMissing(NFPercent) Then<br \/>\nsngMinPercent = 0.05<br \/>\nElse<br \/>\nIf (NFPercent &lt;= 0) Or (NFPercent &gt; 1) Then<br \/>\nFuzzyVLookup = &#8220;*** &#8216;NFPercent&#8217; must be a percentage &gt; zero ***&#8221;<br \/>\nExit Function<br \/>\nEnd If<br \/>\nsngMinPercent = NFPercent<br \/>\nEnd If<\/p>\n<p>If Rank &lt; 1 Then<br \/>\nFuzzyVLookup = &#8220;*** &#8216;Rank&#8217; must be an integer &gt; 0 ***&#8221;<br \/>\nExit Function<br \/>\nEnd If<\/p>\n<p>ReDim udRankData(1 To Rank)<\/p>\n<p>lEndRow = TableArray.Rows.Count<br \/>\nIf VarType(TableArray.Cells(lEndRow, 1).Value) = vbEmpty Then<br \/>\nlEndRow = TableArray.Cells(lEndRow, 1).End(xlUp).Row<br \/>\nEnd If<\/p>\n<p>&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\n&#8216;&#8211; Main loop &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\nFor Each R In Range(TableArray.Cells(1, 1), TableArray.Cells(lEndRow, 1))<br \/>\nvCurValue = &#8220;&#8221;<br \/>\nFor I = 0 To AdditionalCols<br \/>\nvCurValue = vCurValue &amp; R.Offset(0, I).Text<br \/>\nNext I<br \/>\nIf VarType(vCurValue) = vbString Then<br \/>\nstrListString = LCase$(Application.Trim(vCurValue))<\/p>\n<p>&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\n&#8216;&#8211; Fuzzy match strings &amp; get percentage match &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\nsngCurPercent = FuzzyPercent(String1:=LookupValue, _<br \/>\nString2:=strListString, _<br \/>\nAlgorithm:=Algorithm, _<br \/>\nNormalised:=True)<\/p>\n<p>If sngCurPercent &gt;= sngMinPercent Then<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\n&#8216;&#8211; Store in ranked array &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\nFor intRankPtr = 1 To Rank<br \/>\nIf sngCurPercent &gt; udRankData(intRankPtr).Percentage Then<br \/>\nFor intRankPtr1 = Rank To intRankPtr + 1 Step -1<br \/>\nWith udRankData(intRankPtr1)<br \/>\n.Offset = udRankData(intRankPtr1 &#8211; 1).Offset<br \/>\n.Percentage = udRankData(intRankPtr1 &#8211; 1).Percentage<br \/>\nEnd With<br \/>\nNext intRankPtr1<br \/>\nWith udRankData(intRankPtr)<br \/>\n.Offset = R.Row<br \/>\n.Percentage = sngCurPercent<br \/>\nEnd With<br \/>\nExit For<br \/>\nEnd If<br \/>\nNext intRankPtr<br \/>\nEnd If<\/p>\n<p>End If<br \/>\nNext R<\/p>\n<p>If udRankData(Rank).Percentage &lt; sngMinPercent Then<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n&#8216;&#8211; Return &#8216;#N\/A&#8217; if below NFPercent &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\nFuzzyVLookup = CVErr(xlErrNA)<br \/>\nElse<br \/>\nintBestMatchPtr = udRankData(Rank).Offset &#8211; TableArray.Cells(1, 1).Row + 1<br \/>\nIf IndexNum &gt; 0 Then<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n&#8216;&#8211; Return column entry specified &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\nFuzzyVLookup = TableArray.Cells(intBestMatchPtr, IndexNum)<br \/>\nElse<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n&#8216;&#8211; Return offset row &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\nFuzzyVLookup = intBestMatchPtr<br \/>\nEnd If<br \/>\nEnd If<br \/>\nEnd Function<br \/>\nFunction FuzzyHLookup(ByVal LookupValue As String, _<br \/>\nByVal TableArray As Range, _<br \/>\nByVal IndexNum As Integer, _<br \/>\nOptional NFPercent As Single = 0.05, _<br \/>\nOptional Rank As Integer = 1, _<br \/>\nOptional Algorithm As Integer = 3) As Variant<br \/>\n&#8216;********************************************************************************<br \/>\n&#8216;** Function to Fuzzy match LookupValue with entries in\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** row 1 of table specified by TableArray.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** TableArray must specify the top left cell of the range to be searched\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** The function stops scanning the table when an empty cell in row 1\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** is found.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** For each entry in row 1 of the table, FuzzyPercent is called to match\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** LookupValue with the Table entry.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** &#8216;Rank&#8217; is an optional parameter which may take any value &gt; 0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (default 1) and causes the function to return the &#8216;nth&#8217; best\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 match (where &#8216;n&#8217; is defined by &#8216;Rank&#8217; parameter)\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** If the &#8216;Rank&#8217; match percentage &lt; NFPercent (Default 5%), #N\/A is returned. **<br \/>\n&#8216;** IndexNum is the row number of the entry in TableArray required to be\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** returned, as follows:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** If IndexNum &gt; 0 and the &#8216;Rank&#8217; percentage match is &gt;= NFPercent\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (Default 5%) the row entry indicated by IndexNum is\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 returned.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** if IndexNum = 0 and the &#8216;Rank&#8217; percentage match is &gt;= NFPercent\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (Default 5%) the offset col (starting at 0) is returned.\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 This value can be used directly in the &#8216;OffSet&#8217; function.\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** Algorithm can take one of the following values:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** Algorithm = 1:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 For each character in &#8216;String1&#8217;, a search is performed on &#8216;String2&#8217;.\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 The search is deemed successful if a character is found in &#8216;String2&#8217;\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 within 3 characters of the current position.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 A score is kept of matching characters which is returned as a\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 percentage of the total possible score.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** Algorithm = 2:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 A count of matching pairs, triplets, quadruplets etc. in &#8216;String1&#8217; and **<br \/>\n&#8216;**\u00a0\u00a0\u00a0\u00a0 &#8216;String2&#8217; is returned as a percentage of the total possible.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;** Algorithm = 3: Both Algorithms 1 and 2 are performed.\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 **<br \/>\n&#8216;********************************************************************************<br \/>\nDim R As Range<\/p>\n<p>Dim strListString As String<br \/>\nDim strWork As String<\/p>\n<p>Dim sngMinPercent As Single<br \/>\nDim sngWork As Single<br \/>\nDim sngCurPercent\u00a0 As Single<\/p>\n<p>Dim intBestMatchPtr As Integer<br \/>\nDim intPtr As Integer<br \/>\nDim intRankPtr As Integer<br \/>\nDim intRankPtr1 As Integer<\/p>\n<p>Dim iEndCol As Integer<\/p>\n<p>Dim udRankData() As RankInfo<\/p>\n<p>Dim vCurValue As Variant<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n&#8216;&#8211;\u00a0\u00a0\u00a0 Validation\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\nLookupValue = LCase$(Application.Trim(LookupValue))<\/p>\n<p>If IsMissing(NFPercent) Then<br \/>\nsngMinPercent = 0.05<br \/>\nElse<br \/>\nIf (NFPercent &lt;= 0) Or (NFPercent &gt; 1) Then<br \/>\nFuzzyHLookup = &#8220;*** &#8216;NFPercent&#8217; must be a percentage &gt; zero ***&#8221;<br \/>\nExit Function<br \/>\nEnd If<br \/>\nsngMinPercent = NFPercent<br \/>\nEnd If<\/p>\n<p>If Rank &lt; 1 Then<br \/>\nFuzzyHLookup = &#8220;*** &#8216;Rank&#8217; must be an integer &gt; 0 ***&#8221;<br \/>\nExit Function<br \/>\nEnd If<\/p>\n<p>ReDim udRankData(1 To Rank)<br \/>\n&#8216;**************************<br \/>\niEndCol = TableArray.Columns.Count<br \/>\nIf VarType(TableArray.Cells(1, iEndCol).Value) = vbEmpty Then<br \/>\niEndCol = TableArray.Cells(1, iEndCol).End(xlToLeft).Column<br \/>\nEnd If<\/p>\n<p>&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\n&#8216;&#8211; Main loop &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\nFor Each R In Range(TableArray.Cells(1, 1), TableArray.Cells(1, iEndCol))<br \/>\nvCurValue = R.Value<br \/>\nIf VarType(vCurValue) = vbString Then<br \/>\nstrListString = LCase$(Application.Trim(vCurValue))<\/p>\n<p>&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\n&#8216;&#8211; Fuzzy match strings &amp; get percentage match &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\nsngCurPercent = FuzzyPercent(String1:=LookupValue, _<br \/>\nString2:=strListString, _<br \/>\nAlgorithm:=Algorithm, _<br \/>\nNormalised:=True)<\/p>\n<p>If sngCurPercent &gt;= sngMinPercent Then<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\n&#8216;&#8211; Store in ranked array &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br \/>\nFor intRankPtr = 1 To Rank<br \/>\nIf sngCurPercent &gt; udRankData(intRankPtr).Percentage Then<br \/>\nFor intRankPtr1 = Rank To intRankPtr + 1 Step -1<br \/>\nWith udRankData(intRankPtr1)<br \/>\n.Offset = udRankData(intRankPtr1 &#8211; 1).Offset<br \/>\n.Percentage = udRankData(intRankPtr1 &#8211; 1).Percentage<br \/>\nEnd With<br \/>\nNext intRankPtr1<br \/>\nWith udRankData(intRankPtr)<br \/>\n.Offset = R.Column<br \/>\n.Percentage = sngCurPercent<br \/>\nEnd With<br \/>\nExit For<br \/>\nEnd If<br \/>\nNext intRankPtr<br \/>\nEnd If<\/p>\n<p>End If<br \/>\nNext R<\/p>\n<p>If udRankData(Rank).Percentage &lt; sngMinPercent Then<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n&#8216;&#8211; Return &#8216;#N\/A&#8217; if below NFPercent &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\nFuzzyHLookup = CVErr(xlErrNA)<br \/>\nElse<br \/>\nintBestMatchPtr = udRankData(Rank).Offset &#8211; TableArray.Cells(1, 1).Column + 1<br \/>\nIf IndexNum &gt; 0 Then<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n&#8216;&#8211; Return row entry specified &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\nFuzzyHLookup = TableArray.Cells(IndexNum, intBestMatchPtr)<br \/>\nElse<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\n&#8216;&#8211; Return offset col &#8212;<br \/>\n&#8216;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<br \/>\nFuzzyHLookup = intBestMatchPtr<br \/>\nEnd If<br \/>\nEnd If<br \/>\nEnd Function<\/p><\/blockquote>\n<p>Above contains the three functions: FUZZYVLOOKUP, FUZZYHLOOKUP and FUZZYPERCENT.<\/p>\n<ul>\n<li>FUZZYVLOOKUP ( LookupValue, TableArray, IndexNum, NFpercent, Rank, Algorithm, AdditionalCols)<\/li>\n<li>FUZZYHLOOKUP ( LookupValue, TableArray, IndexNum, NFpercent, Rank, Algorithm, AdditionalCols)<\/li>\n<li>FUZZYPERCENT (String1, String2, Algorithm, Normalised)<\/li>\n<\/ul>\n<p><strong>Lookupvalue<br \/>\n<\/strong>The value to search in the first column of the table array<\/p>\n<p><strong>Tablearray<\/strong><br \/>\nOne or more columns of data. Use a reference to a range or a range name. The values in the first column of table array are the values searched by lookup value.<\/p>\n<p><strong>Indexnum<br \/>\n<\/strong>The column number in the table array from which the matching value must be returned. An index num of 1 returns the value in the first column in the table array; an index num of 2 returns the value in the second column in the table array, and so on. If index num is zero, the relative row number in the table array is returned.<\/p>\n<p><strong>NFpercent<\/strong><br \/>\nThe Percentage value below which matching strings are deemed as not found. If no strings in the lookup table equal or exceed this matching percentage, #N\/A is returned.<br \/>\nThe higher the percentage specified, the higher the confidence level in the returned result.<br \/>\nDefault: 5%<\/p>\n<p><strong>Rank<\/strong><br \/>\nAn optional parameter which may take any value &gt; 0 and causes the function to return the specified ranking best match.<br \/>\nDefault: 1<\/p>\n<p><strong>Algorithm<br \/>\n<\/strong>Defines the algorithm to be used for matching strings. Valid values are 1, 2 or 3:<br \/>\nAlgorithm = 1<br \/>\nThis algorithm is best suited for matching mis-spellings.<br \/>\nFor each character in &#8216;String1&#8217;, a search is performed on &#8216;String2&#8217;.<br \/>\nThe search is deemed successful if a character is found in &#8216;String2&#8217; within 3 characters of the current position.<br \/>\nA score is kept of matching characters which is returned as a percentage of the total possible score.<br \/>\nAlgorithm = 2<br \/>\nThis algorithm is best suited for matching sentences, or &#8216;firstname lastname&#8217; compared with &#8216;lastname firstname&#8217; combinations.<br \/>\nA count of matching pairs, triplets, quadruplets etc. in &#8216;String1&#8217; and &#8216;String2&#8217; is returned as a percentage of the total possible.<br \/>\nAlgorithm = 3: Both Algorithms 1 and 2 are performed.<br \/>\nDefault: 3<\/p>\n<p><strong>Additionalcols<\/strong><br \/>\nDefines the number of subsequent columns after the first within the table array to be concatenated with the first column prior to matching.<br \/>\nDefault: 0<\/p>\n<p><strong>String1<br \/>\n<\/strong>The first string to be matched. FuzzyVLookup will pass the lookup value supplied (but normalised) as this parameter.<\/p>\n<p><strong>String2<br \/>\n<\/strong>The second string to be matched. FuzzyVLookup will pass each of the lookup table strings supplied (but normalised) as this parameter.<\/p>\n<p><strong>Algorithm<\/strong><br \/>\nAlgorithm to be used. See FuzzyVLookup for further details.<br \/>\nDefault: 3<\/p>\n<p><strong>Normalised<\/strong><br \/>\nBoolean value indicating whether the two supplied strings have been normalised. Normalised strings have had leading, trailing and multiple internal spaces removed, and have been converted to lowercase.<br \/>\nDefault: False<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A solution I use is to sweep an export of the database for duplicates using the following fuzzy logic match Excel vb macro. Filemaker is my database of choice these days but one of the major issues of database design I find difficult is the balance between freedom of entry and stopping duplicates being created [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[26,4],"tags":[149,79,78,117,150,120,27,119,121,30],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/simoncpage.co.uk\/blog\/wp-json\/wp\/v2\/posts\/22"}],"collection":[{"href":"https:\/\/simoncpage.co.uk\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/simoncpage.co.uk\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/simoncpage.co.uk\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/simoncpage.co.uk\/blog\/wp-json\/wp\/v2\/comments?post=22"}],"version-history":[{"count":0,"href":"https:\/\/simoncpage.co.uk\/blog\/wp-json\/wp\/v2\/posts\/22\/revisions"}],"wp:attachment":[{"href":"https:\/\/simoncpage.co.uk\/blog\/wp-json\/wp\/v2\/media?parent=22"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/simoncpage.co.uk\/blog\/wp-json\/wp\/v2\/categories?post=22"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/simoncpage.co.uk\/blog\/wp-json\/wp\/v2\/tags?post=22"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}