UFN_COMPARECONSTITUENTS_4

Return

Return Type
table

Parameters

Parameter Parameter Type Mode Description
@TITLECODE1 uniqueidentifier IN
@FIRSTNAME1 nvarchar(50) IN
@MIDDLENAME1 nvarchar(50) IN
@KEYNAME1 nvarchar(100) IN
@SUFFIXCODE1 uniqueidentifier IN
@STREETNUMBER1 nvarchar(20) IN
@STREETNAME1 nvarchar(150) IN
@POSTCODE1 nvarchar(12) IN
@TITLECODE2 uniqueidentifier IN
@FIRSTNAME2 nvarchar(50) IN
@MIDDLENAME2 nvarchar(50) IN
@KEYNAME2 nvarchar(100) IN
@SUFFIXCODE2 uniqueidentifier IN
@STREETNUMBER2 nvarchar(20) IN
@STREETNAME2 nvarchar(150) IN
@POSTCODE2 nvarchar(12) IN
@COUNTRYID uniqueidentifier IN
@ADDRESSONLY bit IN
@ISORGANIZATION bit IN
@LOGICALRESULTMAPPING UDT_GENERIC_NVARCHAR100_TINYINT IN
@BAILTHRESHOLD tinyint IN

Definition

Copy


CREATE function dbo.UFN_COMPARECONSTITUENTS_4 (
  @TITLECODE1 uniqueidentifier,
  @FIRSTNAME1 nvarchar(50),
  @MIDDLENAME1 nvarchar(50),
  @KEYNAME1 nvarchar(100),
  @SUFFIXCODE1 uniqueidentifier,
  @STREETNUMBER1 nvarchar(20),
  @STREETNAME1 nvarchar(150),
  @POSTCODE1 nvarchar(12),
  @TITLECODE2 uniqueidentifier,
  @FIRSTNAME2 nvarchar(50),
  @MIDDLENAME2 nvarchar(50),
  @KEYNAME2 nvarchar(100),
  @SUFFIXCODE2 uniqueidentifier,
  @STREETNUMBER2 nvarchar(20),
  @STREETNAME2 nvarchar(150),
  @POSTCODE2 nvarchar(12),
  @COUNTRYID uniqueidentifier,
  @ADDRESSONLY bit,
  @ISORGANIZATION bit,
  @LOGICALRESULTMAPPING dbo.UDT_GENERIC_NVARCHAR100_TINYINT readonly,
  @BAILTHRESHOLD tinyint
)
returns @RESULTS table (
  TITLERESULT tinyint,
  FIRSTNAMERESULT tinyint,
  MIDDLENAMERESULT tinyint,
  KEYNAMERESULT tinyint,
  SUFFIXRESULT tinyint,
  STREETNUMBERRESULT tinyint,
  STREETNAMERESULT tinyint,
  POSTCODERESULT tinyint,
  -- scores are included for informational purposes, the results will be what is used to determine matches

  FIRSTNAMESCORE tinyint,
  MIDDLENAMESCORE tinyint,
  KEYNAMESCORE tinyint,
  STREETNUMBERSCORE tinyint,
  STREETNAMESCORE tinyint,
  POSTCODESCORE tinyint,
  -- this constituent is not a match, but the two constituents may be a household

  HOUSEHOLDMATCH bit,
  COMPOSITESCORE int,
  BAILEDCOMPARISON bit
)
begin
  -- ******

  -- This function assumes all strings are "clean" per UFN_CLEANCONSTITUENTSTRINGS.

  -- ******


  -- RESULT CODES

  -- ---------------

  -- 0 = Match

  -- 1 = Likely match

  -- 2 = Possible match

  -- 3 = Not a match

  -- 4 = Both blank

  -- 5 = Constituent 1 blank, constituent 2 non-blank

  -- 6 = Constituent 1 non-blank, constituent 1 blank


  declare 
    @TITLERESULT tinyint,
    @FIRSTNAMERESULT tinyint,
    @MIDDLENAMERESULT tinyint,
    @KEYNAMERESULT tinyint,
    @SUFFIXRESULT tinyint,
    @STREETNUMBERRESULT tinyint,
    @STREETNAMERESULT tinyint,
    @POSTCODERESULT tinyint,
    @FIRSTNAMESCORE tinyint,
    @MIDDLENAMESCORE tinyint,
    @KEYNAMESCORE tinyint,
    @STREETNUMBERSCORE tinyint,
    @STREETNAMESCORE tinyint,
    @POSTCODESCORE tinyint,
    @HOUSEHOLDMATCH bit = 0

  -- Address Only

  if @ADDRESSONLY = 1
  begin
    set @TITLERESULT = 0
    set @FIRSTNAMERESULT = 0
    set @MIDDLENAMERESULT = 0
    set @KEYNAMERESULT = 0
    set @SUFFIXRESULT = 0
  end

-----CONSTITUENT SCORING-------------------------------------------------------------------------------------------------------------------------

-- We are maintaining this score as we determine the result of each field--as we go

-- This will allow us to bail from scoring the remaining fields when we determine that the max score for the constituent is lower than the provided threshold

-- This score will also be passed back to the caller in @RESULTS

------------------------------------------------------------------------------------------------------------------------------------------------------------


  declare @SCORE smallint = 100
  insert into @RESULTS values (3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1) --Worst result possible in case we decide to bail


-----MORE ON BAILING--------------------------------------------------------------------------------------------------------------------------------

-- We want to test as few cases as possible for performance reasons

-- The quicker we can determine that there's no possible way these two constituents can match, the better

-- We'll start by testing simplier cases and get to more expensive comparisons as we go


-- Testing order:

-- 1. In-memory comparisons

-- 2. Comparisons requiring table resources

-- 3. Fuzzy-match comparisons


-- Because nearly every field (e.g. first name, key name, etc...) has scoring logic based in at least 2 of these types,

-- it will be necessary to check more than one 'block' for logic pertaining to that field

-- An attempt has been made to state the number of times a field has appeared in the code an how many more times it will

-- The accuracy of that number depends on how well it is maintained, 

-- but seeing it should at least serve as a reminder that the field appears throughout the function--not in isolation

------------------------------------------------------------------------------------------------------------------------------------------------------------


  -- Not everyone wants to bail. Caller can use a threshold of 0 to maintain pervious versions' behavior.

  declare @CANPERFORMBAIL bit = 0
  if @BAILTHRESHOLD > 0
    set @CANPERFORMBAIL = 1

  -- Depending on how early we bail, we might go ahead and factor in the best-case scenario when we know the provided field data doesn't match

  -- These will be set in their respective field logic 'blocks' as necessary

  declare @MINIMUMNONMATCHKEYNAMEDEDUCTION smallint
  declare @MINIMUMNONMATCHSTREETNUMBERDEDUCTION smallint
  declare @MINIMUMNONMATCHPOSTCODEDEDUCTION smallint

  -- The result for three digits matching on the post code may factor into the the best-case non-match result for post code

  -- Setting in @MINIMUMNONMATCHPOSTCODEDEDUCTION logic. Value maybe needed twice (if we don't bail), so storing globally

  declare @THREEDIGITZIPMATCHRESULT tinyint

  -----------------------------------------------------------------------------

  -- 1. In-memory comparison scoring

  ------------------------------------------------------------------------------

  -- KEYNAME scoring tests 1 of 2

  if @KEYNAMERESULT is null
  begin
    if @KEYNAME1 = '' or @KEYNAME2 = ''
    begin
      if @KEYNAME1 = ''
        if @KEYNAME2 = ''
          set @KEYNAMERESULT = 4
        else
          set @KEYNAMERESULT = 5
      else
        set @KEYNAMERESULT = 6
    end

    else if @KEYNAME1 = @KEYNAME2
      -- exact match

      set @KEYNAMERESULT = 0

    else if @KEYNAME1 like @KEYNAME2+'-%' or @KEYNAME1 like '%-'+@KEYNAME2 or
            @KEYNAME2 like @KEYNAME1+'-%' or @KEYNAME2 like '%-'+@KEYNAME1
      -- maiden name vs. married name

      select @KEYNAMERESULT = [NUMBER]
      from @LOGICALRESULTMAPPING
      where STRING = 'Last name / Hyphenated last name'
  end

  if @KEYNAMERESULT is not null
    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_KEYNAMESCOREDEDUCTION_BYRESULT(@KEYNAMERESULT, @ISORGANIZATION);
  else -- If we don't have a RESULT now, the best possible KEYNAMERESULT is 1, so let's factor that into the score

  begin
    select @MINIMUMNONMATCHKEYNAMEDEDUCTION = DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_KEYNAMESCOREDEDUCTION_BYRESULT(1, @ISORGANIZATION);
    set @SCORE -= @MINIMUMNONMATCHKEYNAMEDEDUCTION;
  end

  -- Key name bail test

  if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
      return;
  ------------------------------------------------------------------------------

  -- STREETNAME scoring tests 1 of 2

  if @STREETNAME1 = '' or @STREETNAME2 = ''
  begin
    if @STREETNAME1 = ''
      if @STREETNAME2 = ''
        set @STREETNAMERESULT = 4
      else
        set @STREETNAMERESULT = 5
    else
      set @STREETNAMERESULT = 6
  end

  else if @STREETNAME1 = @STREETNAME2
    -- exact match

    set @STREETNAMERESULT = 0

  if @STREETNAMERESULT is not null
  begin
    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_STREETNAMESCOREDEDUCTION_BYRESULT(@STREETNAMERESULT);

  -- Street name bail test

    if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
        return;
  end
  ------------------------------------------------------------------------------

  -- STREETNUMBER scoring tests 1 of 2

  if @STREETNUMBER1 = '' or @STREETNUMBER2 = ''
  begin
    if @STREETNUMBER1 = ''
      if @STREETNUMBER2 = ''
        set @STREETNUMBERRESULT = 4
      else
        set @STREETNUMBERRESULT = 5
    else
      set @STREETNUMBERRESULT = 6
  end

  else if @STREETNUMBER1 = @STREETNUMBER2
    -- exact match

    set @STREETNUMBERRESULT = 0

  else if @STREETNUMBER1 like @STREETNUMBER2+'-%' or @STREETNUMBER2 like @STREETNUMBER1+'-%'
    -- street number vs. street number range or street/apartment combination

    select @STREETNUMBERRESULT = [NUMBER]
    from @LOGICALRESULTMAPPING
    where STRING = 'Street number / Hyphenated street number'

  if @STREETNUMBERRESULT is not null
    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_STREETNUMBERSCOREDEDUCTION_BYRESULT(@STREETNUMBERRESULT);
  else -- If we don't have a RESULT now, the best possible STREETNUMBERRESULT is 1, so let's factor that into the score

  begin
    select @MINIMUMNONMATCHSTREETNUMBERDEDUCTION = DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_STREETNUMBERSCOREDEDUCTION_BYRESULT(1);
    set @SCORE -= @MINIMUMNONMATCHSTREETNUMBERDEDUCTION;
  end

  -- Street number bail test

  if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
      return;
  ------------------------------------------------------------------------------

  -- POSTCODE scoring tests 1 of 2

  if @POSTCODE1 = '' or @POSTCODE2 = ''
  begin
    if @POSTCODE1 = ''
      if @POSTCODE2 = ''
        set @POSTCODERESULT = 4
      else
        set @POSTCODERESULT = 5
    else
      set @POSTCODERESULT = 6
  end

  else if @POSTCODE1 = @POSTCODE2
    -- exact match

    set @POSTCODERESULT = 0

  if @POSTCODERESULT is not null
    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_POSTCODESCOREDEDUCTION_BYRESULT(@POSTCODERESULT);
  else -- If we don't have a POSTCODERESULT now, the let's factor in the best-case deduction

  begin
    declare @BESTCASEPOSTCODERESULT tinyint
    select @THREEDIGITZIPMATCHRESULT = [NUMBER]
    from @LOGICALRESULTMAPPING
    where STRING = 'Three digits of ZIP code match, but not first three'

    if isnull(@THREEDIGITZIPMATCHRESULT, 2) > 1 --Unless above is less than 1, 1 is best case

      set @BESTCASEPOSTCODERESULT = 1
    else
      set @BESTCASEPOSTCODERESULT = @THREEDIGITZIPMATCHRESULT

    select @MINIMUMNONMATCHPOSTCODEDEDUCTION = DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_POSTCODESCOREDEDUCTION_BYRESULT(@BESTCASEPOSTCODERESULT);

    set @SCORE -= @MINIMUMNONMATCHPOSTCODEDEDUCTION;
  end

  -- Post code bail test

  if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
      return;
  ------------------------------------------------------------------------------

  -- TITLECODE scoring tests 1 of 2

  if @TITLERESULT is null
  begin
    if @TITLECODE1 is null or @TITLECODE2 is null
    begin
      if @TITLECODE1 is null
        if @TITLECODE2 is null
          set @TITLERESULT = 4
        else
          set @TITLERESULT = 5
      else
        set @TITLERESULT = 6
    end

    else if @TITLECODE1 = @TITLECODE2
      -- exact match

      set @TITLERESULT = 0
  end

  -- Title bail test

  if @TITLERESULT is not null
  begin
    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_TITLESCOREDEDUCTION_BYRESULT(@TITLERESULT);

    if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
        return;
  end
  ------------------------------------------------------------------------------

  -- FIRSTNAME scoring tests 1 of 3

  if @FIRSTNAMERESULT is null
  begin
    if @FIRSTNAME1 = '' or @FIRSTNAME2 = ''
    begin
      if @FIRSTNAME1 = ''
        if @FIRSTNAME2 = ''
          set @FIRSTNAMERESULT = 4
        else
          set @FIRSTNAMERESULT = 5
      else
        set @FIRSTNAMERESULT = 6
    end

    else if @FIRSTNAME1 = @FIRSTNAME2
      -- exact match

      set @FIRSTNAMERESULT = 0

    else if @FIRSTNAME1 = left(@FIRSTNAME2,1) or @FIRSTNAME2 = left(@FIRSTNAME1,1)
      -- first initial vs. first name

      select @FIRSTNAMERESULT = [NUMBER]
      from @LOGICALRESULTMAPPING
      where STRING = 'First name / First initial'

    else if @FIRSTNAME1 like '% AND %' or
            @FIRSTNAME1 like '% & %'
    begin
      if charindex('|'+@FIRSTNAME2+'|',
                    '|'+replace(replace(replace(replace(@FIRSTNAME1,' AND ','|'),' & ','|'),', ','|'),',','|')+'|') > 0
      begin
        -- one name vs. two names (household) = not a constituent match, a household match

        set @FIRSTNAMERESULT = 3
        set @HOUSEHOLDMATCH = 1 --TODO

      end
    end

    else if @FIRSTNAME2 like '% AND %' or
            @FIRSTNAME2 like '% & %'
    begin
      if charindex('|'+@FIRSTNAME1+'|',
                    '|'+replace(replace(replace(replace(@FIRSTNAME2,' AND ','|'),' & ','|'),', ','|'),',','|')+'|') > 0
      begin
        -- one name vs. two names (household) = not a constituent match, a household match

        set @FIRSTNAMERESULT = 3
        set @HOUSEHOLDMATCH = 1 --TODO

      end
    end

    else if @MIDDLENAME1 <> '' or @MIDDLENAME2 <> ''
    begin
      if @FIRSTNAME1 = @FIRSTNAME2 + ' ' + @MIDDLENAME2 or
          @FIRSTNAME2 = @FIRSTNAME1 + ' ' + @MIDDLENAME1
      begin
        -- first/middle name combined vs. separate

        set @FIRSTNAMERESULT = 0

        if @MIDDLENAME1 <> '' and @MIDDLENAME2 <> ''
          -- fn1=John, mn1=Roy, fn2=John Roy, mn2=Bob

          set @MIDDLENAMERESULT = 4
        else
          -- fn1=John, mn1=Roy, fn2=John Roy

          set @MIDDLENAMERESULT = 0
      end

      else if @FIRSTNAME1 = @FIRSTNAME2 + ' ' + left(@MIDDLENAME2,1) or
              @FIRSTNAME2 = @FIRSTNAME1 + ' ' + left(@MIDDLENAME1,1) or
              @FIRSTNAME1 = left(@MIDDLENAME2,1) + ' ' + @FIRSTNAME2 or
              @FIRSTNAME2 = left(@MIDDLENAME1,1) + ' ' + @FIRSTNAME1
      begin
        -- middle initial included in first name = match

        select @FIRSTNAMERESULT = [NUMBER]
        from @LOGICALRESULTMAPPING
        where STRING = 'First name / First name & middle initial'

        if @MIDDLENAME1 <> '' and @MIDDLENAME2 <> ''
          -- fn1=John, mn1=Roy, fn2=John R, mn2=Bob

          set @MIDDLENAMERESULT = 4
        else
          -- fn1=John, mn1=Roy, fn2=John R

          select @MIDDLENAMERESULT = [NUMBER]
          from @LOGICALRESULTMAPPING
          where STRING = 'First name / First initial'
      end
    end

    else if @FIRSTNAME1 like @FIRSTNAME2 + ' _' or
            @FIRSTNAME2 like @FIRSTNAME1 + ' _' or
            @FIRSTNAME1 like '_ ' + @FIRSTNAME2 or
            @FIRSTNAME2 like '_ ' + @FIRSTNAME1
      -- middle initial included in first name = match

      select @FIRSTNAMERESULT = [NUMBER]
      from @LOGICALRESULTMAPPING
      where STRING = 'First name / First name & middle initial'
  end

  --Not putting in logic to bail now since it's possible for the middle name score to credit points lost by first name

  --Otherwise, we might bail in error

  ------------------------------------------------------------------------------

  -- MIDDLENAME scoring tests 1 of 2

  -- use first name logical results and score mappings

  if @MIDDLENAMERESULT is null
  begin
    if @MIDDLENAME1 = '' or @MIDDLENAME2 = ''
    begin
      if @MIDDLENAME1 = ''
        if @MIDDLENAME2 = ''
          set @MIDDLENAMERESULT = 4
        else
          set @MIDDLENAMERESULT = 5
      else
        set @MIDDLENAMERESULT = 6
    end

    else if @MIDDLENAME1 = @MIDDLENAME2
      -- exact match

      set @MIDDLENAMERESULT = 0

    else if @MIDDLENAME1 = left(@MIDDLENAME2,1) or @MIDDLENAME2 = left(@MIDDLENAME1,1)
      -- middle initial vs. middle name

      select @MIDDLENAMERESULT = [NUMBER]
      from @LOGICALRESULTMAPPING
      where STRING = 'First name / First initial'
  end

    -- Middle name bail test

  if @MIDDLENAMERESULT is not null and @FIRSTNAMERESULT is not null
  begin
    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_FIRSTNAMESCOREDEDUCTION_BYRESULT(@FIRSTNAMERESULT);

    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_MIDDLENAMESCOREDEDUCTION_BYRESULT(@MIDDLENAMERESULT, @FIRSTNAMERESULT);

    if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
        return;
  end

  ------------------------------------------------------------------------------

  -- 2. Scoring tests that need table resources

  ------------------------------------------------------------------------------

  -- FIRSTNAME scoring tests 2 of 3

  if @FIRSTNAMERESULT is null
  begin
    declare @MATCHTYPECODE tinyint

    select @MATCHTYPECODE = MATCHTYPECODE
    from dbo.FIRSTNAMEMATCH
    where FIRSTNAME1 = @FIRSTNAME1
    and FIRSTNAME2 = @FIRSTNAME2

    if @MATCHTYPECODE = 0
      -- nickname

      select @FIRSTNAMERESULT = [NUMBER]
      from @LOGICALRESULTMAPPING
      where STRING = 'Nicknames'

    else if @MATCHTYPECODE = 1
      -- different spellings of same name

      select @FIRSTNAMERESULT = [NUMBER]
      from @LOGICALRESULTMAPPING
      where STRING = 'First name spelling variation'

    else if @MATCHTYPECODE = 2
      -- two different common names, may still be a typo/match

      select @FIRSTNAMERESULT = [NUMBER]
      from @LOGICALRESULTMAPPING
      where STRING = 'Known different first names'

    -- First name bail test

    if @MIDDLENAMERESULT is not null and @FIRSTNAMERESULT is not null
    begin
      select @SCORE -= DEDUCTION
      from dbo.UFN_CONSTITUENTMATCH_FIRSTNAMESCOREDEDUCTION_BYRESULT(@FIRSTNAMERESULT);

      select @SCORE -= DEDUCTION
      from dbo.UFN_CONSTITUENTMATCH_MIDDLENAMESCOREDEDUCTION_BYRESULT(@MIDDLENAMERESULT, @FIRSTNAMERESULT);

      if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
          return;
    end
  end
  ------------------------------------------------------------------------------

  -- TITLECODE scoring tests 2 of 2

  if @TITLERESULT is null
  begin
    select @TITLERESULT = [NUMBER]
    from @LOGICALRESULTMAPPING
    left outer join dbo.TITLECODECONFIGURATION c1 on c1.ID = @TITLECODE1
    left outer join dbo.TITLECODECONFIGURATION c2 on c2.ID = @TITLECODE2
    where STRING = case 
        when c1.GENDERCODE = c2.GENDERCODE and c1.GENDERCODE > 0 then 'Title: Same gender'
        when isnull(c1.GENDERCODE,0) = 0 or isnull(c2.GENDERCODE,0) = 0 then 'Title: Gender unknown'
        else 'Title: Different genders'
      end

    -- Title bail test

    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_TITLESCOREDEDUCTION_BYRESULT(@TITLERESULT);

    if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
        return;
  end
  ------------------------------------------------------------------------------

  -- SUFFIXCODE scoring tests 1 of 1

  if @SUFFIXRESULT is null
  begin
    if @SUFFIXCODE1 is null and @SUFFIXCODE2 is null -- some single blank cases will be given different scores

      set @SUFFIXRESULT = 4

    else if @SUFFIXCODE1 = @SUFFIXCODE2
      -- exact match

      set @SUFFIXRESULT = 0

    else
    begin
      with TCLEANSUFFIX as (
        select ID, replace(replace(replace(DESCRIPTION,',',''),'.',''),' ','') CLEANSUFFIX
        from dbo.SUFFIXCODE
        where ID in (@SUFFIXCODE1,@SUFFIXCODE2)
        union all
        select '00000000-0000-0000-0000-000000000000', ''
        where @SUFFIXCODE1 is null or @SUFFIXCODE2 is null
      ),
      TSUFFIXES as (
        select s1.CLEANSUFFIX SUFFIX1,
               s2.CLEANSUFFIX SUFFIX2
        from TCLEANSUFFIX s1, TCLEANSUFFIX s2
        where s1.CLEANSUFFIX < s2.CLEANSUFFIX  -- so we don't have to handle both permutations of each pair

      )
      select @SUFFIXRESULT = [NUMBER]
      from TSUFFIXES, @LOGICALRESULTMAPPING 
      where STRING = 'Suffix: ' + case when SUFFIX1 = '' then 'Blank' else SUFFIX1 end + '|' + SUFFIX2;

      if @SUFFIXRESULT is null
      begin
        if @SUFFIXCODE1 is null
          set @SUFFIXRESULT = 5
        else if @SUFFIXCODE2 is null
          set @SUFFIXRESULT = 6
     else
        begin
          -- Both suffixes non-blank, use the "all others" result

          select @SUFFIXRESULT = [NUMBER]
          from @LOGICALRESULTMAPPING
          where STRING = 'Suffix: All others'
        end
      end
    end
  end

  select @SCORE -= DEDUCTION
  from dbo.UFN_CONSTITUENTMATCH_SUFFIXSCOREDEDUCTION_BYRESULT(@SUFFIXRESULT);

  --Suffix bail test

  if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
      return;

  ------------------------------------------------------------------------------

  -- 3. Last resort: Fuzzy matching scoring.

  ------------------------------------------------------------------------------

  -- KEYNAME scoring tests 2 of 2

  if @KEYNAMERESULT is null
  begin
    -- fuzzy score

    set @KEYNAMESCORE = dbo.UFN_FUZZYSTRINGCOMPARER_COMPARESTRINGS(@KEYNAME1,@KEYNAME2)*100
    set @KEYNAMERESULT = dbo.UFN_CONSTITUENTMATCHSCORERESULT(1,@KEYNAMESCORE)

    set @SCORE += @MINIMUMNONMATCHKEYNAMEDEDUCTION; -- We factored in the best-case scenario into the score earlier.

    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_KEYNAMESCOREDEDUCTION_BYRESULT(@KEYNAMERESULT, @ISORGANIZATION)

    if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
        return;
  end
  ------------------------------------------------------------------------------

  -- FIRSTNAME scoring 3 of 3

  if @FIRSTNAMERESULT is null
  begin
    -- fuzzy score

    set @FIRSTNAMESCORE = dbo.UFN_FUZZYSTRINGCOMPARER_COMPARESTRINGS(@FIRSTNAME1,@FIRSTNAME2)*100
    set @FIRSTNAMERESULT = dbo.UFN_CONSTITUENTMATCHSCORERESULT(0,@FIRSTNAMESCORE)

    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_FIRSTNAMESCOREDEDUCTION_BYRESULT(@FIRSTNAMERESULT);

    if @MIDDLENAMERESULT is not null 
    begin
      select @SCORE -= DEDUCTION
      from dbo.UFN_CONSTITUENTMATCH_MIDDLENAMESCOREDEDUCTION_BYRESULT(@MIDDLENAMERESULT, @FIRSTNAMERESULT);

      if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
          return;
    end
  end
  ------------------------------------------------------------------------------

  -- MIDDLENAME scoring 2 of 2

  if @MIDDLENAMERESULT is null
  begin
    -- fuzzy score

    set @MIDDLENAMESCORE = dbo.UFN_FUZZYSTRINGCOMPARER_COMPARESTRINGS(@MIDDLENAME1,@MIDDLENAME2)*100
    set @MIDDLENAMERESULT = dbo.UFN_CONSTITUENTMATCHSCORERESULT(0,@MIDDLENAMESCORE)

    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_MIDDLENAMESCOREDEDUCTION_BYRESULT(@MIDDLENAMERESULT, @FIRSTNAMERESULT);

    if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
        return;
  end
  ------------------------------------------------------------------------------

  -- STREETNUMBER scoring tests 2 of 2

  if @STREETNUMBERRESULT is null
  begin
    -- fuzzy score

    set @STREETNUMBERSCORE = dbo.UFN_FUZZYSTRINGCOMPARER_EDITDISTANCESCORE(@STREETNUMBER1,@STREETNUMBER2)*100
    set @STREETNUMBERRESULT = dbo.UFN_CONSTITUENTMATCHSCORERESULT(2,@STREETNUMBERSCORE)

    set @SCORE += @MINIMUMNONMATCHSTREETNUMBERDEDUCTION; -- We factored in the best-case scenario into the score earlier.

    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_STREETNUMBERSCOREDEDUCTION_BYRESULT(@STREETNUMBERRESULT);

    if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
        return;
  end
  ------------------------------------------------------------------------------

  -- POSTCODE scoring tests 2 of 2

  if @POSTCODERESULT is null
  begin
    -- fuzzy score

    set @POSTCODESCORE = dbo.UFN_FUZZYSTRINGCOMPARER_EDITDISTANCESCORE(@POSTCODE1,@POSTCODE2)*100
    set @POSTCODERESULT = dbo.UFN_CONSTITUENTMATCHSCORERESULT(4,@POSTCODESCORE)

    if @POSTCODESCORE = 60 and left(@POSTCODE1,3) <> left(@POSTCODE2,3)
      -- first three digits of ZIP code not the same

      set @POSTCODERESULT = @THREEDIGITZIPMATCHRESULT

    set @SCORE += @MINIMUMNONMATCHPOSTCODEDEDUCTION; --Already factored in the best-case scenario.

    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_POSTCODESCOREDEDUCTION_BYRESULT(@POSTCODERESULT);

    if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
        return;
  end
  ------------------------------------------------------------------------------

-- STREETNAME scoring tests 2 of 2

  if @STREETNAMERESULT is null
  begin
    declare @TRANSLATEDSTREETNAME1 nvarchar(300), @TRANSLATEDSTREETNAME2 nvarchar(300)
    declare @BASESTREETNAME1 nvarchar(300), @BASESTREETNAME2 nvarchar(300)
    declare @STREETTOKEN1 nvarchar(20), @STREETTOKEN2 nvarchar(20)
    declare @DIRECTIONAL1 nvarchar(20), @DIRECTIONAL2 nvarchar(20);

  -- find known words in the street names

  -- @TRANSLATEDSTREETNAMEn will have all words translated to consistent words (Street->St, North->N, Apartment->Apt)

  -- @BASESTREETNAMEn will have street words and directionals removed (N Main St->Main) and apartment words converted to #

  -- The reason for converting to # is so that we can find the apartment section again later if needed.

  -- The removed street words and directionals are captured - if the base names are the same but these other pieces are different,

  -- we will adjust the score accordingly (Main St vs. Main Rd)


    with CTE as (
      select 
        cast(' '+@STREETNAME1+' ' as nvarchar(4000)) TRANSLATEDSTREETNAME,
        cast(' '+@STREETNAME1+' ' as nvarchar(4000)) BASESTREETNAME,
        cast(null as nvarchar(4000)) STREETWORDS,
        cast(null as nvarchar(4000)) DIRECTIONALS,
        0 LEVEL
      union all
      select 
        replace(TRANSLATEDSTREETNAME,TOKEN,TRANSLATION),
        replace(BASESTREETNAME,TOKEN,case when TOKENTYPECODE = 2 then ' #' else ' ' end),
        case when TOKENTYPECODE = 0 then isnull(STREETWORDS,'') + TRANSLATION else STREETWORDS end,
        case when TOKENTYPECODE = 1 then isnull(DIRECTIONALS,'') + TRANSLATION else DIRECTIONALS end,
        LEVEL+1
      from CTE
      inner join dbo.ADDRESSTOKEN m on charindex(TOKEN,BASESTREETNAME) > 0
      where not exists(select 'x' from dbo.ADDRESSTOKEN m2 where m2.TOKEN<m.TOKEN and charindex(m2.TOKEN,BASESTREETNAME) > 0)
      and COUNTRYID = isnull(@COUNTRYID,COUNTRYID)
    )
    select top 1 
      @TRANSLATEDSTREETNAME1 = ltrim(rtrim(TRANSLATEDSTREETNAME)),
      @BASESTREETNAME1 = ltrim(rtrim(BASESTREETNAME)),
      @STREETTOKEN1 = ltrim(rtrim(STREETWORDS)),
      @DIRECTIONAL1 = ltrim(rtrim(DIRECTIONALS))
    from CTE
    order by LEVEL desc;

    -- repeat for street name 2

    with CTE as (
      select 
        cast(' '+@STREETNAME2+' ' as nvarchar(4000)) TRANSLATEDSTREETNAME,
        cast(' '+@STREETNAME2+' ' as nvarchar(4000)) BASESTREETNAME,
        cast(null as nvarchar(4000)) STREETWORDS,
        cast(null as nvarchar(4000)) DIRECTIONALS,
        0 LEVEL
      union all
      select 
        replace(TRANSLATEDSTREETNAME,TOKEN,TRANSLATION),
        replace(BASESTREETNAME,TOKEN,case when TOKENTYPECODE = 2 then ' #' else ' ' end),
        case when TOKENTYPECODE = 0 then isnull(STREETWORDS,'') + TRANSLATION else STREETWORDS end,
        case when TOKENTYPECODE = 1 then isnull(DIRECTIONALS,'') + TRANSLATION else DIRECTIONALS end,
        LEVEL+1
      from CTE
      inner join dbo.ADDRESSTOKEN m on charindex(TOKEN,BASESTREETNAME) > 0
      where not exists(select 'x' from dbo.ADDRESSTOKEN m2 where m2.TOKEN<m.TOKEN and charindex(m2.TOKEN,BASESTREETNAME) > 0)
      and COUNTRYID = isnull(@COUNTRYID,COUNTRYID)
    )
    select top 1 
      @TRANSLATEDSTREETNAME2 = ltrim(rtrim(TRANSLATEDSTREETNAME)),
      @BASESTREETNAME2 = ltrim(rtrim(BASESTREETNAME)),
      @STREETTOKEN2 = ltrim(rtrim(STREETWORDS)),
      @DIRECTIONAL2 = ltrim(rtrim(DIRECTIONALS))
    from CTE
    order by LEVEL desc

    if @TRANSLATEDSTREETNAME1 = @TRANSLATEDSTREETNAME2
      -- exact match after translations

      set @STREETNAMERESULT = 0

    else if @BASESTREETNAME1 = @BASESTREETNAME2
    begin
      -- street names are same after removing street words and directionals

      if @STREETTOKEN1 <> @STREETTOKEN2 or
          @DIRECTIONAL1 <> @DIRECTIONAL2
        -- street words and/or directionals are different

        select @STREETNAMERESULT = [NUMBER]
        from @LOGICALRESULTMAPPING
        where STRING = 'Same base street names, different street words/directionals'

      else
        -- street word and/or directional present on one side but not other, or in different order in the string

        select @STREETNAMERESULT = [NUMBER]
        from @LOGICALRESULTMAPPING
        where STRING = 'Same base street names, missing or reordered street words/directionals'
    end

    else if @BASESTREETNAME1 like 'PO BOX %' and @BASESTREETNAME1 not like 'PO BOX % ' and
            @BASESTREETNAME2 like 'PO BOX %' and @BASESTREETNAME2 not like 'PO BOX % '
    begin
      -- PO BOX

      set @STREETNAMESCORE = dbo.UFN_FUZZYSTRINGCOMPARER_EDITDISTANCESCORE(
                              substring(@BASESTREETNAME1,8,len(@BASESTREETNAME1)-7),
                              substring(@BASESTREETNAME2,8,len(@BASESTREETNAME2)-7))*100

      -- use the streetnumber score results for PO Box numbers

      set @STREETNAMERESULT = dbo.UFN_CONSTITUENTMATCHSCORERESULT(2,@STREETNAMESCORE)
    end

    else if dbo.UFN_REGULAREXPRESSIONMATCH('^[\d]+(?:ST|ND|RD|TH)*$',@BASESTREETNAME1) = 1 and
            dbo.UFN_REGULAREXPRESSIONMATCH('^[\d]+(?:ST|ND|RD|TH)*$',@BASESTREETNAME2) = 1
    begin
      -- numeric street name, use "short string comparison" like street number/post code

      set @BASESTREETNAME1 = replace(replace(replace(replace(@BASESTREETNAME1,'ST',''),'ND',''),'RD',''),'TH','')
      set @BASESTREETNAME2 = replace(replace(replace(replace(@BASESTREETNAME2,'ST',''),'ND',''),'RD',''),'TH','')

      if @BASESTREETNAME1 = @BASESTREETNAME2
      begin
        -- same numeric street name, n vs. nth

        if @STREETTOKEN1 <> @STREETTOKEN2 or
            @DIRECTIONAL1 <> @DIRECTIONAL2
          -- street words and/or directionals are different

          select @STREETNAMERESULT = [NUMBER]
          from @LOGICALRESULTMAPPING
          where STRING = 'Same base street names, different street words/directionals'

        else
          set @STREETNAMERESULT = 0
      end

      else if @STREETTOKEN1 <> @STREETTOKEN2 or
              @DIRECTIONAL1 <> @DIRECTIONAL2
        -- different numbers, and different street words or directionals - not a match

        set @STREETNAMERESULT = 3

      else
      begin
        set @STREETNAMESCORE = dbo.UFN_FUZZYSTRINGCOMPARER_EDITDISTANCESCORE(@BASESTREETNAME1,@BASESTREETNAME2)*100

        -- use the streetnumber score results for numeric street names

        set @STREETNAMERESULT = dbo.UFN_CONSTITUENTMATCHSCORERESULT(2,@STREETNAMESCORE)
      end
    end
    else
    begin
      -- fuzzy score

      set @STREETNAMESCORE = dbo.UFN_FUZZYSTRINGCOMPARER_COMPARESTRINGS(@TRANSLATEDSTREETNAME1,@TRANSLATEDSTREETNAME2)*100
      set @STREETNAMERESULT = dbo.UFN_CONSTITUENTMATCHSCORERESULT(3,@STREETNAMESCORE)
    end

    select @SCORE -= DEDUCTION
    from dbo.UFN_CONSTITUENTMATCH_STREETNAMESCOREDEDUCTION_BYRESULT(@STREETNAMERESULT)

    if @CANPERFORMBAIL = 1 and @SCORE < @BAILTHRESHOLD
        return;
  end
  ------------------------------------------------------------------------------


  update @RESULTS 
  set 
    TITLERESULT = @TITLERESULT,
    FIRSTNAMERESULT = @FIRSTNAMERESULT,
    MIDDLENAMERESULT =@MIDDLENAMERESULT,
    KEYNAMERESULT = @KEYNAMERESULT,
    SUFFIXRESULT = @SUFFIXRESULT,
    STREETNUMBERRESULT = @STREETNUMBERRESULT,
    STREETNAMERESULT = @STREETNAMERESULT,
    POSTCODERESULT = @POSTCODERESULT,
    FIRSTNAMESCORE = @FIRSTNAMESCORE,
    MIDDLENAMESCORE = @MIDDLENAMESCORE,
    KEYNAMESCORE = @KEYNAMESCORE,
    STREETNUMBERSCORE = @STREETNUMBERSCORE,
    STREETNAMESCORE = @STREETNAMESCORE,
    POSTCODESCORE = @POSTCODESCORE,
    HOUSEHOLDMATCH = @HOUSEHOLDMATCH,
    COMPOSITESCORE = @SCORE,
    BAILEDCOMPARISON = 0;

  return;
end