【问题标题】:Copy a range of values based on date in another column根据另一列中的日期复制一系列值
【发布时间】:2015-09-17 20:00:33
【问题描述】:

我正在尝试编写一个宏,它根据另一列中的相应日期复制一列中的一系列值。

例如,我需要复制 G 列中与 B 列中的日期相对应的值。对于 2015 年 9 月 18 日,我需要根据 2015 年 9 月 18 日的日期选择并复制 G 列中的范围来自 B 列。然后我需要为 9/19 做同样的事情,以此类推所有其他日期。然后我将它粘贴到其他几个页面,尽管这部分代码不包含在此处。

我在下面的尝试只检查 B 列中的日期,然后在 G 列中复制一个范围。我相信我需要一个 for 循环,但我不确定如何正确构建它以满足我的需要。

 If ActiveCell >= Date + 1 And ActiveCell <= Date + 7 Then

' Compare date on Day Sheet to sheet s and select cells in column G
' corresponding to that date

        x = ActiveCell
        ActiveWorkbook.Sheets("s").Activate
        Range("B2").Select

' If statement to check if dates match

            If ActiveCell = x Then
            Range("G2").Select
            ActiveCell.Offset(0, 5).Select
            Range("G2:G10").Copy
            Else
            End If

【问题讨论】:

  • 也许我误解了这种情况,但听起来很可疑,就像您的目标选项卡上的一些 VLookup 公式可以在没有 VBA 的情况下执行此操作。有什么特殊原因不能使用它们吗?
  • 这可能会做到,但它比这更复杂。我还需要代码来检查下一列 (C) 中的文本,以确定它对应的特定日期将进入工作表上的哪个单元格。例如,明天 9/18 会有一系列单元格。一旦确定了该范围,我将需要代码来识别 C 列中的文本并将其与另一张纸上的其他文本匹配以确定将值粘贴到何处。
  • 我很难想象我们正在努力实现的目标。你能告诉我们更多关于你的数据的结构方式吗? B 列是否是您试图循环以测试它们是否在明天和从今天起一周之间的一堆日期?看起来您将x 设置为等于ActiveCell,但之后您立即测试ActiveCell 是否等于x。似乎这将在 100% 的时间内评估为真实的。你能分步解释你想让它做什么吗?
  • 当然,让我先解释一下我在看什么,以便您更好地理解这一点,然后我可以解释我正在尝试采取的步骤。我每天从数据库中下载一个电子表格,其中日期在 B 列中分组在一起,帐户名称在 C 列中,金额在 F 列中。我在 G 列中创建一个公式来获取 F 列的百分比。有 9-12帐户对应于 B 列中的每个日期,因此会有几行代表 9/18,然后是几行代表 9/19,依此类推。
  • 我还有 36 张其他表标记为第 1 天 - 第 36 天,其中一些的日期与下载的表中的日期相匹配。在这些工作表中的每一个上,都有一个包含所有帐户名称的列,而旁边的列是我要从下载的工作表中粘贴相应日期和帐户名称的值的位置。我需要为每个日期和每个帐户名称执行此操作。现在我被困在日期上,下一步将是帐户名称。

标签: vba excel date for-loop


【解决方案1】:

哦,这太吓人了。我现在有一个几乎相同的任务——除了我的是从 SQL 导入到 Excel 的每月飞行日志,它必须将每天的工作时间转移到飞行员的个人工作表中。将“帐户”切换为“试点”,将“金额”切换为“飞行时间”,我们的项目完全一样。

我实际上只是在下面剪切并粘贴了我的代码,它会为您完成整个shabang。在 StackOverflow 上为他们解决某人的整个任务并不是很好的形式,但在这种情况下,仅粘贴一些过程似乎毫无意义。

对我来说最大的教训是只将 Excel 视为数据检索和数据显示界面。诀窍是创建自己的数据结构,将数据读入其中,根据需要操作/询问它们,然后在一切完成后将结果写入工作表。换句话说,避免像瘟疫一样的宏生成器!我宁愿怀疑您的复制单元格 x,y 粘贴到单元格 r,c 方法会将您带到与我相同的死胡同。我发现最好的方法是有一个Dictionary 的飞行员(为你负责),然后是一个内部的Dictionary 的航班日期(你的价值/日期)。然后,您只需为您的日程表的每个帐户测试一个帐户密钥和一个日期密钥。

要访问 Dictionary 对象,您需要引用 Microsoft Scripting Runtime(工具 -> 引用... -> 通过勾选复选框在列表中选择)。

您需要创建两个类 - 这些是您的数据字段。调用第一个cAccountFields,在类中加入如下代码:

Public AccountName As String
Public ActivityByDate As Dictionary
Public Sub Create(accName As String)
    Me.AccountName = accName
    Set Me.ActivityByDate = New Dictionary
End Sub

调用第二个cActivityFields并将以下代码添加到类中:

Public DateOf As Date
Public Value As Double
Public Sub Create(dat As Date, val As Double)
    Me.DateOf = dat
    Me.Value = val
End Sub

然后只需将以下代码添加到您的模块中。私有常量需要在模块级别(即页面顶部)声明。你可以使用这些来定义你的行和列引用——如果它们与飞行员的日志相匹配,那就太不可思议了:

Private Const DB_SHEET As String = "Sheet1"
Private Const DB_DATE_COL As String = "B"
Private Const DB_ACCOUNT_COL As String = "C"
Private Const DB_VALUE_COL As String = "G"
Private Const DB_ACCOUNT_START_ROW As Long = 1
Private Const DAY_DATE_ADDRESS As String = "A1"
Private Const DAY_ACCOUNT_COL As String = "A"
Private Const DAY_VALUE_COL As String = "B"
Private Const DAY_ACCOUNT_START_ROW As Long = 2


Public Sub ProcessData()
    Dim daySheets As Collection
    Dim accountsFromDB As Dictionary
    Dim account As cAccountFields
    Dim activity As cActivityFields
    Dim ws As Worksheet
    Dim dat As Date
    Dim accName As String
    Dim accValue As Double
    Dim endRow As Long
    Dim r As Long

    ' Create a Collection of the Day sheets
    Set daySheets = New Collection
    For Each ws In ThisWorkbook.Worksheets
        If Left(ws.Name, 4) = "Day " Then
            daySheets.Add ws
        End If
    Next

    ' Read the database sheet
    Set ws = ThisWorkbook.Worksheets(DB_SHEET)
    Set accountsFromDB = New Dictionary

    endRow = ws.Cells.Find(What:="*", _
                           After:=ws.Range("A1"), _
                           LookIn:=xlFormulas, _
                           LookAt:=xlPart, _
                           SearchOrder:=xlByRows, _
                           SearchDirection:=xlPrevious).Row

    For r = DB_ACCOUNT_START_ROW To endRow

        dat = ws.Cells(r, DB_DATE_COL).Value2
        accName = ws.Cells(r, DB_ACCOUNT_COL).Text
        accValue = ws.Cells(r, DB_VALUE_COL).Value2

        ' Add the account or retrieve it if it already exists.
        If Not accountsFromDB.Exists(accName) Then
            Set account = New cAccountFields
            account.Create accName
            accountsFromDB.Add key:=accName, Item:=account
        Else
            Set account = accountsFromDB.Item(accName)
        End If

        ' Add the value for a specific date.
        If Not account.ActivityByDate.Exists(dat) Then
            Set activity = New cActivityFields
            activity.Create dat, accValue
            account.ActivityByDate.Add key:=dat, Item:=activity
        Else
            ' If the same account and date occurs, then aggregate the values.
            Set activity = account.ActivityByDate(dat)
            activity.Value = activity.Value + accValue
        End If

    Next

    ' Populate the Day sheets
    For Each ws In daySheets

        dat = ws.Range(DAY_DATE_ADDRESS).Value2

        endRow = ws.Cells.Find(What:="*", _
                               After:=ws.Range("A1"), _
                               LookIn:=xlFormulas, _
                               LookAt:=xlPart, _
                               SearchOrder:=xlByRows, _
                               SearchDirection:=xlPrevious).Row

        For r = DAY_ACCOUNT_START_ROW To endRow

            accName = ws.Cells(r, DAY_ACCOUNT_COL).Text

            ' If account and value for this date exists then write the value
            If accountsFromDB.Exists(accName) Then
                Set account = accountsFromDB.Item(accName)
                If account.ActivityByDate.Exists(dat) Then
                    Set activity = account.ActivityByDate.Item(dat)
                    ws.Cells(r, DAY_VALUE_COL).Value = activity.Value
                End If
            End If

        Next

    Next

End Sub

在 OP Q 之后更新:

在模块级别添加额外的常量并酌情修改:

Private Const DB_BOOK As String = "Macro Test File.xlsx"
Private Const DAY_BOOK As String = "Macro Test File.xlsx"
Private Const INITIAL_SHEET As String = "Initial Revenue"
Private Const INITIAL_COL As String = "E"

然后使用这个代码:

Dim daySheets As Collection
Dim accountsFromDB As Dictionary
Dim account As cAccountFields
Dim activity As cActivityFields
Dim dbWb As Workbook
Dim dayWb As Workbook
Dim ws As Worksheet
Dim dat As Date
Dim accName As String
Dim accValue As Double
Dim endRow As Long
Dim r As Long

' Assign the workbook containing the database sheet
On Error Resume Next
Set dbWb = Workbooks(DB_BOOK)
On Error GoTo 0
If dbWb Is Nothing Then
    MsgBox "Please open " & DB_BOOK & " in this application and run this routine again."
    End
End If

' Assign the workbook containing the days sheets
On Error Resume Next
Set dayWb = Workbooks(DAY_BOOK)
On Error GoTo 0
If dayWb Is Nothing Then
    MsgBox "Please open " & DAY_BOOK & " in this application and run this routine again."
    End
End If


' Create a Collection of the Day sheets
Set daySheets = New Collection
For Each ws In dayWb.Worksheets
    If Left(ws.Name, 4) = "Day " Then
        daySheets.Add ws
    End If
Next

' Read the database sheet
Set ws = dbWb.Worksheets(DB_SHEET)
Set accountsFromDB = New Dictionary

endRow = ws.Cells.Find(What:="*", _
                       After:=ws.Range("A1"), _
                       LookIn:=xlFormulas, _
                       LookAt:=xlPart, _
                       SearchOrder:=xlByRows, _
                       SearchDirection:=xlPrevious).Row

For r = DB_ACCOUNT_START_ROW To endRow

    dat = ws.Cells(r, DB_DATE_COL).Value2
    accName = ws.Cells(r, DB_ACCOUNT_COL).Text
    accValue = ws.Cells(r, DB_VALUE_COL).Value2

    ' Add the account or retrieve it if it already exists.
    If Not accountsFromDB.Exists(accName) Then
        Set account = New cAccountFields
        account.Create accName
        accountsFromDB.Add Key:=accName, Item:=account
    Else
        Set account = accountsFromDB.Item(accName)
    End If

    ' Add the value for a specific date.
    If Not account.ActivityByDate.Exists(dat) Then
        Set activity = New cActivityFields
        activity.Create dat, accValue
        account.ActivityByDate.Add Key:=dat, Item:=activity
    Else
        ' If the same account and date occurs, then aggregate the values.
        Set activity = account.ActivityByDate(dat)
        activity.Value = activity.Value + accValue
    End If

Next

' Populate the Day sheets
For Each ws In daySheets

    dat = ws.Range(DAY_DATE_ADDRESS).Value2

    endRow = ws.Cells.Find(What:="*", _
                           After:=ws.Range("A1"), _
                           LookIn:=xlFormulas, _
                           LookAt:=xlPart, _
                           SearchOrder:=xlByRows, _
                           SearchDirection:=xlPrevious).Row

    For r = DAY_ACCOUNT_START_ROW To endRow

        ' Write the standard formula into the cell
        ws.Cells(r, DAY_VALUE_COL).Formula = "='" & INITIAL_SHEET & "'!" & _
                                             INITIAL_COL & CStr(r)

        accName = ws.Cells(r, DAY_ACCOUNT_COL).Text

        ' If account and value for this date exists then write the value
        If accountsFromDB.Exists(accName) Then
            Set account = accountsFromDB.Item(accName)
            If account.ActivityByDate.Exists(dat) Then
                Set activity = account.ActivityByDate.Item(dat)
                ws.Cells(r, DAY_VALUE_COL).Formula = ws.Cells(r, DAY_VALUE_COL).Formula & _
                                                     " + " & CStr(activity.Value)
            End If
        End If

    Next

Next

【讨论】:

  • 我之前没有使用过类,所以对它们不熟悉。我在网上看了一点,但我在设置它们时遇到了麻烦。我需要做什么才能正确设置课程?
  • 他们非常直截了当。您基本上是在创建自己的对象,该对象具有与任何其他对象一样的方法和属性。例如,在您的代码中,ActiveWorkbook 是一个对象的实例,Sheets 是它的一个属性。要设置班级,请在菜单栏上单击插入 -> 班级模块。在属性窗口中编辑器的左下方,您可以键入其Name。默认为Class1。右边是你输入代码的地方,就像任何模块一样。
  • 感谢您对课程的帮助。那是一件非常简单的事情。我现在收到错误“对象变量或未设置块变量”,当我单击调试时它指向代码的 endRow 部分。
  • 听起来好像 Worksheet 对象有问题。检查(在模块顶部)您是否已为常量 DB_SHEET 分配了工作表的名称。如果这是正确的,请将以下行放入您的代码中,运行它并告诉我它说了什么。 Debug.Print "ws Is Nothing = " &amp; (ws Is Nothing) &amp; ". Sheet is " &amp; ActiveSheet.Name。输出将位于“即时”窗口中编辑器的底部。
  • 你是对的,那个名字是不正确的。我纠正了它,现在它告诉我下标超出了 set ws 的范围。
猜你喜欢
  • 2015-07-11
  • 1970-01-01
  • 2021-06-16
  • 2020-06-22
  • 2021-07-13
  • 1970-01-01
  • 1970-01-01
  • 2019-12-01
  • 1970-01-01
相关资源
最近更新 更多