【问题标题】:Extract fixed length number from Excel cell从 Excel 单元格中提取固定长度的数字
【发布时间】:2020-10-11 02:30:56
【问题描述】:

一些类似命名的线程,但仍然无法解决我的问题。我需要从 Excel 字符串中提取一个固定长度的 NUMBER 值(在我的场景中为 8 位)。为此目的提供了以下 Excel 公式:

=MID(A1,FIND("--------",SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(A1,"0","-"),"1","-"),"2","-"),"3","-"),"4","-"),"5","-"),"6","-"),"7","-"),"8","-"),"9","-")),8)

它完成了这项工作,但是我有两个问题:

  1. 最重要的是 - 我正在寻找一个精确匹配。虽然它确实提取了它找到的第一个 8 位序列,但我真的在 only 8 位数字之后,这意味着应该忽略 9 位(或更长)数字(作为 7 位数字已经是)。此公式还从较长的数字中提取前 8 位数字。

  2. 不太重要,但最好只查找以 1 开头的数字。所以,真的只是想提取这个:1???????作为数值。所以应该提取像“a12891212a”或“a 12891212a”这样的东西,同时128912120a或23456789 不应该。

如果可行,与 VBA 相比,我更喜欢基于 Excel 公式的方法。 非常感谢任何帮助!

【问题讨论】:

  • 012345678 呢?
  • 不行,9 位数字。虽然这样的事情不应该经常发生(98% 的证明解决方案也可以)。

标签: excel vba excel-formula


【解决方案1】:

使用 CONCAT+FILTERXML 函数的另一个选项

在B2中,公式复制下来:

=IFERROR(FILTERXML("<a><b>"&CONCAT(INDEX(TEXT(MID(SUBSTITUTE(A2," ","A"),ROW(A$1:INDEX(A:A,LEN(A2))),1),"0;;0;""</b><b>"""),0))&"</b></a>","//b[.>9999999][.<20000000]"),"not found")

【讨论】:

    【解决方案2】:

    通过Split() 函数实现的简单 VBA 替代方案

    注意 Split() 函数将在此处仅由其(可选)最后一个参数2 拆分为两个 部分; 默认情况下你会得到无限的分割部分

    由于有效的数字序列必须以"1" 开头,因此该数字显示为我们拆分操作的分隔符。

    将分隔符 "1" 加入要拆分的 sample 字符串允许引用 parts(1) 而不会出错,即使根本没有分隔符 ("1")。

        parts = Split(sample & "1", "1", 2)    ' arguments: string, delimiter, {limit}
    
    Function EightDigits(sample, Optional chk As String = "N/A!") As String
    Dim parts: parts = Split(sample & "1", "1", 2)   ' limit to 2 split parts only!
    If Not Right(parts(0), 1) Like "#" Then          ' other preceding digit means no first "1"
        EightDigits = chk                            ' temporary string value "N/A!"
        chk = "1" & CStr(Val(parts(1)))              ' Val() cuts following non digits
    End If
    If Len(chk) = 8 Then EightDigits = chk           ' return only 8-digit numbers
    End Function
    

    【讨论】:

    • 看起来很漂亮,但它不适用于 OP 提供的更多扩展示例。不幸的是,他只在 cmets 中提到了一些东西。它适用于实际问题中的样本。 =)
    【解决方案3】:

    这是基于公式的方法,假设数据在单元格 A2 中,原则上应该有效

    =IFERROR(LOOKUP(2,1/( MID(A2,ROW($A$1:INDEX(A:A,LEN(A2))),8)* (LEN(MID(A2,ROW($A$1:INDEX(A:A,LEN(A2))),8))=8)* (NOT(ISNUMBER(MID("a"&amp;A2,ROW($A$1:INDEX(A:A,LEN(A2))),1)+0)))* (NOT(ISNUMBER(RIGHT(MID(A2,ROW($A$1:INDEX(A:A,LEN(A2))),9))+0)))* (MID(A2,ROW($A$1:INDEX(A:A,LEN(A2))),1)="1")),MID(A2,ROW($A$1:INDEX(A:A,LEN(A2))),8)),"")

    它很长,因为它主要是为了生成 1 个字符串和 4 个测试而构建的。

    MID(A2,ROW($A$1:INDEX(A:A,LEN(A2))),8) 生成 8 个字符的字符串。

    (LEN(MID(A2,ROW($A$1:INDEX(A:A,LEN(A2))),8))=8) 测试是否有 8 个字符

    (NOT(ISNUMBER(MID("a"&amp;A2,ROW($A$1:INDEX(A:A,LEN(A2))),1)+0))) 检查前一个字符不是数字

    (NOT(ISNUMBER(RIGHT(MID(A2,ROW($A$1:INDEX(A:A,LEN(A2))),9))+0))) 检查正在测试的字符串旁边的字符不是数字

    (MID(A2,ROW($A$1:INDEX(A:A,LEN(A2))),1)="1") 检查第一个数字是否为 1。

    因此,如果最后 4 个测试为真,则乘法产生 8 位数字,用于计算倒数,使用​​ LOOKUP 检查,用于返回找到的结果。

    但是,从维护的角度来看,我不会推荐它。

    【讨论】:

    • 谢谢,它确实起作用了,但与 JvdV-s 第一个选项类似,它不能正确识别其中的一些。一些例子:“10626872 BLUE RASPBERRY OU 22”; “INVO 10627724 OU TSETSEC”无法识别。编辑:我可能被 INDEX/MATCH 和现在的 XLOOKUP 宠坏了。事实上,我真正的公式是从实际字符串中得到的。看到包含 LOOKUP 的解决方案,确实有所作为吧?
    • @dotsent12,两者都在我的最后被识别,所以我对你的回复感到困惑。
    【解决方案4】:

    这是一个简单的用户定义函数,用于查找数字子字符串。它创建一个子字符串数组。然后它在该数组中查找长度为 8 且前导字符为 1 的元素:

    Option Explicit
    
    Public Function NineD(s As String) As String
        Dim L As Long, temp As String, wf As WorksheetFunction
        Dim i As Long, arr, a
        
        Set wf = Application.WorksheetFunction
        temp = s
        L = Len(s)
        
        For i = 1 To L
            If Mid(temp, i, 1) Like "[0-9]" Then
            Else
                temp = wf.Replace(temp, i, 1, " ")
            End If
        Next i
        
        arr = Split(wf.Trim(temp), " ")
        For Each a In arr
            If Len(a) = 8 And Left(a, 1) = "1" Then
                NineD = a
                Exit Function
            End If
        Next a
    End Function
    

    用户定义函数 (UDF) 非常易于安装和使用:

    1. ALT-F11 调出 VBE 窗口
    2. ALT-I ALT-M 打开一个新模块
    3. 粘贴内容并关闭 VBE 窗口

    如果您保存工作簿,UDF 将与它一起保存。 如果您使用的是 2003 年以后的 Excel 版本,则必须保存 文件为 .xlsm 而不是 .xlsx

    删除 UDF:

    1. 如上所示打开 VBE 窗口
    2. 清除代码
    3. 关闭 VBE 窗口

    使用来自 Excel 的 UDF:

    =NineD(A1)

    要了解有关宏的更多信息,请参阅:

    http://www.mvps.org/dmcritchie/excel/getstarted.htm

    http://msdn.microsoft.com/en-us/library/ee814735(v=office.14).aspx

    有关 UDF 的详细信息,请参阅:

    http://www.cpearson.com/excel/WritingFunctionsInVBA.aspx

    必须启用宏才能正常工作!

    【讨论】:

      【解决方案5】:

      这可以通过公式完成,但完全取决于您的 Excel 版本:


      1) Excel 2016,您仍然可以使用公式:

      B1中的公式:

      =IFERROR(MID(A1,MAX((MID(A1,ROW(A$1:INDEX(A:A,LEN(A1))),1)="1")*(ISNUMBER(--MID(A1,ROW(A$1:INDEX(A:A,LEN(A1))),8)))*(NOT(ISNUMBER(--MID(A1,ROW(A$1:INDEX(A:A,LEN(A1)))+8,1))))*(NOT(ISNUMBER(--MID(A1,ROW(A$1:INDEX(A:A,LEN(A1)))-1,1))))*(ROW(A$1:INDEX(A:A,LEN(A1))))),8),"Nothing found")
      

      注意:这是一个数组公式,需要通过CtrlShiftEnter确认


      2) Excel 2019,使用CONCATFILTERXML

      B1中的公式:

      =IFERROR(FILTERXML("<t><s>"&CONCAT(IF(ISNUMBER(--MID(A1,ROW(A$1:INDEX(A:A,LEN(A1))),1)),MID(A1,ROW(A$1:INDEX(A:A,LEN(A1))),1),"</s><s>"))&"</s></t>","//s[starts-with(., '1') and string-length(.) =8]"),"Nothing Found")
      

      注意:这是一个数组公式,需要通过CtrlShiftEnter确认


      3) Excel 365,使用前面提到的功能,但包括SEQUENCE

      B1中的公式:

      =IFERROR(FILTERXML("<t><s>"&CONCAT(IF(ISNUMBER(--MID(A1,SEQUENCE(LEN(A1)),1)),MID(A1,SEQUENCE(LEN(A1)),1),"</s><s>"))&"</s></t>","//s[starts-with(., '1') and string-length(.) =8]"),"Nothing Found")
      

      公式的XPATH 部分负责实际查询,查找以“1”开头且总长度为“8”的字符串。这甚至可以处理返回 '12345678' 之类的字符串,例如 'abc123456789abc12345678abc29876543'。

      如果您喜欢FILTERXMLXPATH,那么您可能会觉得this 很有趣。


      4) VBA:如果您必须使用 VBA,我想 UDF 是一个不错的选择。比如:

      Function GetStr(str As String, pat As String) As String
      
      With CreateObject("vbscript.regexp")
          .Pattern = pat
          .Global = True
          If .Test(str) = True Then
              GetStr = .Execute(str)(0).Submatches(0)
          Else
              GetStr = "Nothing found"
          End If
      End With
      
      End Function
      

      您可以按照=GetStr(A1,"(?:^|\D)(1\d{7})(?:\D|$)")B1 中调用它。这是使用正则表达式。如果您有兴趣并想了解更多信息,那么this 对您来说是一本有趣的书。

      我故意将模式留在 UDF 之外,您可能想要更改它。当前的模式可以在这个在线Demo 中看到,引擎将从左到右查找:

      • (?: - 第一个非捕获组
        • ^|\D - 起始字符串 ancor 或除数字以外的任何内容。
        • ) - 关闭第一个非捕获组。
      • ( - 第一个捕获组。
        • 1\d{7} - 搜索文字 1 后跟 7 位数字。
        • ) - 关闭第一个捕获组。
      • (?: - 第二个非捕获组
        • \D|$ - 除数字或结束字符串 ancor 以外的任何内容。
        • ) - 关闭第二个非捕获组。

      【讨论】:

      • 您如何尝试涵盖几乎所有可能性,这对许多用户来说是令人惊奇和有启发性的 :-)
      • 非常感谢,多么伟大的努力和专业知识。不幸的是,它没有像我希望的那样工作。抱歉,错过了正确的标签,在这里运行 Office365!不幸的是,从 CONCAT&FILTERXML 或 SEQUENCE 选项中没有得到任何匹配,都以错误结尾。想知道我是否在公式复制方面失败了——例如字符串“AIR LIVON LTD UK8810109920 099920082709286949 INV NO 10628054 1094284”应该获取“10628054”,但会产生错误。
      • 我确实得到了第一个选项的匹配 - 但同样不是 100% 始终如一。例如。对于相同字符串的示例,它会获取“1094284”而不是正确的。
      • @dotsent12,如果您有 Excel 365,那么我放下的选项在您提供的示例上可以正常工作,它最终会产生正确的值。你能告诉我你是如何输入公式的吗?你使用什么语言?另外,你得到了什么确切的错误?
      • @dotsent12。有什么进展吗?
      猜你喜欢
      • 2018-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多