【问题标题】:How do I perform a function in sequential dynamic ranges?如何在顺序动态范围内执行功能?
【发布时间】:2013-12-03 23:08:08
【问题描述】:

VBA 新手在这里。我确实找到了一些关于编码这些循环的信息,但我很难弄清楚它是否和/或如何适用于我的特定需求,所以提前感谢您提供的任何帮助。

为了在格式化和上传信息之前对其进行质量检查,我想循环浏览多组动态范围,并对照该范围内的另一列检查信息。每个范围都按 D 列中的电子邮件地址分组,我需要确保 G 列中也列出了相同的电子邮件(我将在上传之前删除 B-D 列)。由于每个分组可以是 1 到 100 行之间的任何地方,我已经编写了如何定义范围的代码(如下),但是如何添加一个循环来分别在每个组中执行检查?

所有这些的输出都应该是一个消息框,上面写着“全部清除!”如果代码没有发现错误,或者“[Name] 未列出。请在继续之前添加他们的信息。”如果它们未列出。

我假设我应该为此执行某种 Do While 或 Do Until 或 For 循环,但是我在概念上对是否在循环内或循环外声明我的变量以及如何连接可能的多个未列出的变量感到困惑名字放到最后的同一个消息框中。

这是我目前所拥有的:

Sub QANameIsListed()
'Declare the variables.
Dim nRow As Long
Dim nStart As Long, nEnd As Long
Dim sEmail As String
Dim sName As String

'Figure out what first email address is.
sEmail = Range("D2").Text

'Figure out where first group data starts.
For nRow = 1 To 65536
    If Range("D" & nRow).Value = sEmail Then
        nStart = nRow
    End If
Exit For
Next nRow

'Figure out where first group data ends.
For nRow = nStart To 65536
    If Range("D" & nRow).Value <> sEmail Then
        nEnd = nRow
    End If
Exit For
Next nRow
nEnd = nEnd - 1

'Check whether the name is listed in the second column.
With Range("G" & nStart & ":G" & nEnd)
sName = Range("B" & nStart).Text & " " & Range("C" & nStart).Text
    Set c = .Find(sEmail)
    If c Is Nothing Then
        MsgBox (sName & " " & "isn't listed." _
        & "  " & "Please add their information before continuing.")
    Else
        MsgBox ("All clear!")
    End If
End With
End Sub

【问题讨论】:

    标签: excel vba msgbox


    【解决方案1】:

    我在您的帖子中没有看到任何真正的问题。 :) 但是,这是我的看法。

    首先,您将Exit For 放置在错误的位置。如果将其放在If---End If 块之外,那么您的For 循环将始终在到达Next nRow 之前退出。

    其次,您循环通过 65536 个单元两次,这不仅是资源密集型的,而且也不完全兼容。如果我的数据在第 65537 行,我会完全避开循环。毕竟,在 Excel 2007 及以后的版本中,有一百万行可用。

    我的建议是,只使用Find。我们将使用它从顶部查找sEmail 的第一次出现和底部的sEmail 的最后一次出现。我们将为此返回他们的行索引。当然,这仅适用于您的电子邮件已正确排序的假设...

    最后的部分很简单,但是可以逃过一些初学者,所以不用担心。我们所做的是,我们声明从上面确定的范围,我们将在这个范围内循环。你就快到了,太好了。

    我对您的代码的修改未经测试,但它捕获了您尝试实现的目标,然后可能还有一些。有些行我冒昧地完全删除,因为我发现它们是不必要的(Set c = .Find(sEmail),其中之一)。我还添加了一些其他“对新手友好”的东西,比如Boolean 检查和MsgBox 中多行的快速而肮脏的方法。

    代码如下:

    Sub QANameIsListed()
    'Declare the variables.
    Dim nRow As Long
    Dim nStart As Long, nEnd As Long
    Dim sEmail As String
    Dim sName As String
    Dim cRng As Range, cL As Range 'BK201: Declare cRng.
    Dim rStr As String 'BK201: For multiple unlisted names.
    Dim aClr As Boolean 'BK201: To check if it's all clear.
    
    'Figure out what the first email address is.
    sEmail = Range("D2").Value
    
    'Figure out where first group data starts.
    nStart = Range("D:D").Find(sEmail).Row
    
    'Figure out where first group data ends.
    nEnd = Range("D:D").Rows.Find(What:=sEmail, SearchDirection:=xlPrevious).Row
    
    'BK201: Set the target range.
    Set cRng = Range("G" & nStart & ":G" & nEnd)
    
    'BK201: Set a default value for aClr.
    aClr = True
    
    For Each cL In cRng
        'Similar to B and C.
        sName = cL.Offset(0, -5).Value & " " & cL.Offset(0, -4).Value
        If cL.Value = sEmail Then
            'Do nothing. Let the loop continue.
        Else
            aClr = False 'BK201: Oops. At least one entry isn't listed.
            rStr = rStr & sName & vbNewLine
        End If
    Next cL
    
    If aClr Then 'BK201: If all is clear...
        MsgBox "All clear!"
    Else 'BK201: Otherwise...
        rStr = "The unlisted names are:" & vbNewLine & vbNewLine & rStr
        rStr = rStr & vbNewLine & vbNewLine & "Please add their information before continuing."
        MsgBox rStr
    End If
    
    End Sub
    

    不过,这并没有到此结束,因为这只会对您列表中的一封电子邮件正常运行,并且该电子邮件也位于D2,这是nStart 无论如何都会默认的位置。因此,即使使用上面的代码,我的下一个建议是:最好在其他地方列出所有唯一电子邮件,然后对其进行迭代,sEmail 等于当前迭代的电子邮件字符串。

    如果这听起来不错,请告诉我们,以便我们可以相应地应用它。否则,此代码将在您当前的设置或请求中正常工作。 :)

    sEmail 的测试结果位于下面的M2 而不是D2

    大规模编辑:

    根据与 OP 的交流,以下内容应该可以解决问题。但是请注意,为了方便起见,我冒昧地假设所有团队负责人的唯一电子邮件列表都位于某个地方。根据需要修改代码。代码如下:

    Private Sub CheckIfLeadExists()
    
        'Dimension area.
        Dim wSht As Worksheet
        Dim rMem As Range
        Dim vList As Variant, vElement As Variant
        Dim lStart As Long, lEnd As Long
        Dim sEmail As String, sName As String, rStr As String
        Dim bClear As Boolean
    
        'Assignment area.
        Set wSht = ThisWorkbook.Sheets("Sheet1") 'Modify as necessary.
        vList = wSht.Range("J2:J4").Value 'Assign the unique e-mails to a variable.
        bClear = True 'Default value of boolean check for clear run.
    
        For Each vElement In vList 'Iterate over the e-mails.
            sEmail = vElement
            With wSht
                'Find the starting row for current e-mail of loop.
                lStart = .Columns("D").Find(sEmail).Row
                'Likewise, find the ending row for current e-mail of loop.
                lEnd = .Columns("D").Find(sEmail, SearchDirection:=xlPrevious).Row
                'Get the lead's name.
                sName = .Cells(lStart, 2).Value & " " & .Cells(lStart, 3).Value
                'Assign the member's area to a range.
                Set rMem = .Range("E" & lStart & ":G" & lEnd)
            End With
            'We now search this member's area for the current lead's e-mail.
            If Not rMem.Find(sEmail) Is Nothing Then
                'E-mail exists in member's area. Do nothing.
            Else
                bClear = False 'Oops. At least one entry isn't listed.
                rStr = rStr & sName & vbNewLine 'Add to string.
            End If
        Next vElement
    
        If bClear Then 'If all is clear...
            MsgBox "All clear!"
        Else 'Otherwise, list them all.
            rStr = "The unlisted names are:" & vbNewLine & vbNewLine & rStr
            rStr = rStr & vbNewLine & "Please add their information before continuing."
            MsgBox rStr
        End If
    
    End Sub
    

    结果截图:

    最后编辑(希望如此):

    以下代码考虑到事先没有列表。这将在 J 列中创建列表。

    Private Sub CheckIfLeadExists()
    
        'Dimension area.
        Dim wSht As Worksheet
        Dim rMem As Range
        Dim vList As Variant, vElement As Variant
        Dim lStart As Long, lEnd As Long, lRow As Long
        Dim sEmail As String, sName As String, rStr As String
        Dim bClear As Boolean
        Dim oDict As Object, vMails As Variant, vItem As Variant
        Dim lCount As Long
    
        'Assignment area.
        Set wSht = ThisWorkbook.Sheets("Sheet1") 'Modify as necessary.
    
        'Get first all the emails with duplicates. Modify as necessary.
        vMails = wSht.Range("D2:D" & Cells(Rows.Count, 4).End(xlUp).Row).Value
        'Create a dictionary.
        Set oDict = CreateObject("Scripting.Dictionary")
        With oDict
            For Each vItem In vMails
                If Not .Exists(vItem) And Not IsEmpty(vItem) Then
                    .Add vItem, Empty
                End If
            Next vItem
        End With
        'Copy unique list of e-mails to column J.
        lRow = oDict.Count
        wSht.Range("J2").Resize(lRow, 1).Value = Application.Transpose(oDict.Keys)
        vList = wSht.Range("J2:J" & lRow + 1).Value 'Assign the unique e-mails to a variable.
        bClear = True 'Default value of boolean check for clear run.
    
        For Each vElement In vList 'Iterate over the e-mails.
            sEmail = vElement
            With wSht
                'Find the starting row for current e-mail of loop.
                lStart = .Columns("D").Find(sEmail).Row
                'Likewise, find the ending row for current e-mail of loop.
                lEnd = .Columns("D").Find(sEmail, SearchDirection:=xlPrevious).Row
                'Get the lead's name.
                sName = .Cells(lStart, 2).Value & " " & .Cells(lStart, 3).Value
                'Assign the member's area to a range.
                Set rMem = .Range("E" & lStart & ":G" & lEnd)
            End With
            'We now search this member's area for the current lead's e-mail.
            If Not rMem.Find(sEmail) Is Nothing Then
                'E-mail exists in member's area. Do nothing.
            Else
                bClear = False 'Oops. At least one entry isn't listed.
                rStr = rStr & sName & vbNewLine 'Add to string.
            End If
        Next vElement
    
        If bClear Then 'If all is clear...
            MsgBox "All clear!"
        Else 'Otherwise, list them all.
            rStr = "The unlisted names are:" & vbNewLine & vbNewLine & rStr
            rStr = rStr & vbNewLine & "Please add their information before continuing."
            MsgBox rStr
        End If
    
    End Sub
    

    结果是一样的。希望这会有所帮助!

    后续编辑:

    在处理字典时,由于并非总是遇到只有一项的字典(至少根据我的经验),Transpose 通常是将键或项打印到范围的最佳方法。但是,由于字典中只有一项,因此无法将其打印出来(从不费心检查确切原因)。但是,遍历键或项目就可以了,并且应该会打印出那个唯一的键/项目。请参阅以下编辑。

    Private Sub CheckIfLeadExists()
    
        'Dimension area.
        Dim wSht As Worksheet
        Dim rMem As Range
        Dim vList As Variant, vElement As Variant
        Dim lStart As Long, lEnd As Long, lRow As Long
        Dim sEmail As String, sName As String, rStr As String
        Dim bClear As Boolean
        Dim oDict As Object, vMails As Variant, vItem As Variant
        Dim lCount As Long
    
        'Assignment area.
        Set wSht = ThisWorkbook.Sheets("Sheet5") 'Modify as necessary.
    
        'Get first all the emails with duplicates. Modify as necessary.
        vMails = wSht.Range("D2:D" & Cells(Rows.Count, 4).End(xlUp).Row).Value
        'Create a dictionary.
        Set oDict = CreateObject("Scripting.Dictionary")
        With oDict
            For Each vItem In vMails
                If Not .Exists(vItem) And Not IsEmpty(vItem) Then
                    .Add vItem, Empty
                End If
            Next vItem
        End With
        'Copy unique list of e-mails to column J.
        lRow = 2 '--Changed this.
        For Each Key In oDict.Keys '--Changed this as well.
            wSht.Range("J" & lRow).Value = Key
            lRow = lRow + 1
        Next Key
        vList = wSht.Range("J2:J" & lRow + 1).Value 'Assign the unique e-mails to a variable.
        bClear = True 'Default value of boolean check for clear run.
    
        For Each vElement In vList 'Iterate over the e-mails.
            sEmail = vElement
            With wSht
                'Find the starting row for current e-mail of loop.
                lStart = .Columns("D").Find(sEmail).Row
                'Likewise, find the ending row for current e-mail of loop.
                lEnd = .Columns("D").Find(sEmail, SearchDirection:=xlPrevious).Row
                'Get the lead's name.
                sName = .Cells(lStart, 2).Value & " " & .Cells(lStart, 3).Value
                'Assign the member's area to a range.
                Set rMem = .Range("E" & lStart & ":G" & lEnd)
            End With
            'We now search this member's area for the current lead's e-mail.
            If Not rMem.Find(sEmail) Is Nothing Then
                'E-mail exists in member's area. Do nothing.
            Else
                bClear = False 'Oops. At least one entry isn't listed.
                rStr = rStr & sName & vbNewLine 'Add to string.
            End If
        Next vElement
    
        If bClear Then 'If all is clear...
            MsgBox "All clear!"
        Else 'Otherwise, list them all.
            rStr = "The unlisted names are:" & vbNewLine & vbNewLine & rStr
            rStr = rStr & vbNewLine & "Please add their information before continuing."
            MsgBox rStr
        End If
    
    End Sub
    

    多组结果相同,只有一组时不会报错。

    如果这有帮助,请告诉我。

    【讨论】:

    • 消息框字符串列表很棒!!我在概念上试图弄清楚如何做到这一点时遇到了麻烦,但rStr = rStr &amp; sName &amp; vbNewLine 很棒而且超级聪明。
    • 抱歉不清楚。我没有 10 个代表,所以我还不能发布图片。从您发布的示例开始,BK201 应该只在 G 列中列出一次,对于它在 D 列中列出的每一行(如果有的话)。我需要的是如果它 not 列在 G 中,那就是应该弹出的名称。 BK201 也应始终具有与其关联的相同名称。然后在 D 列中有另一组 BK200,在 G 列中有不同的电子邮件,但 BK200 也应该列出(一次),然后继续。那有意义吗?我知道,这很愚蠢,但这就是我必须解决的问题......哈哈。
    • @jfkenne:在检查 G 列时,是否应该只在与 cRng 相同的范围内找到它?例如,如果我的cRngD5:D10,那么电子邮件应该只在G5:G10 中还是可以在G 中的任何位置?还有,第二。 G 中的电子邮件应该严格一个还是至少一个?
    • 它只能在cRng 中找到。该电子邮件可能会在 G 列中出现多次(在不同的组中),但我不一定关心这一点。用户输入数据的方式是在 E:G 列中该组的每个成员旁边的 B:D 列中列出组长的姓名,包括他们自己。领导者偶尔会忘记将自己添加到“成员”列(G 列)中,这就是进行质量检查的原因。因此,对于每个cRng,G 中的电子邮件应该严格 一个,但该电子邮件可能出现在 G 中的多个组中。这有意义吗?
    • @jfkenne:不需要开始一个新的话题——这就像我之前应该指出的一个小修改的请求代表。 ;) 检查上面的最后一个代码并检查标记为已更改的代码。你马上就会明白的。 ;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-17
    • 2015-08-12
    • 1970-01-01
    • 2020-12-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多