【问题标题】:Delete the Rows all at a time,once selected一次删除所有行,一旦选择
【发布时间】:2012-12-18 21:22:50
【问题描述】:

考虑下面的 Excel:

Id    Col1     Col2     Col3   Col4
25     s        p         n    
11     a        t         x     g
17                        r     t
10     a                  a     e
66     a                  a

假设我有一个包含 ID 号的数组
例如Arr=(25,11,66)

是否可以一次删除该数组中ID号所在的所有行?

我需要先选择它们吗?

代码:

 Option Explicit

 Dim arr,objExcel1,strPathExcel1,objSheet1


 Set objExcel1 = CreateObject("Excel.Application")'Object for Condition Dump
 strPathExcel1 = "D:\VA\Test.xlsx"
 objExcel1.Workbooks.open strPathExcel1

 Set objSheet1 = objExcel1.ActiveWorkbook.Worksheets(4)


 arr = Array(5,11,66)

 objSheet1.Range("A" & Join(arr, ",A")).EntireRow.Delete

错误“未知的运行时错误”——我收到了

【问题讨论】:

  • 有可能。您尝试过任何代码吗?
  • 请看我更新的描述,但它不起作用。

标签: excel vbscript vba


【解决方案1】:

这是一种蛮力方法。 根据 OP 的后续评论更新

代码:

Option Explicit


Sub overWriteRows()
Dim d As Object
Dim wkSheet As Worksheet
Dim myRange As Range
Dim myArray As Variant
Dim deleteArray As Variant
Dim finalArray As Variant
Dim upBound As Long, i As Integer
Dim j As Integer, k As Integer, m As Integer

Set d = CreateObject("scripting.dictionary")
Set wkSheet = Sheets("Sheet1") '-- set your own sheet e.g. Sheet2
Set myRange = wkSheet.Range("B3:F8") '-- set your own range e.g. "B2:E5"

'-- validate if range is null or not
If myRnage is nothing then
  Exit Sub
End if

myArray = Application.WorksheetFunction.Transpose(myRange)
'-- now if you do not have delete range in a sheet range then
    '-- you may populate the dictionary right away manually so
        '-- you do not need deleteArray
deleteArray = Application.WorksheetFunction.Transpose(Range("G3:I3"))
'-- if you are populating dictionary manually then
    '-- you may set upBound = Ubound(myArray,2) - d.Count
upBound = UBound(myArray, 2) - UBound(deleteArray)
ReDim finalArray(LBound(myArray, 2) To upBound, LBound(myArray) To UBound(myArray))

'-- replace this with your manual dictionary population code
For i = LBound(deleteArray) To UBound(deleteArray)
    If Not d.exists(deleteArray(i, 1)) Then
        d.Add deleteArray(i, 1), i
    End If
Next i

    k = 1

    For j = LBound(myArray, 2) To UBound(myArray, 2)
        If Not d.exists(myArray(1, j)) Then
        '-- if you want to remove even duplicate records then u can use this
           'd.Add myArray(1, j), k 
                For m = LBound(myArray) To UBound(myArray)
                    finalArray(k, m) = myArray(m, j)
                Next m
                k = k + 1
        End If
    Next j

'-- you may use following code to flush old row data
    'myRange.Value = ""
'-- output the new array to sheet by over writing the old range
'-- you may use myRange instead of "B11" to overwrite old data with filtered data
    wkSheet.Range("B11").Resize(UBound(finalArray), _ 
         UBound(Application.Transpose(finalArray))) = finalArray

    Set d = Nothing
End Sub

输出

【讨论】:

  • @Tukai Rakshit 这里的代码可以避免带有字典的嵌套循环;)
  • 你太棒了!!想要你做我的VBS私教!! :-)。谢谢.. 感谢 Stackoverflow 也将 BonCodigo 带到这里,很高兴向你们学习。
  • @bonCodigo +1 使用字典 - 所以它不完全是“蛮力”。
  • @TukaiRakshit Set myRange = wkSheet.Range("B3:F8") 此行将 B3:F8 工作表范围设置为范围对象。它更多的是为了代码中的可读性和干净的访问。这不是很重要,因为你可以直接使用,myArray = Application.WorksheetFunction.Transpose(wkSheet.Range("B3:F8")) 我已经展示了两种方法。
  • 无论如何,您需要调整您的工作表、范围、值等的代码... ;) 不用担心,因为代码足够动态,可以根据您使用的范围设置数组大小。唯一的事情是,如果范围为空,我们需要对exist sub 进行验证。 我什至建议你放一个按钮。然后它的on click 触发器将打开两个输入框,您可以在其中输入范围。这两个范围可以是 myRange 和 deleteRange 的参数;)有意义吗?如果你想让我也用它来更新代码,请告诉我。
【解决方案2】:

EDIT:进一步优化代码

编辑:使用字典避免嵌套循环以提高性能

还请注意,由于标签是 vba & vbs,所以这里给出的答案是兼容的。

这个解决方案是删除整行而不是只包含数据的范围。

编辑:更新代码以将 A 列的值与 Arr 中的值匹配

假设从第 2 行开始的值是数字

您可以使用Excel提供的录制宏功能来观察Range Object的样子

http://spreadsheets.about.com/od/advancedexcel/ss/080703macro2007.htm

Sub t()
    Dim str
    Dim arr
    Dim i
    arr = Array(1, 2, 4)
    Dim row
    Dim height
    Dim found
    Dim dataArray
    Dim d
    height = Cells(Rows.Count, 1).End(-4162).row
    ReDim dataArray(height - 2, 0) ' -1 for 0 index, -1 for the first row as header row, excluded
    str = ""
    dataArray = Range(Cells(2, 1), Cells(height, 1)).Value
    Set d = CreateObject("scripting.dictionary")
    For i = LBound(arr) To UBound(arr)
        If Not d.exists(arr(i)) Then
            d(arr(i)) =  0
        End If
    Next
    For i = LBound(dataArray, 1) To UBound(dataArray, 1)
        If d.exists(dataArray(i, 1)) Then
            'found in column 1
            str = str & i & ":" & i & ","
        Else
            'found = False
        End If
    Next
    If Len(str) > 0 Then
        str = Mid(str, 1, Len(str) - 1)
        Range(str).Delete

    End If

End Sub

【讨论】:

  • 只是为了确认,“5、11、66”是行号对吗?因为我想删除给定数组中存在 ID 的行。
  • 我的描述数组只包含ID号,不包含行号。我想删除ID对应的行。
  • @Larry - 如果您重构代码以使用字典以避免嵌套循环,我会投赞成票。
  • @Larry +1 使用字典进行了很好的优化并且准时
  • @TukaiRakshit 两种方法都检查,添加元素到对字典字典教程:kamath.com/tutorials/tut009_dictionary.asp
【解决方案3】:

不是答案,而是关于优化@Larry 解决方案的思考:

>> a = Array(1, 3, 5, 5, 3, 1)
>> b = Array(1, 2, 3, 4, 5, 6)
>> set c = CreateObject("Scripting.Dictionary")
>> for i = 0 To UBound(a)
>>   c(a(i)) = 0
>> next
>> for i = 0 To Ubound(b)
>>     if c.Exists(b(i)) then
>>        WScript.Echo "delete", i, b(i)
>>     end if
>> next
>>
delete 0 1
delete 2 3
delete 4 5
  1. 使用 dic(key)=value 而不是通过 .Exists 进行检查
  2. 通过利用 .Exists 返回 bool 的事实来避免额外的变量(找到)

【讨论】: