On Error GoTo 不起作用;代码中断

5
我正在编写一个VBA函数,将数据从一个Access表导入到另一个表中。我要导入的表具有更严格的数据约束(例如类型、大小等),因此我预计会出现许多错误。
为了避免筛选每个VBA错误,当记录集循环遇到错误时,我希望它跳过整个当前记录并在运行时将其记录在单独的表中。因此,我已经插入了On Error GoTo RecordError。但由于某种原因,它并不能处理每个错误。我的代码只是报错并告诉我错误是什么。我已经打开了“在未处理异常时中断”选项。
这里是一张截图,应该可以解释清楚。 即使是单独的截图,对我来说似乎也毫无意义。 为什么它会在Error处理程序后面立即报错呢?

你能提供一下你看到的错误信息的例子吗? - Tim Lentine
错误信息与问题无关。错误与我的数据库字段格式有关,如果我没有设置 On Error GoTo...,那么我预计会收到这个错误信息。问题在于我的代码出现了错误,而不是转到我设置的标签。 - rdevitt
1
我询问的原因是因为在Access中会触发一些错误消息,这些错误消息无法使用VBA捕获。 - Tim Lentine
在编程中,"On Error Resume Next" 可以解决你所有的问题。 - Cody Gray
7个回答

3
我认为你不理解VB(A)错误处理的工作原理。请遵循以下原则:
- On Error...语句仅适用于它所在的过程(Sub或Function),但它也会捕获从该过程中调用的子过程中“冒泡”的错误。 - On Error设置了一个状态。也就是说,一旦发出On Error...,它将在剩余的过程中保持有效,除非被新的On Error...覆盖。 - 有四种形式的On Error...: - On Error GoTo <label>:<label>必须在同一过程中定义,方法是在单独的行上紧接着写标签名称,然后加上冒号(:)。 - On Error Resume:立即重试抛出错误的语句。这几乎从不使用,因为它可能是无限的。 - On Error Resume Next:忽略错误并继续执行。有时在清理例程末尾很有用(例如,如果要关闭可能打开或未打开的记录集)。或者,如果在任何可能引发错误的行之后立即检查Err对象(如果Err.Number为零(0),则该语句成功执行而不引发错误),也可以使用此形式。对于大多数情况来说,这是过度的工作量。 - On Error GoTo 0:关闭错误处理。 - 因此,通常将On Error...语句放在过程声明(Sub或Function语句)之后,但有些人会在它们的Dim语句之间放置它们。如果要在例程中临时更改错误处理方式,请将“新”处理方式放在适用的代码之前,然后(如果使用),在其后放置“还原”(重新发出原始处理方式)。
即使如此,我也不知道为什么在选择“Break on Unhandled Errors”时它会在抛出错误的行上中断,除非你已经混淆了它,以至于它认为没有活动的错误处理(如果是这种情况,我会感到惊讶,因为它编译不过)。
请注意,David Heffernan在他的答案中给出了这个问题的关键部分,并且在我的答案之前就已经回答了。

2
它不起作用的原因是因为你不能在错误处理程序中使用On Error Goto...。
请参见http://www.cpearson.com/excel/errorhandling.htm 你不能使用On Error跳过几行,而是应该将其放到一个错误处理程序中,然后恢复到所需的下一行(在你的示例中,你可能可以通过一个包含Resume Next的错误处理程序来解决)。
感谢Tim Williams在这个问题上的回答:The second of 2 'On Error goto ' statements gets ignored 顺便说一句,在ZIP上使用ParseInt会破坏以0开头的邮政编码,邮政编码应该被视为文本。

这是我的情况下的答案:我使用了 On Error GoTo _label_ 来跳过 For 循环内部的一些代码行。谢谢! - LS_ᴅᴇᴠ

1

您需要在希望处理错误的代码之前放置On Error行。

此外,您只需要有一条On Error行。然后错误处理程序将保持活动状态,直到子例程退出或执行另一个On Error语句。


这是我的假设,但似乎并没有起作用。我注释掉了除第一个之外的所有On Error GoTo RecordError语句,但仍然得到相同的结果。一旦我设置了On Error GoTo...,我的代码就不应该在该子程序中的任何其他位置中断。对吧??除非我将On Error更改为其他内容。 - rdevitt

1
使用VBA进行错误处理真的是一件非常麻烦的事情。我建议您看看这个“MS-Access,VBA和错误处理”问题的答案,并将其适应于您自己的情况。您可以轻松编写一些代码,将所有错误消息存储在表中,构建一个实际的错误报告系统。

1
将调试模式设置为“在所有错误处中断”会使程序执行在发生错误的行处停止,即使错误处理程序已经正确编写。这可能会让人感到困惑,因为它似乎表明错误处理没有起作用。

0

没有人真正回答了你的问题。

假设你的代码像这样(一个基本框架):

Public Sub MySub()
On Error GoTo errHandler
  Dim rs As DAO.Recordset

  Set rs = CurrentDB.OpenRecords([SQL SELECT])
  If rs.RecordCount >0 Then
     rs.MoveFirst
     Do Until rs.EOF
       [do whatever that produces the error]
errSkipToNext:
       rs.MoveNext
     Loop
  End If

exitRoutine:
  If Not (rs Is Nothing) Then
     rs.Close
     Set rs = Nothing
  Exit Sub

errHandler:
  Select Case Err.Number
    Case X, Y, Z ' where these are error numbers you want to ignore
      Err.Clear
      ' do whatever it is you need to do in order to record the offending row
      Call RecordError(rs!PK, Err.Number) ' PK is a field that identifies the bad record
      GoTo errSkipToNext
    Case Else
      MsgBox Err.Number & ": " & Err.Description, vbExclamation, _
         "Error!"
      Resume exitRoutine
  End Select
End Sub

在这段代码中,您在错误处理程序中使用SELECT CASE来决定要忽略哪些错误。在我上面的代码框架中,我将错误编号列为X,Y,Z,但您应该将其替换为您想要忽略的实际错误编号。
您不希望忽略每个错误,因为您可能会在子例程的其他地方忽略重要错误。如果您不想弄清楚要忽略的有限数量的错误是什么,我建议您在产生要忽略的错误的代码块的开头设置一个标志,然后使用“如果bolErrorInCodeBlockToIgnore Then”来决定是否忽略所有错误。类似这样:
Public Sub MySub()
On Error GoTo errHandler
  Dim rs As DAO.Recordset
  Dim bolErrorInCodeBlockToIgnore As Boolean

  Set rs = CurrentDB.OpenRecords([SQL SELECT])
  If rs.RecordCount >0 Then
     rs.MoveFirst
     Do Until rs.EOF
       bolErrorInCodeBlockToIgnore = True
       [do whatever that produces the error]
errSkipToNext:
       rs.MoveNext
     Loop
  End If

exitRoutine:
  If Not (rs Is Nothing) Then
     rs.Close
     Set rs = Nothing
  Exit Sub

errHandler:
  If bolErrorInCodeBlockToIgnore Then
     Err.Clear
     ' do whatever it is you need to do in order to record the offending row
     Call RecordError(rs!PK, Err.Number) ' PK is a field that identifies the bad record
     bolErrorInCodeBlockToIgnore = False
     GoTo errSkipToNext
  Else
     MsgBox Err.Number & ": " & Err.Description, vbExclamation, _
        "Error!"
     Resume exitRoutine
  End If
End Sub

我更喜欢第一种方式,因为我坚信只忽略已知错误,而不是发生的任何旧错误。但是,可能很难想出能生成所有您想要忽略的可能错误的测试。


0

我也曾见过错误处理失败。这是一个例子。

Public Function Have(ByVal item As Variant) As Boolean
'Have = Have data.  Simplifies handling nulls and empty strings in validation code

    On Error GoTo Procerr

    If IsNull(item) Then
        Have = False
    **ElseIf Len(Trim(item)) = 0 Then  'Faster than Item <> ""**
        Have = False
    ElseIf item = 0 Then
        Have = False
    Else
        Have = True
    End If

exitproc:
    Exit Function

Procerr:
    'Errors sometimes occur if an unbound control is referenced
    Have = False

End Function

代码有时会在标记为**的行上失败。以下是错误消息。

error dialog

请注意,错误处理程序已经失败。在这种情况下,调用代码的表单返回时,其记录源设置为空记录集,因此屏幕上的字段不可见。该表单是一个连续的形式,因此当使用空记录集加载表单时,记录和字段是不可见的。have()函数不是直接由我的代码调用的,但似乎是由me.requery方法触发的。在我的代码中,have()被调用了数亿次,但这是唯一导致它失败并且错误处理程序未被调用的实例。
针对Lance Roberts的原始问题,UTF-8 Unicode有时会与MS-Access产生冲突,因为它似乎允许数据被混淆为指令代码(我猜测)。如果数据最初是从文本文件加载的,则UTF-8可能会进入您的数据。带字节顺序标记(BoM)的UTF-8尤其棘手。当您运行某些处理数据的过程时,可能会出现奇怪的错误,并且看起来您的文件已经损坏。在其他情况下,文本处理函数会给出错误的答案,例如Mid()将看到BOM,如果您指定一个起始点,它将从BOM开始,但Len()会忽略BOM。我猜测如果您遇到此问题,则MS-Access可能无法正确处理错误。我导入数据时也遇到了类似的问题,将UTF-8作为ANSI导入是原因。请注意,对于纯英语数据,UTF-8和ANSI大多数时间是相同的,因此您的错误可能不在每一行中。我的错误主要出现在时间日期字段中。尝试先导出数据,然后强制将其转换为ANSI并删除任何BoM,然后重新导入数据。

谢谢Andoriyu, 我的系统设置为在出现所有错误时中断,这就是问题所在。 - AndrewM

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接