【问题标题】:How do I transform multiple columns to a stacked column set of three in VBA?如何在 VBA 中将多列转换为三个堆叠的列集?
【发布时间】:2021-11-24 15:34:11
【问题描述】:

我有 12 个月的数据(见图一),按日期-一月、日、小时、/ 日期-二月、日、小时等列,共 36 列。

我正在尝试将该工作表转换为新的工作表报告样式,它应该看起来堆叠起来(见图 2)它应该有四列,按姓名(员工的)、日期、日期、时间。

注意事项:

它必须是 reference - 所以当我在 sheet2 中的 自动更新 中更改 sheet1 中的时间时。没有复制粘贴。 (so like example(=b2)) (无需颠倒)。

如果我尝试每三列重复一次,请记住,每个月都有不同的天数,我们不希望空行。

我在考虑 vlookup 或索引功能,但似乎无法使其工作

【问题讨论】:

  • 这种设计不会有一个简洁的解决方案:在 D2 中写入 ̀ =E6` 并将其拉下直到4/30/2021 然后写入=H6 下方的单元格并将其拉下直到@ 987654327@。其他月份以此类推。
  • 年份和月份因工作表而异,因此需要是动态的并计算出一个月中有多少天。
  • 最快的方法可能是每月复制,然后在报告表上“粘贴链接”。如果您希望它自动化,则需要 VBA - 您可以尝试在录制宏时复制几个块,并将其用作起点。

标签: excel vba report transform


【解决方案1】:

开发单个动态公式参考 (MS 365)

“它必须是一个参考 - 所以当我在 sheet1 中更改小时数时,在 sheet2 中自动更新”

预先确定的固定结构让您有出路 获得想要的动态参考。 “逃生路线”只是为了

  • 通过公式重新计算日期值(col 2-Date 和 3-Day)作为数字序列而不是尝试引用原始日期 ,
  • 并将所有需要的列结果作为第一步包含在类似
   =CHOOSE({1,2,3,4},Employee,dt,dt,hours)

作为以下步骤,您需要将所有需要的列参数包含在定义所有列输入的公式容器 (LET()) 中, 部分通过引用命名单元,例如

  • Employee .. 例如"Bob Smith")
  • StartDate .. 例如2021 年 4 月 1 日(这里等于 44291
  • StartYear .. 例如2021
  • StartMonth.. 例如4

LET 函数(在 MS 365 中可用)允许在 结构化的方式也避免了一些冗余。

    =LET(data,Sheet1!$C$6:$AL$36,dt,SEQUENCE(366,1,StartDate),hours,INDEX(data,DAY(dt),(MONTH(dt)+(YEAR(dt)-StartYear)*12-StartMonth)*3+3),CHOOSE({1,2,3,4},Employee,dt,dt,hours))

通过将此公式输入任何目标单元格(例如在Sheet2 中),您将获得四列的动态溢出范围 自动显示原始时间的变化。

提示:考虑到闰年,我留给你完善Let 公式。

要最终为计算的日期序列(只是数字)获得正确的报告布局,您必须使用所需的日期格式来格式化第二和第三输出列, 例如"'m\/d""[$-409]ddd"

公式部分概览

其中包含换行符以提高可读性

 =LET(
      data,  Sheet1!$C$6:$AL$36,
      dt,    SEQUENCE(366,1,StartDate),
      hours, INDEX(data,DAY(dt),(MONTH(dt)+(YEAR(dt)-StartYear)*12-StartMonth)*3+3),
      CHOOSE({1,2,3,4},Employee,dt,dt,hours)
     )

【讨论】:

    【解决方案2】:

    正如@PEH 在评论中正确提到的那样,对于更改月份长度您的要求没有“整洁的解决方案”“它必须是一个参考 - 所以当我在 sheet2 中自动更新 sheet1 中的小时数时"

    没有直接引用的 VBA 启动

    (c.f. ►2nd post developmenting a single dynamic formula reference)

    由于您的 31 x 36 数据单元范围的固定结构,您可以,但是

    • 提供一个 具有 31*12 和 4 (Name,Date,Day,Hours) 的报告数组,
    • 您填写员工姓名 (col.1), 计算的 日期(假设:字符串!)范围从 1 到最大值。 31 天(第 2 栏和第 3 栏), 以及从源中按列读取的小时
    • 并写回任何想要的目标。

    调用示例

    根据您的需要更改工作表指示。

    Sub WriteReport()
    'A) create report
        Dim report As Variant
        report = getReport("Bob Smith", ThisWorkbook.Worksheets("Sheet1"))
    'B) write report to any wanted target
        With Sheet2            
        .Range("A1".resize(1,4) = split("Name,Date,Day,Hours", ",")
        .Range("A2").Resize(UBound(report), UBound(report, 2)) = report
        End With
    End Sub
     
    

    帮助功能getReport()

    Function getReport(ByVal employee As String, _
         SourceSheet As Worksheet, _
         Optional StartYear As Long = 2021, _
         Optional startMonth As Long = 4)
    '0) get start dates for e.g. 12 months via  help function getDates()
        Const MonthsCount As Long = 12
        Dim datearr: datearr = getDates(DateSerial(StartYear, startMonth, 1), MonthsCount)
    '1) define source range
        Dim rng As Range
        Set rng = SourceSheet.Range("A6").Resize(31, 3 * MonthsCount)
    '2) define 1-based 2-dim report array comprising 31 x 4 elements
        Dim report
        ReDim report(1 To MonthsCount * 31, 1 To 4)
    '3) add calculated dates and add monthly hours to report array
        Dim mth As Long, d As Long, cnt As Long
        For mth = 1 To MonthsCount
            'get monthly hours as 2-dim array (1 column each)
            Dim monthlyHours: monthlyHours = rng.Columns(mth * 3 + 2).Value
            For d = 1 To ultimo(datearr(mth))
                cnt = cnt + 1
                report(cnt, 1) = employee
                report(cnt, 2) = Application.Text(datearr(mth) + d - 1, "'m\/d")      ' force date string
                report(cnt, 3) = Application.Text(datearr(mth) + d - 1, "[$-409]ddd") ' force EN-US vers.
                report(cnt, 4) = monthlyHours(d, 1)
            Next d
        Next
    '4) return function result
        getReport = report
    End Function
    
    

    帮助功能getDates()

    返回每个月开始日期的 1-dim 数组

    Function getDates(dt As Date, Optional MonthsCount As Long = 12)
    'Purpose: get 1-dim array of last 12 months dates 
    'a) get start date
        Dim EndDate As Date: EndDate = DateAdd("m", MonthsCount, dt)
        Dim yrs As Long:     yrs = Year(EndDate) - Year(dt)
    'b) get column numbers representing a months sequence
        Dim cols As String
        cols = Split(Cells(, Month(dt)).Address, "$")(1)
        cols = cols & ":" & Split(Cells(, Month(EndDate) - 1 + Abs(yrs * 12)).Address, "$")(1)
    'c) evaluate dates
        getDates = Evaluate("Date(" & Year(dt) & _
            ",Column(" & cols & "),1)")
    End Function
    

    帮助功能ultimo()

    计算给定月份日期的最后一天(范围从 28 到 31)。 这可以使用零 (0) 作为理论日输入和函数 getSerial() 中的最后一个参数 如果申请下个月(月+1)。

    Function ultimo(ByVal dt) As Long
    'Purp.: return last day of month
            ultimo = Day(DateSerial(Year(dt), Month(dt) + 1, 0))
    End Function
    

    【讨论】:

    • 请注意,OP 需要报告中的公式(“它必须是一个参考 - 所以当我在 sheet2 中自动更新工作表中的小时数时”)。跨度>
    • 感谢您的提示,这当然会改变游戏规则。 @PEH
    • 是的,这就是为什么我评论说如果你真的必须链接这些单元格,就不会有 "整洁的解决方案" 并且月份位置可以改变(根据OP)那是一团糟。我的意思是,如果我们重新设计原始表格,可能会有一个解决方案(我做了类似的事情),但这太远了,无法给出一个好的答案)。
    • ... 并且不知道 OP 的实际逻辑如何进行。再次感谢。 - 不过,我会留下我的答案作为替代展示一些有用的方法。 @PEH
    • Fyi 发布了一个 MS 365 方法,在重新计算 LET 公式中的日期而不是参考原始数据时,一次显示溢出范围内所有需要的列。 - 感谢@PEH 的宝贵反馈
    【解决方案3】:

    我最终使用了这个公式,它奏效了。

    firstDate = DateValue("4/1/2021")
    secondDate = DateValue("4/1/2024")
    n = DateDiff("d", firstDate, secondDate)
    sc = Sheets.Count
    scd = sc - 3
    datar = "$A$1:$G$" & scd * n + 1
    
    
    For c = sc - 1 To 3 Step -1
        q = Sheets(c).Name
        Sheets("Report").Cells(Rows.Count, 2).End(xlUp).Offset(1, 0).Formula2R1C1 = "=SEQUENCE(DAYS(""4/1/2024"",""4/1/2021""),,""4/1/2021"")"
        Sheets("Report").Cells(Rows.Count, 3).End(xlUp).Offset(1, 0).Resize(n).Formula2R1C1 = "=TEXT(INDIRECT(""RC[-1]"",0),""ddd"")"
        Sheets("Report").Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Resize(n).Formula2R1C1 = Sheets(c).Range("$K$1")
        Sheets("Report").Cells(Rows.Count, 1).End(xlUp).Offset(-n + 1, 0).Resize(n).Select
        Selection.Hyperlinks.Add Anchor:=Selection, Address:="", SubAddress:="'" & q & "'" & "!$A$1"
        Sheets("Report").Cells(Rows.Count, 4).End(xlUp).Offset(1, 0).Resize(n).Formula2R1C1 = "=INDEX(" & q & "!R6C3:R114C38,IF(LARGE((" & q & "!R6C3:R114C38=RC2)*ROW(" & q & "!R6C3:R114C38),1),LARGE((" & q & "!R6C3:R114C38=RC2)*ROW(" & q & "!R6C3:R114C38),1)-5,""""),IFERROR(MATCH(RC2,INDEX(" & q & "!R6C3:R114C38,IF(LARGE((" & q & "!R6C3:R114C38=RC2)*ROW(" & q & "!R6C3:R114C38),1),LARGE((" & q & "!R6C3:R114C38=RC2)*ROW(" & q & "!R6C3:R114C38),1)-5,""""),0),0),"""")+2)"
       
    

    【讨论】:

      猜你喜欢
      • 2019-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-14
      • 1970-01-01
      相关资源
      最近更新 更多