ExcelTip.Net留存知识帖 ---【注:附件之前被网盘供应商清空后,现已修复-现已修复-现已修复为本地下载!】
现在位置:首页 > 我的酷贴 > Excel VBA > 如何运用VBA从已知地址库中,模糊查找匹配输入的地址

如何运用VBA从已知地址库中,模糊查找匹配输入的地址

作者:绿色风 分类: 时间:2022-08-18 浏览:273
楼主
xmyjk
Q:有一个范围涉及到全国的地址库汇总表;现在手动输入一个地址A,要从地址库中模糊查找匹配的地址,作为查询结果。
     比如:地址库中有一个地址是“ 广东省佛山市顺德区大良镇”,而我手动输入的地址是“佛山 大良”,那么该函数的答案就是 库中的这个地址了。另外还需满足以下两点:
    1、当输入的内容,比 实际的地址库资料 要长一些时,要求也能找到答案,如输入 “北京 通州 新华 司空” 也能找到“北京市市辖区通州区新华街道办事处”这个答案;
    2、当输入的内容中不存在空格时,也能找到。如输入“涟源三甲”也能找到。

A:
  1. Option Explicit
  2. Public k

  3. Private Sub CommandButton1_Click()
  4.     Dim c$, jg$, I&, J&, cc$, d, ds()
  5.     Set d = CreateObject("scripting.dictionary")
  6.     TextBox2.Value = ""
  7.     If Len(Trim(TextBox1.Value)) = 0 Then MsgBox "并未输入查询内容,重新输入!": TextBox1.Value = "": Exit Sub
  8.     cc = Replace(TextBox1.Value, " ", "*")
  9.     ReDim ds(1 To Len(cc))
  10.     For I = 1 To Len(cc)
  11.         ds(I) = Mid(cc, I, 1)
  12.     Next
  13.     c = "*" & Join(ds(), "*") & "*"
  14.     cc = "": Erase ds
  15. line1:
  16.     For I = 0 To UBound(k)
  17.         If k(I) Like c Then d(k(I)) = 0
  18.     Next
  19.     jg = Join(d.KEYS, vbCrLf)
  20.     If Len(jg) > 0 Then
  21.         TextBox2.Value = jg
  22.     Else
  23.         c = Left(c, Len(c) - 2)
  24.         If Len(c) = 1 Then TextBox2.Value = "未找到!": Set d = Nothing: Exit Sub
  25.         GoTo line1
  26.     End If
  27.     d.RemoveAll
  28.     Set d = Nothing
  29. End Sub

  30. Private Sub UserForm_Initialize()
  31.     Dim arr, I&, d, J&, ssx$

  32.     Set d = CreateObject("scripting.dictionary")
  33.     arr = Sheets("全国地址库").UsedRange.Value
  34.     For I = 2 To UBound(arr)
  35.         ssx = arr(I, 1) & arr(I, 2) & arr(I, 3)
  36.         d(ssx) = 0
  37.         For J = 4 To UBound(arr, 2)
  38.             If IsEmpty(arr(I, J)) Or arr(I, J) Like "*未找到*" Then Exit For
  39.             d(ssx & arr(I, J)) = 0
  40.         Next
  41.         ssx = ""
  42.     Next
  43.     k = d.KEYS
  44.     d.RemoveAll
  45.     Erase arr
  46.     Set d = Nothing
  47.     TextBox2.Value = ""
  48. End Sub

  49. Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
  50.     Erase k
  51. End Sub

该帖已经同步到 xmyjk的微博

地址库.rar
2楼
chenlifeng
,有东西及时整理,这个习惯真好!
成功就是由这一系列的好习惯组成的!
3楼
liu12345678jing
有点难,没看懂。
4楼
jxcaixiaomeng
我试试看好不好用,感谢  
5楼
JOYARK1958
謝謝提供學習下載中
6楼
chenlifeng
对以上贴,我已经进行了最后的整理,结果更加精准了,看起来很长,实际运行的速度还不错,如下:


Option Explicit
Public K, arr
'查询中也出现一些特殊情况:如上海有两个一样的“青浦区”,一个是大点的行政划分,一个小点; 再如输入“蓬安”,四川省有个蓬安县,但就在这个答案的上一行有两个县,一个叫太蓬乡,另个是固安乡,两者合一起就饱含了刚才的“蓬安”两字,搜索中常找不到(已经避免);再如陕西有个“镇巴县“,开头第一字为“镇”,此行后面亦有镇,查询不方便,等等问题,发现的已经处理了。
Private Sub CommandButton1_Click()
    Dim c$, jg$, i&, II&, III, j&, CC$, d, DD$, ds(), ar(), br, str, bb, m, mm, mmm, tt, abc, bcd%, cde As String
    Dim ee, ff, di, dic, ddc, gg, u, w, x, y, ufo, fo, ppp, zzz, t1, t2, k1, k2, K3, xh
    zzz = 0
    Set d = CreateObject("scripting.dictionary")
    Set di = CreateObject("scripting.dictionary")
    Set dic = CreateObject("scripting.dictionary")
    Set ddc = CreateObject("scripting.dictionary")
    TextBox2.Value = ""
    If Len(Trim(TextBox1.Value)) = 0 Then MsgBox "并未输入查询内容,重新输入!": TextBox1.Value = "": Exit Sub

'*******下面简略地对输入的内容进行处理,去除空格、TAB键及其它,并把整理的结果以单个字符,中间隔个星号,以备下一步查询********
    CC = Replace(TextBox1.Value, " ", "")      '替换掉空格
    CC = Replace(CC, Chr$(9), "")     '替换掉有可能是从其它单元格复制过来的TAB键,看起来是空格,但在工作表中用=code()查阅,答案是9。
    CC = Replace(CC, "地区", "")
    CC = Replace(CC, "自治区", "")
    CC = Replace(CC, "自治州", "")
    CC = Replace(CC, "自治", "")
    CC = Replace(CC, "省直辖行政单位", "")
    CC = Replace(CC, "行政单位", "")
    CC = Replace(CC, "特别行政区", "")
    If Len(CC) - Len(Replace(CC, Right(CC, 1), "")) > 1 And (Right(CC, 1) = "区" Or Right(CC, 1) = "市") Then CC = Left(CC, Len(CC) - 1)
'上面一行的代码中,如果输入的关键词语最后一个字在输入的整个词语中出现了两次以上,则去掉最后这个字。为的是防止 :当输入“北京市 市辖区 东城区”等字眼时,找到的答案是“北京市市辖区”,而没有把应该包括在内的“东城区”计算在内。在下面的“去重复内容的代码”已经表示,故本行可去除。


'*******下面的几段用于处理特殊情况,然后退出程序*******
line0:
    ReDim ds(1 To Len(CC))
    For i = 1 To Len(CC)
        ds(i) = Mid(CC, i, 1)
    Next
    c = "*" & Join(ds(), "*") & "*"

    If Right(CC, 1) = "省" Then '本句专门对付 只输入省名的情况
       If Application.CountIf(Sheets("全国地址库").Range("A2:A" & Sheets("全国地址库").Range("A65536").End(xlUp).Row), TextBox1.Value) > 0 Then
          TextBox2.Value = CC: Exit Sub
       Else: MsgBox "没找到": Exit Sub
       End If
    End If
   
    If Application.CountIf(Sheets("全国地址库").Range("A2:A" & Sheets("全国地址库").Range("A65536").End(xlUp).Row), c & "市") > 0 Then '对付只输入市前面几个字,且是直辖市
       TextBox2.Value = CC & "市": Exit Sub
    ElseIf Application.CountIf(Sheets("全国地址库").Range("A2:A" & Sheets("全国地址库").Range("A65536").End(xlUp).Row), c & "省") > 0 Then '对付只输入省前面几个字的情况
       TextBox2.Value = CC & "省": Exit Sub
    ElseIf Application.CountIf(Sheets("全国地址库").Range("A2:A" & Sheets("全国地址库").Range("A65536").End(xlUp).Row), c & "自治区") > 0 Then   '对应是自治区类型的行政划分的情况
       TextBox2.Value = 匹配单元格值(Sheets("全国地址库").Range("A2:A" & Sheets("全国地址库").Range("A65536").End(xlUp).Row), c & "自治区"): Exit Sub
    End If
   
'*******初步实现查询,定义字典名称是d
line1:
    For i = 0 To UBound(K)
        If K(i) Like c Then
           If Len(CC) > 1 Then  '输入两个或以上字
               For II = 1 To Len(CC)
                   DD = Mid(CC, II, 2)
                   If InStr(K(i), DD) > 0 And Len(DD) = 2 Then d(K(i)) = 0:  Exit For
               Next
           Else: MsgBox "请输入两个以上字进行查找!", 48, "友情提示:": Exit Sub 'd(k(i)) = 0   '当只输入一个字时,也能查找到答案
           End If
        End If
    Next

    jg = Join(d.KEYS, vbCrLf)
    If Len(jg) > 0 Then
   

'**********下面则是把刚得出来的数组 d 的内容进行整理,去掉重复的,生成新字典 di,如“湖南娄底”与“湖南娄底涟源”,后者算重复********************************
       ar = d.KEYS
       str = CC
       For j = 1 To Len(str)
           For i = 0 To UBound(ar)
               bb = Mid(str, Len(str) + 1 - j, 1)  'bb 表示:当J为1时,是CC中的倒数第一个字符,J为2时,是倒数第二个字符,类推。
               m = InStr(ar(i), bb)  ' InStr只会按由前到后的顺序查找,查找到第一个后即停止查找,不管后面有没有再符合的。
               If m > 0 Then
                    mm = Len(ar(i)) - Len(Replace(ar(i), bb, "")) 'mm是在统计BB字符出现的次数
                    If mm > 1 Then  '表示存在两个或以上的bb
                       t1 = Mid(ar(i), m + 1, 1 + InStr(Replace(ar(i), bb, "", , 1), bb)) '如果bb出现了两次以上,这里指前两个bb之间是什么字符
                       mmm = InStr(Replace(ar(i), bb, "", , mm - 1), bb) 'mmm是指最后那个BB出现在CC中的第几个位置(去掉了前面的所有BB后);
                       If (bb = "县" Or bb = "市") Or ((InStr(ar(i), "市辖区") > 0 Or InStr(ar(i), "自治区") > 0) And bb = "区") Then
                           If InStr(str, t1) > 0 Then    '这儿是针对这种情况的,如我在窗口输入“朝阳区”时,我想要的答案是“北京市市辖区朝阳区”,但答案中出现了两个“区”,如果没有这一步,答案会是两种:一种就是刚才提到的,另一种就是去了“朝阳区”三字,即为“北京市市辖区”,出现的原因是下面判断时只以最后那个“区”为准,而InStr查找时,市辖区的“区”排列在朝阳区的“区”前面。
                              tt = Mid(ar(i), 1, mmm + mm - 1) '选择最后面的那个(一般情况可能第二个就是最后那个)
                           ElseIf InStr(t1, str) > 0 Then
                              If bb = "县" Or (bb = "市" And InStr(Mid(ar(i), 1, 3), bb) > 0) Then '这里有个3,专用于四大直辖市,当出现“重庆市县级市永川市”时,便起用; 三个“市”出现的不只是直辖市,如“长沙市济阳市文家市镇”,故刚才的条件中不用“mm>=3”
                                 tt = Mid(ar(i), 1, mmm + mm - 1)
                              Else
                                  If InStr(Mid(ar(i), 1, m), "自治区") > 0 Then  '本行专治“内蒙古自治区”等类别,当输入的是“回民区”之类的带有“区”字词语时
                                     tt = Mid(ar(i), 1, mmm + mm - 1)
                                  Else: tt = Mid(ar(i), 1, m) '“区”的其它情况而言。
                                  End If
                              End If
                           Else: tt = Mid(ar(i), 1, m)  '其它情况,通通取前面的那个字作依照
                           End If
                       Else
                           If InStr(t1, str) > 0 Or InStr(str, t1) > 0 Then '当出现两个BB时,第一个与最末那个之间的部分(一般为两个BB)与STR(或CC)之间的关系,只要存在从属关系,不论是谁包含谁,都取后者:MMM+MM-1
                              tt = Mid(ar(i), 1, mmm + mm - 1)
                           Else: tt = Mid(ar(i), 1, m)
                           End If
                       End If
                    Else:  tt = Mid(ar(i), 1, m) ':  If xh = 1 Then MsgBox ar(i) & "," & tt
                    End If
                    If Not di.Exists(tt) Then di.Add tt, "": III = 1
               End If
           Next
           If III = 1 Then GoTo line2: III = 0 '在CC中的倒数第一个字符中找到了,就不再进入倒数第二个查找,,千万注意的时,一定要记得把变量III重新赋值为0,否则答案会为空或只有一个答案。
       Next
line2:

       k1 = 0
       For Each k2 In di
           If InStr(k2, CC) > 0 Then k1 = k1 + 1   '在新的字典里审查元素,查找与输入的内容CC有从属关系的,在下一步循环中再把它从字典中删除
       Next
    If k1 > 0 Then  '承上一步,是指存在从属关系时才执行本动作
       For Each k2 In di
           If InStr(k2, CC) = 0 Then di.Remove k2
       Next
    End If
   
'下面一段,用于处理这个问题:当答案是“内蒙古自治区赤峰市红山区”与“内蒙古自治区赤峰市红山区红山工业园区”和“内蒙古自治区赤峰市红山区红山物流园区”之类时,很明显后面两个多余,去去除。

    br = di.KEYS
    For i = 1 To UBound(br)
        If Right(br(i), Len(CC)) = CC Then
            di(br(i)) = ""
        Else
            j = InStr(br(i), CC) + Len(CC) - 1
            If InStr(Right(br(i), Len(br(i)) - j), CC) <> 0 Then
               di(br(i)) = ""
            Else: di.Remove br(i)
            End If
        End If
    Next
        
'**********************到此,整理重复的过程完毕*********************************


        
'**********下面一段,为后一段赋上行政地址作准备,主要是从地址源库中,一行一行地查找符合上面字典di的行,把结果放到新字典dic中,再进入下一环节**********************************
    zzz = 0   '初始化zzz,zzz有其妙用处
    fo = 2    '初始华fo,它与ZZZ一样代表源地址库中的行数。
line4:  '标志4

For Each abc In di  'di 就是所输入的TEXTBOX1中的内容,进行了整理后的内容,每个字符中间用星号隔开。可能会循环一次或多次
    cde = "*"
    For bcd = 1 To Len(abc)
        cde = cde & Mid(abc, bcd, 1) & "*"
    Next
   
    For ufo = fo To Sheets("全国地址库").Range("A65536").End(xlUp).Row
      For ee = 1 To Sheets("全国地址库").Range("A" & ufo).End(xlToRight).Column
        If Not dic.Exists(arr(ufo, ee)) Then dic.Add arr(ufo, ee), ""    'dic 是源数据 ,现在被定义为字典,向里面写数据,这一句是把每行的数据收集起来,再到下面做判断,故每一行有多少就收集多少到字典中。
      Next
        ppp = Join(dic.KEYS, "") '把上面 定义好的字典赋值给变量PPP,方便下面的比较。
        zzz = zzz + 1        '这里的累加,是为了后面作准备:万一下面这句是属于误判,则根据ZZZ再返回到刚才收集数据的下一行进行,而不必再从头再来,以致死循环。至于下句会出现误判的原因:如输入“蓬安”,四川省有个蓬安县,但就在这个答案的上一行有两个县(各占一单元格),一个叫太蓬乡,另个是固安乡,两者合一起就饱含了刚才的“蓬安”两字,符合下面的要求,但最后会导致答案为空。
        If ppp Like cde Then '把上面收集来的数据与输入的内容比较判断,一旦符合就退出这个UFO循环, 进入下一个环节(给地址查找行政划分的单位)否则清空字典,再从上面来过。
           GoTo line3
        Else:   dic.RemoveAll
        End If
    Next
   
'*******下面意思是,给上面查找到的结果,赋上正确的行政划分地址,有行政地址的则免********
line3:
    u = abc   'abc即前面 进行了重复排查后的字典di中的元素,也是手动输入后整理出来的结果
    For Each gg In dic
        If InStr(u, gg) > 0 Then   '源数据中的行政地址与 U(或abc) 中的作一一对比,如果源数据的某一个数据被包括在U中,则
           u = Replace(u, gg, "")  '发现两者存在重复,清除U中的内容,
           dic.Remove gg           '亦清除字典dic中的重复内容
           If Len(u) = 0 Then  '这里是指全部把dic中的内容匹配完了,结果为空,说明输入的内容正好是一个完整的行政划分。
              If Not ddc.Exists(abc) Then ddc.Add abc, ""    '向新字典ddc 中写abc,这里表示所输入部分正好是一个完整的行政划分
           End If
        End If
    Next

    If Len(u) > 0 Then               '没有清除完的abc内容
       For Each gg In dic
           If InStr(gg, u) > 0 Then  '输入数据u与剩余的源数据gg作一一对比,与上面的一一对比正好相反 '如果存在重复,则:
              x = Mid(gg, InStr(gg, u) + Len(u), Len(gg) - InStr(gg, u) + Len(u) - 1) '提取源数据GG中比输入数据多余的部分
              y = abc & x        '再把多余的这部分与已经从最先输入的TEXTBOX1内容得了的答案di合并
              If Not ddc.Exists(y) Then ddc.Add y, ""     'ddc 向新字典中写入 “所输入的TEXTBOX1内容加 所缺少内容”
              Exit For  '找到后,给上述字典赋值完后,退出本轮的循环,进行新一个ABC的循环,如果字典DI有多个元素的话。
           End If
       Next
    End If
Next
       If zzz = Sheets("全国地址库").Range("A65536").End(xlUp).Row Then MsgBox "没找到": Exit Sub:
       If UBound(ddc.KEYS) + 1 = 0 Then:   fo = fo + zzz: GoTo line4 '这里的确UBound(ddc.keys) + 1 = 0表示数字是空?!
       If UBound(ddc.KEYS) = 0 And Right(CC, 1) = "市" Then
          For Each gg In ddc
              If InStr(gg, Mid(CC, 1, Len(CC) - 1)) = 0 Then CC = Mid(CC, 1, Len(CC) - 1): xh = 1: ddc.RemoveAll: GoTo line0 '本行作用在于:把不是“市”的行政单位当成“市”时,去掉“市”再运行一次。如“新化县”存在,“新化市”不存在。
          Next
       End If
       TextBox2.Value = Join(ddc.KEYS, vbCrLf)
    Else
        c = Left(c, Len(c) - 2) '当找不到时,把输入的关键词语从末尾起减少一个再看。(这里的C包含星号,故这样算)
        If Len(c) = 1 Then TextBox2.Value = "没找到": Set d = Nothing: Exit Sub
        GoTo line1
    End If

    Erase ds
      Set di = Nothing: Set di = Nothing: d.RemoveAll: Set d = Nothing
     ddc.RemoveAll: Set ddc = Nothing
   
End Sub


Private Sub UserForm_Initialize()
    Dim i&, d, j&, ssx$

    Set d = CreateObject("scripting.dictionary")
    arr = Sheets("全国地址库").UsedRange.Value
    For i = 2 To UBound(arr)
        ssx = arr(i, 1) & arr(i, 2) & arr(i, 3)
        d(ssx) = 0
        For j = 4 To UBound(arr, 2)
            If IsEmpty(arr(i, j)) Or arr(i, j) Like "*没找到*" Then Exit For
            d(ssx & arr(i, j)) = 0
        Next
        ssx = ""
    Next
    K = d.KEYS
    d.RemoveAll: Set d = Nothing
    TextBox2.Value = ""
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    Erase K
End Sub
7楼
chenlifeng
另还有一答案,基本和上面差不多,简短很多,仅是后面的地址没有进行行政匹配罢了:
Public brr(1 To 100000)

Sub test()
    Dim d As Object, arr, i, j, s$
    arr = Range("a1").CurrentRegion
    Set d = CreateObject("scripting.dictionary")
    s = InputBox("请输入关键词语", "请输入")
    For i = 1 To UBound(arr)
        If Right(arr(i, 1), Len(s)) = s Then
            d(arr(i, 1)) = ""
        Else
            j = InStr(arr(i, 1), s) + Len(s) - 1
            If InStr(Right(arr(i, 1), Len(arr(i, 1)) - j), s) <> 0 Then d(arr(i, 1)) = ""
        End If
    Next
    MsgBox Join(d.KEYS, vbCrLf)
End Sub

Sub 筛选()
    Dim arr, i&, j&, k&, rng As Range, s$, t, d
    t = Timer
    Set rng = Sheet2.Range("a1").CurrentRegion
    Set d = CreateObject("scripting.dictionary")
    arr = Range("a2").Resize(rng.Rows.Count - 1, rng.Columns.Count)
    k = 1
    For i = 1 To UBound(arr)
        s = arr(i, 1) & arr(i, 2) & arr(i, 3)
        For j = 4 To UBound(arr, 2)
            If arr(i, j) = "未找到" Then arr(i, j) = ""
            If s & arr(i, j) <> brr(k) Then
                k = k + 1
                brr(k) = s & arr(i, j)
            End If
        Next
        s = ""
    Next
    MsgBox brr
End Sub

Sub 显示窗体()
    UserForm1.Show 0
End Sub

'以上是在模块中的代码,下面是窗体中的代码:

Option Explicit

Private Sub CommandButton1_Click()
    Dim arr(), i&, s$, lng%, d As Object
    With Me
        If .TextBox1 = "" Then
            MsgBox "第一关键字不能为空!", vbCritical, "错误提示"
            .TextBox1 = ""
            .TextBox2 = ""
            .TextBox1.SetFocus
            Exit Sub
        End If
        筛选
        Set d = CreateObject("scripting.dictionary")
        If .TextBox2 <> "" Then
            For i = 2 To UBound(brr)
                If brr(i) <> "" Then
                    lng = InStr(brr(i), .TextBox2) + Len(.TextBox2) - 1
                    If Right(Left(brr(i), lng), Len(.TextBox2)) = .TextBox2 Then
                        If InStr(Left(brr(i), lng), .TextBox1) <> 0 Then
                            d(Left(brr(i), lng)) = ""
                        End If
                    End If
                Else
                    Exit For
                End If
            Next
        Else
            For i = 2 To UBound(brr)
                If brr(i) <> "" Then
                    lng = InStr(brr(i), .TextBox1) + Len(.TextBox1) - 1
                    If Right(Left(brr(i), lng), Len(.TextBox1)) = .TextBox1 Then
                        d(Left(brr(i), lng)) = ""
                    End If
                Else
                    Exit For
                End If
            Next
        End If
        .ListBox1.List = d.KEYS
        .Label6 = "一共有 " & d.Count & " 个满足条件的记录!"
    End With
   
End Sub

Private Sub CommandButton2_Click()
    With Me
        .TextBox1 = ""
        .TextBox2 = ""
        .ListBox1.Clear
        .Label6 = ""
        .TextBox1.SetFocus
    End With
End Sub

Private Sub CommandButton3_Click()
    Unload Me
End Sub

End Sub

Private Sub UserForm_Initialize()
    With Me
        .TextBox1 = ""
        .TextBox2 = ""
        .TextBox1.SetFocus
    End With
End Sub
地址查询与匹配.rar
8楼
living88
哈哈,代码都非常的哇

免责声明

有感于原ExcelTip.Net留存知识的价值及部分知识具有的时间限定性因素, 经与ExcelTip.Net站长Apolloh商议并征得其同意, 现将原属ExcelTip.Net的知识帖采集资料于本站点进行展示, 供有需要的人士查询使用,也慰缅曾经的论坛时代。 所示各个帖子的原作者如对版权有异议, 可与本人沟通提出,或于本站点留言,我们会尽快处理。 在此,感谢ExcelTip.Net站长Apolloh的支持,感谢本站点所有人**绿色风(QQ:79664738)**的支持与奉献,特此鸣谢!
------本人网名**KevinChengCW(QQ:1210618015)**原ExcelTip.Net总版主之一

评论列表
sitemap