Excel VBA 宏类型不匹配错误

Excel VBA macro type mismatch error

我发现我工作的一个办公室花了数周时间手动检查一个 Excel 电子表格,该电子表格包含一个超过 500,000 行的数据库,以查找符合特定条件的重复行。在研究之前不能简单地删除重复项,因为一个错误可能会导致数十万美元的生产损失。我决定简单地标记它们并引用原始行将是这种情况下的最佳答案。所以我决定研究宏,看看使用一个简单的宏可以节省多少时间 。我将其用作编程学习经验,所以请不要 "here's a =function()" 回答。

我写了一个宏,改了好几次都没用(最新的在下面)。我想使用 String 变量,因为无法确定要检查的单元格中输入了什么。以下是我从该站点尝试过、失败过和学到的东西(?):

最初,我尝试声明一个变量,然后将单元格中的值直接附加到它。例如Dim myString As String Set myString = Cells(x, x).Value 但是,我一直收到对象错误。感谢Michael's response here,我了解到你必须使用Range变量才能使用Set

我的下一期遇到了 "type mismatch" 错误。我正在尝试分配一个存储变量并将其与另一个存储变量进行比较,我确信这是导致问题的原因。我最初尝试 Dim myRange As Range, myString As String Set myRange = Cells(x, x).Value myString = myRange。这显然行不通,所以我尝试使用 CStr() "change to string" 函数将 Range 变量转换为我想要的 String 变量。这就是我被困的地方。

Sub Duplicate()

'Declare the variables
Dim NSNrange, PNrange, KitIDrange As Range
Dim NSN, PN, KitID As String
Dim NSNCheck, PNCheck, KitIDCheck As String
Dim i, j, printColumn, rowCount As Integer

'Set which column we want to print duplicates on, and count the number of rows used
rowCount = ActiveSheet.UsedRange.Rows.Count
printColumn = 9

'Lets get started!

'Clear the duplicate list column for a fresh start
Columns(printColumn).EntireColumn.Delete

'Start on line 2, and grab the cell values for the NSN, Part number and kit ID.
For i = 2 To rowCount

   Set NSNrange = Cells(i, 5).Value
   Set PNrange = Cells(i, 7).Value
   Set KitIDrange = Cells(i, 2).Value

   'Change whatever is contained in those cells into a string and store them into their respective containers
   NSN = CStr(NSNrange)
   PN = CStr(PNrange)
   KitID = CStr(KitIDrange)


      'Now let's look through the rest of the sheet and find any others that match the 3 variables that we stored above
      For j = 2 To rowCount

      'To avoid needless checks, we'll check to see if it's already had a duplicate found. If so, we'll just skip to the next row
      If Cells(j, printColumn).Value = "" Then

      'If the print column is blank, we'll grab the 3 values from the current row to compare against the above variables
      Set NSNrange = Cells(j, 5).Value
      Set PNrange = Cells(j, 7).Value
      Set KitIDrange = Cells(j, 2).Value

      'Now we store the contents into their very own container
      NSNCheck = CStr(NSNrange)
      PNCheck = CStr(PNrange)
      KitIDCheck = CStr(KitIDrange)

         'Check the initial row with the current row to see if the contents match. If so, print which row it is duplicated on.
         If NSN = NSNCheck And PN = PNCheck And KitID = KitIDCheck Then Cells(j, printColumn).Value = "Duplicated on row " & i


        End If

        Next j


   Next i

MsgBox "Search Complete"

End Sub

正如您所要求的有关类型错误的评论。有很多地方可能会引起混淆

1) 像这样在同一行做多个声明的每一行:

Dim NSNrange, PNrange, KitIDrange As Range

只有最后一个变量是显式类型声明的(在本例中为 Range)。其他的是隐式的Variant。所以,我已经仔细阅读并放在不同的行中并声明它们,因为我相信你可能已经打算这样做了。

2) 使用 Activesheet 并且在其他地方仅使用 CellsRange,这隐式引用了 Activesheet,这意味着如果您更改了 sheets 到那时你可能不再指的是你想要的 sheet 了。因此,虽然我保留了 Activesheet,并使用了一个总体 With Activesheet 语句,然后允许我说 .Cells.Range 等,但您应该将其更改为使用显式 sheet 名字。

3) 无论你在哪里使用 Set 关键字,都期望你正在使用一个对象(例如 Range)。按照你的命名约定,我会说你的意思是

Set NSNrange = Cells(i, 5)   

当你说

Set NSNrange = Cells(i, 5).Value

将一个范围设置为另一个范围而不是单元格值。

4) 我已将您的整数更改为长整数。您正在处理超出 Integer 类型可以处理的范围的行,因此您有溢出的风险。 Long更安全。

5) 而不是在 Range 上进行如下转换

NSN = CStr(NSNrange)

将采用范围的默认 属性,.Value,因为你想要一个字符串,你可以放弃 CStr 转换,只采用 .Text 属性 这会给你你想要的字符串。

6) 我没有使用空字符串文字 "" 比较,而是使用了 vbNullString,它的分配和检查速度更快。

Option Explicit

Sub Duplicate()

    Dim NSNrange As Range
    Dim PNrange As Range
    Dim KitIDrange As Range
    Dim NSN As String
    Dim PN As String
    Dim KitID As String
    Dim NSNCheck As String
    Dim PNCheck As String
    Dim KitIDCheck As String
    Dim i As Long
    Dim j As Long
    Dim printColumn As Long
    Dim rowCount As Long

    With ActiveSheet

        rowCount = .UsedRange.Rows.Count

        printColumn = 9

        .Columns(printColumn).EntireColumn.Delete

        For i = 2 To rowCount

            Set NSNrange = .Cells(i, 5)
            Set PNrange = .Cells(i, 7)
            Set KitIDrange = .Cells(i, 2)

            NSN = NSNrange.Text
            PN = PNrange.Text
            KitID = KitIDrange.Text

            For j = 2 To rowCount

                If .Cells(j, printColumn).Value = vbNullString Then

                    Set NSNrange = .Cells(j, 5)
                    Set PNrange = .Cells(j, 7)
                    Set KitIDrange = .Cells(j, 2)

                    NSNCheck = NSNrange.Text
                    PNCheck = PNrange.Text
                    KitIDCheck = KitIDrange.Text

                    If NSN = NSNCheck And PN = PNCheck And KitID = KitIDCheck Then
                        .Cells(j, printColumn).Value = "Duplicated on row " & i
                    End If

                End If

            Next j

        Next i

    End With

    MsgBox "Search Complete"

End Sub

因此,您将 对象 分配给 set(不仅仅是 range)是正确的。 cell 是一个对象,可以分配给 range 变量。但是当你使用对象的 methodsproperties 时,在这种情况下 .Value,并不意味着 return值是一个 range 对象。

因此,如果您需要了解所有属性和方法的用途,我强烈推荐 microsoft documentation

所以当你使用 .Value 时,你会得到一个变体(取决于值的类型)。在您的 use-case 中,您可以将其分配给 string,即 Dim str as string: str = Cells(1,1).Value。如果您只想将 cell 作为可以引用的对象:Dim cell as Range: Set cell = Cells(1,1)。现在可以处理所有属性和方法,例如:cell.Value 而不是 Cells(1,1).Value.

其他一些有用的知识。在VBA不像在VB.Net,那你最好不要搞混,如果你Dim var1, var2 as String只有var2是一个stringvar1是一个variant。所以需要为每个变量指定类型,Dim var1 as String, var2 as String.

您可能想要更改的另一件事是将 Cells, Range 分配给特定的 Worksheet。根据您的代码所在的模块,您的代码可能会在错误的工作表上运行。 (当其他人 adjust/run 代码时,它也最大限度地减少了错误),但主要是你只需要更改一个变量,如果你想引用另一个 Worksheet。可以使用 Worksheet-Object 来完成。

Dim ws as Worksheet
Dim str as String
Set ws = Worksheets(1)
'Now adress methods and properties with ws
str = ws.Cells(1,1).Value

还要注意这里 对象 Worksheet 而没有 sWorksheets是当前WorkbookWorksheet的合集。

您也可以使用 RemoveDuplicates 方法。

'Remove duplicates based on the data in columns; 2 "Kit", 5 "NSN", and 7 "PN".
ActiveSheet.UsedRange.RemoveDuplicates Columns:=Array(2, 5, 7), Header:=xlYes