这是答案的第 2 部分,它介绍了解决方案并包含主例程。 第 3 部分包含子例程。 第 1 部分介绍了我在解决方案中使用的技术。
我的解决方案要求宏的工作簿包含两个工作表:一个用于错误,另一个用于合并数据。这些工作簿的名称被定义为常量,因此可以根据需要进行更改。
我创建了一些我认为与您下载的格式相匹配的 CSV 文件。一个典型的例子是:
1 Caution: Rates Have Not Been Adjusted For Patient Mix
2 St Anthony's Hospital
3 Jan 2013 - April 2013 Location Comparison Based on 6 Locations
4 CMS Qualified HCAHPS Data from All Service Lines
5 Communications about Medications Composite Results
6 Location,Jan 2013,Feb 2013,Mar 2013,Apr 2013,Composite Rate,Percentile
7 2E,70,72.22,64.62,81.82,72.17,49th
8 2S,60,62.22,54.62,71.82,62.17,39th
9 3N,78.57,83.33,66.67,NR,76.19,74th
10 3S,50,90,50,100,72.5,56th
11 4N,88.89,75,77.27,100,85.29,85th
12 ICU/PCU,72.73,50,80,100,75.68,54th
13
14 St Anthony's Hospital,73.5,73.28,67.89,84.21,74.72,59th
15 Vendor DB % Top Box,72.29,72.86,73.58,75.17,73.48
医院名称是真实的,但如果您感兴趣,那纯属巧合。我认为这些问题是正确的。位置和数据是虚构的。
我的代码彻底检查了 CSV 文件的格式,因为作者发现我在没有警告的情况下更改了此类文件的格式。总体变化可能会使宏崩溃,但细微的变化可能会在几个月内被忽视。
检查包括将第 3 行的日期范围与第 6 行的相同日期进行匹配。检查失败将导致错误工作表中出现一条消息。大多数检查只会导致该文件被拒绝。但是,两个 CSV 文件具有不同的日期范围是一个致命错误。
我计划根据我找到的数据创建合并的工作表。但是,您使用绝对地址将值复制到报告工作表中,因此您不希望数据根据 CSV 文件中包含的位置逐月移动。相反,我创建了一个固定的布局:
医院名称在第 1 列中。名称必须与医院的第一个位置相对,但对于后续行是可选的。毫无疑问,你会选择一种风格或另一种风格,但我为我的测试混合了风格。医院名称不是此处列出的医院名称的 CSV 文件将被拒绝。
位置在第 2 列。除了最后一行必须是总计/平均/汇总之外,位置顺序没有意义。我使用“总计”作为行标题,但您可以将其更改为任何内容。并非此处列出的每个位置都需要出现在 CSV 文件中,但如果 CSV 文件包含意外位置,它将被拒绝。
这些问题从 A3 开始列出。包含此处未列出的问题的 CSV 文件将被拒绝。
此工作表数据区的初始内容无关紧要,因为它们已被宏清除。
运行宏后,工作表可能如下所示。差距意味着我没有该医院/问题的测试数据:
我相信我的代码中的注释足以让您更改它以匹配 CSV 文件的格式(如果它们与我的猜测不同)。
此代码设计为在其自己的模块中。此代码不依赖演示宏中的任何内容。祝你好运。
Option Explicit
' Constants are a convenient way of defining values that will not change
' during a run of the macro. They are particular suitable for:
' (1) Replacing numbers by meaningful name. If column 5 is used for
' names, say, using ColName instead of 5 helps document the macro.
' (2) Values that are used in several places and might change. When they
' do change, one amendment is sufficient to fully update the macro.
Const ColConsolHosp As Long = 1 '\
Const ColConsolLocn As Long = 2 '| If the columns of the consolidate
Const ColConsolQuestFirst As Long = 3 '| worksheet are rearranged, these
Const ColConsolQuestLast As Long = 12 '/ valuesmust be ajusted to match.
Const ColErrorTime As Long = 1
Const ColErrorFile As Long = 2
Const ColErrorRow As Long = 3
Const ColErrorCol As Long = 4
Const ColErrorMsg As Long = 5
Const FmtDate As String = "dmmmyy"
Const FmtDateTime As String = "dmmmyy hh:mm"
Const WkShtNameConsol As String = "Consolidate" '\ Change if require output to
Const WkShtNameError As String = "Error" '/ different worksheets.
Sub Consolidate()
Dim CellValueConsol() As Variant ' Cell values from used range
' of consoldate worksheet
Dim ColSrcCompositeRate As Long ' Column hold composite rate
Dim ColConsolCrnt As Long
Dim DateStartAll As Date
Dim DateStartCrnt As Date
Dim DateEndAll As Date
Dim DateEndCrnt As Date
Dim ErrMsg As String
Dim FileCellValueSrc() As Variant ' Value of UsedRange for each CSV file
Dim FileError() As Boolean ' Error state for each file
Dim FileInxHosp() As Long ' Hospital for each CSV file
Dim FileInxQuest() As Long ' Question for each CSV file
Dim FileName() As String ' Name for each CSV file
Dim FileSysObj As Object
Dim FileObj As Object
Dim FolderObj As Object
Dim Found As Boolean
Dim HospName() As Variant ' Names of hospitals
Dim HospNameCrnt As String
Dim InxFileCrnt As Long
Dim InxFileDate As Long
Dim InxHospCrnt As Long
Dim InxLocnCrnt As Long
Dim InxQuestCrnt As Long
Dim Locn() As Variant ' Locations for each hosital
Dim NumCSVFile As Long ' Number of CSV files
Dim NumHosps As Long
Dim NumMonthsData As Long
Dim PathName As String
Dim Quest As Variant ' Array of questions
Dim RowConsolCrnt As Long
Dim RowConsolHospFirst() As Long ' First row for each hospital
' within consolidate worksheet
Dim RowConsolTemp As Long
Dim RowErrorCrnt As Long
Dim RowSrcCrnt As Long
Dim WkBkSrc As Workbook
Application.ScreenUpdating = False ' Reduces screen flash and increases speed
' Load CSV files
' ==============
PathName = Application.ThisWorkbook.Path
Set FileSysObj = CreateObject("Scripting.FileSystemObject")
Set FolderObj = FileSysObj.GetFolder(PathName)
NumCSVFile = 0
' Loop through files to count number of CSV files
For Each FileObj In FolderObj.Files
If LCase(Right(FileObj.Name, 4)) = ".csv" Then
NumCSVFile = NumCSVFile + 1
End If
Next
' Size arrays holding data per file
ReDim FileCellValueSrc(1 To NumCSVFile)
ReDim FileError(1 To NumCSVFile)
ReDim FileInxHosp(1 To NumCSVFile)
ReDim FileInxQuest(1 To NumCSVFile)
ReDim FileName(1 To NumCSVFile)
InxFileCrnt = 0
' Loop through files to save names and cell values.
For Each FileObj In FolderObj.Files
If LCase(Right(FileObj.Name, 4)) = ".csv" Then
InxFileCrnt = InxFileCrnt + 1
FileName(InxFileCrnt) = FileObj.Name
Set WkBkSrc = Workbooks.Open(PathName & "\" & FileObj.Name)
FileCellValueSrc(InxFileCrnt) = WkBkSrc.ActiveSheet.UsedRange
WkBkSrc.Close ' Close the CSV file
End If
Next
' Release resources
Set FileSysObj = Nothing
Set FolderObj = Nothing
' Extract controlling values from consolidate worksheet
' =====================================================
With Worksheets(WkShtNameConsol)
CellValueConsol = .UsedRange.Value
End With
'Debug.Print UBound(CellValueConsol, 1)
'Debug.Print UBound(CellValueConsol, 2)
' This code assumes a single header row consisting of:
' Hospital Location Question1 Question2 ...
' with appropriate names in the first two columns. The cells under the
' questions will all be overwritten.
' These columns are accessed using constants. Limited variation could
' be achieved within amending the code by changing constants.
' Execution will stop at a Debug.assert statement if the expression has a
' value of False. This is an easy way of confirming the worksheet is as
' expected. If a user might change the format of the output worksheet,
' this should be replaced by a MsgBox statement.
Debug.Assert CellValueConsol(1, ColConsolHosp) = "Hospital"
Debug.Assert CellValueConsol(1, ColConsolLocn) = "Location"
' Count number of hospitals.
' This code assumes all locations for a hospital are together and start at
' row 2. The hospital name may be repeated or may be blank on the second and
' subsequent rows for a hospital. That is, the following is acceptable:
' HospitalA X
' HospitalA Y
' HospitalA Z
' HospitalB X
' Y
' Z
' Count number of hospitals
HospNameCrnt = CellValueConsol(2, ColConsolHosp)
NumHosps = 1
For RowConsolCrnt = 3 To UBound(CellValueConsol, 1)
If CellValueConsol(RowConsolCrnt, ColConsolHosp) <> HospNameCrnt And _
CellValueConsol(RowConsolCrnt, ColConsolHosp) <> "" Then
NumHosps = NumHosps + 1
HospNameCrnt = CellValueConsol(RowConsolCrnt, ColConsolHosp)
End If
Next
'Debug.Print NumHosps
' Size HospName, Locn and RowConsolHospFirst for the number of hospitals
ReDim HospName(1 To NumHosps)
ReDim Locn(1 To NumHosps)
ReDim RowConsolHospFirst(1 To NumHosps)
' Load Hospital and Location arrays
InxHospCrnt = 1
HospNameCrnt = CellValueConsol(2, ColConsolHosp)
HospName(InxHospCrnt) = HospNameCrnt
RowConsolHospFirst(InxHospCrnt) = 2
For RowConsolCrnt = 3 To UBound(CellValueConsol, 1)
If CellValueConsol(RowConsolCrnt, ColConsolHosp) <> HospNameCrnt And _
CellValueConsol(RowConsolCrnt, ColConsolHosp) <> "" Then
' Load locations from worksheet to Location array
Call ExtractSubArray(CellValueConsol, Locn(InxHospCrnt), _
RowConsolHospFirst(InxHospCrnt), ColConsolLocn, _
RowConsolCrnt - 1, ColConsolLocn)
HospNameCrnt = CellValueConsol(RowConsolCrnt, ColConsolHosp)
InxHospCrnt = InxHospCrnt + 1
HospName(InxHospCrnt) = HospNameCrnt
RowConsolHospFirst(InxHospCrnt) = RowConsolCrnt
End If
Next
' Load locations for final hospital from worksheet to Location array
Call ExtractSubArray(CellValueConsol, Locn(InxHospCrnt), _
RowConsolHospFirst(InxHospCrnt), ColConsolLocn, _
UBound(CellValueConsol, 1), ColConsolLocn)
' Load questions
Call ExtractSubArray(CellValueConsol, Quest, _
1, ColConsolQuestFirst, _
1, ColConsolQuestLast)
' Clear data area of Consolidate worksheet
' =======================================
For RowConsolCrnt = 2 To UBound(CellValueConsol, 1)
For ColConsolCrnt = ColConsolQuestFirst To ColConsolQuestLast
CellValueConsol(RowConsolCrnt, ColConsolCrnt) = ""
Next
Next
' Prepare error worksheet
'========================
With Worksheets(WkShtNameError)
.Cells.EntireRow.Delete
.Cells(1, ColErrorTime).Value = "Time"
With .Cells(1, ColErrorFile)
.Value = "File"
.ColumnWidth = 71.71
End With
With .Cells(1, ColErrorRow)
.Value = "Row"
.HorizontalAlignment = xlRight
.ColumnWidth = 4
End With
With .Cells(1, ColErrorCol)
.Value = "Col"
.HorizontalAlignment = xlRight
.ColumnWidth = 4
End With
With .Cells(1, ColErrorMsg)
.Value = "Error"
.ColumnWidth = 71.71
End With
End With
RowErrorCrnt = 1
' Validate the CSV files and extract key information
' ==================================================
InxFileDate = -1 'Date range not yet found
NumMonthsData = 0
For InxFileCrnt = 1 To UBound(FileName)
FileError(InxFileCrnt) = False ' No error found for this file
If IsEmpty(FileCellValueSrc(InxFileCrnt)) Then
' The CSV file was empty
Call RecordError(FileName(InxFileCrnt), 0, 0, _
"Empty CSV file", RowErrorCrnt)
FileError(InxFileCrnt) = True ' This CSV file to be ignored
ElseIf VarType(FileCellValueSrc(InxFileCrnt)) = vbString Then
' The CSV file contained a single value
Call RecordError(FileName(InxFileCrnt), 0, 0, _
"CSV file contains a single string", RowErrorCrnt)
FileError(InxFileCrnt) = True ' This CSV file to be ignored
Else
' The only remaining format that could be returned from a range
' is an array
' Check that cells contain the values expected.
' Most checking code has been placed in subroutines. This keeps the code
' in the main routine clean and simple and allows the subroutines to be
' copied easily to new workbooks with macros performing similar tasks.
' Check Cell A1 = "Caution: Rates Have Not Been Adjusted For Patient Mix"
Call CheckCellValueSingle(FileName(InxFileCrnt), _
FileCellValueSrc(InxFileCrnt), FileError(InxFileCrnt), _
1, 1, _
"Caution: Rates Have Not Been Adjusted For Patient Mix", _
RowErrorCrnt)
' Check Cell A2 is a known hospital. Save InxHosp against file
Call CheckCellValueMultiple(FileName(InxFileCrnt), _
FileCellValueSrc(InxFileCrnt), _
FileError(InxFileCrnt), 2, 1, HospName, _
FileInxHosp(InxFileCrnt), RowErrorCrnt)
' Check Cell A3 is: Date - Date Location Comparison Based on N Locations
Call CheckDateRangeLocn(FileName(InxFileCrnt), _
FileCellValueSrc(InxFileCrnt), _
FileError(InxFileCrnt), 3, 1, _
DateStartCrnt, DateEndCrnt, RowErrorCrnt)
' Save DateStartCrnt and DatEndCrnt or check they are the same as the
' previously saved values
If InxFileDate = -1 Then
' First set of dates
DateStartAll = DateStartCrnt
DateEndAll = DateEndCrnt
InxFileDate = InxFileCrnt ' The first file found with these dates
Else
If DateStartAll = DateStartCrnt And DateEndAll = DateEndCrnt Then
' The date range for this CSV file matches those of previous files
Else
Call RecordError(FileName(InxFileCrnt), 3, 1, _
"**FATAL ERROR**: Date ranges do not match:" & vbLf & _
Format(DateStartAll, FmtDate) & " - " & _
Format(DateEndAll, FmtDate) & " " & _
FileName(InxFileDate) & vbLf & _
Format(DateStartCrnt, FmtDate) & " - " & _
Format(DateEndCrnt, FmtDate) & " " & _
FileName(InxFileCrnt), RowErrorCrnt)
' There are incompatible CSV files. This is a fatal error. Give up.
Exit Sub
End If
End If
' Check Cell A4 = "CMS Qualified HCAHPS Data from All Service Lines"
Call CheckCellValueSingle(FileName(InxFileCrnt), _
FileCellValueSrc(InxFileCrnt), _
FileError(InxFileCrnt), 4, 1, _
"CMS Qualified HCAHPS Data from All Service Lines", _
RowErrorCrnt)
' Check Cell A5 = Question " Composite Results"
If Not CheckBound(FileCellValueSrc(InxFileCrnt), 5, 1, ErrMsg) Then
Call RecordError(FileName(InxFileCrnt), 5, 1, ErrMsg, RowErrorCrnt)
FileError(InxFileCrnt) = True
Else
FileInxQuest(InxFileCrnt) = -1 ' No match against question
For InxQuestCrnt = 1 To UBound(Quest)
If FileCellValueSrc(InxFileCrnt)(5, 1) = _
Quest(InxQuestCrnt) & " Composite Results" Then
FileInxQuest(InxFileCrnt) = InxQuestCrnt
Exit For
End If
Next
If FileInxQuest(InxFileCrnt) = -1 Then
' No match found
FileError(InxFileCrnt) = True
Call RecordError(FileName(InxFileCrnt), 5, 1, """" & _
FileCellValueSrc(InxFileCrnt)(5, 1) & _
""" does not match a known question", RowErrorCrnt)
End If
End If
' Check cell A6 is: "Location"
Call CheckCellValueSingle(FileName(InxFileCrnt), _
FileCellValueSrc(InxFileCrnt), _
FileError(InxFileCrnt), 6, 1, "Location", _
RowErrorCrnt)
' Check cells B6 to X6 are the 1st day of month
' from DateStartAll to DateEndAll
Call CheckDateSequence(FileName(InxFileCrnt), _
FileCellValueSrc(InxFileCrnt), _
FileError(InxFileCrnt), 6, 2, DateStartAll, _
DateEndAll, "a", "m", RowErrorCrnt)
' Check cells Y6 is "Composite Rate"
If Not FileError(InxFileCrnt) Then
' The data range is not guaranteed until the file is error free
NumMonthsData = DateDiff("m", DateStartAll, DateEndAll) + 1
ColSrcCompositeRate = NumMonthsData + 2
Call CheckCellValueSingle(FileName(InxFileCrnt), _
FileCellValueSrc(InxFileCrnt), _
FileError(InxFileCrnt), 6, ColSrcCompositeRate, _
"Composite Rate", RowErrorCrnt)
End If
If Not FileError(InxFileCrnt) Then
' For row 7 down to the first empty column A, check column A contains
' a known location and ColSrcCompositeRate is numeric.
RowSrcCrnt = 7
InxHospCrnt = FileInxHosp(InxFileCrnt)
Do While True
If Not CheckBound(FileCellValueSrc(InxFileCrnt), _
RowSrcCrnt, 1, ErrMsg) Then
' Row not present
Call RecordError(FileName(InxFileCrnt), RowSrcCrnt, 1, _
ErrMsg, RowErrorCrnt)
FileError(InxFileCrnt) = True
Exit Do
End If
If Not CheckBound(FileCellValueSrc(InxFileCrnt), _
RowSrcCrnt, ColSrcCompositeRate, ErrMsg) Then
' Composite rate missing
Call RecordError(FileName(InxFileCrnt), RowSrcCrnt, _
ColSrcCompositeRate, ErrMsg, RowErrorCrnt)
FileError(InxFileCrnt) = True
Exit Do
ElseIf Not IsNumeric(FileCellValueSrc(InxFileCrnt)(RowSrcCrnt, _
ColSrcCompositeRate)) Then
' Composite rate is not numeric
Call RecordError(FileName(InxFileCrnt), RowSrcCrnt, _
ColSrcCompositeRate, "Composite rate """ & _
FileCellValueSrc(InxFileCrnt)(RowSrcCrnt, _
ColSrcCompositeRate) & """ is not numeric", _
RowErrorCrnt)
End If
If FileCellValueSrc(InxFileCrnt)(RowSrcCrnt, 1) = "" Then
' End of location list within file
Exit Do
End If
Found = False
For InxLocnCrnt = 1 To UBound(Locn(InxHospCrnt))
If FileCellValueSrc(InxFileCrnt)(RowSrcCrnt, 1) = _
Locn(InxHospCrnt)(InxLocnCrnt) Then
' Location from CSV file found in list from consolidate worksheet
Found = True
Exit For
End If
Next
If Not Found Then
Call RecordError(FileName(InxFileCrnt), RowSrcCrnt, 1, _
"Location """ & _
FileCellValueSrc(InxFileCrnt)(RowSrcCrnt, 1) & _
""" not found in list from worksheet """ & _
WkShtNameConsol & """", RowErrorCrnt)
FileError(InxFileCrnt) = True
End If
RowSrcCrnt = RowSrcCrnt + 1
Loop
End If
If Not FileError(InxFileCrnt) Then
' Row RowSrcCrnt will have a blank column 1
RowSrcCrnt = RowSrcCrnt + 1
' Check column A is the total line for the hospital
Call CheckCellValueSingle(FileName(InxFileCrnt), _
FileCellValueSrc(InxFileCrnt), _
FileError(InxFileCrnt), RowSrcCrnt, 1, _
HospName(FileInxHosp(InxFileCrnt)), _
RowErrorCrnt)
If Not CheckBound(FileCellValueSrc(InxFileCrnt), _
RowSrcCrnt, ColSrcCompositeRate, ErrMsg) Then
' Composite rate missing
Call RecordError(FileName(InxFileCrnt), RowSrcCrnt, _
ColSrcCompositeRate, ErrMsg, RowErrorCrnt)
FileError(InxFileCrnt) = True
ElseIf Not IsNumeric(FileCellValueSrc(InxFileCrnt)(RowSrcCrnt, _
ColSrcCompositeRate)) Then
' Composite rate is not numeric
Call RecordError(FileName(InxFileCrnt), RowSrcCrnt, _
ColSrcCompositeRate, "Composite rate """ & _
FileCellValueSrc(InxFileCrnt)(RowSrcCrnt, _
ColSrcCompositeRate) & """ is not numeric", _
RowErrorCrnt)
End If
End If
End If
Next InxFileCrnt
' If get here there has not been a fatal error although one or more
' individual files may have been rejected.
For InxFileCrnt = 1 To UBound(FileName)
If Not FileError(InxFileCrnt) Then
' No error has been found in this file
InxHospCrnt = FileInxHosp(InxFileCrnt)
InxQuestCrnt = FileInxQuest(InxFileCrnt)
ColConsolCrnt = 2 + InxQuestCrnt
RowSrcCrnt = 7 ' First location row
Do While True
If FileCellValueSrc(InxFileCrnt)(RowSrcCrnt, 1) = "" Then
' End of location list within file
Exit Do
End If
For InxLocnCrnt = 1 To UBound(Locn(InxHospCrnt))
If FileCellValueSrc(InxFileCrnt)(RowSrcCrnt, 1) = _
Locn(InxHospCrnt)(InxLocnCrnt) Then
' Location from CSV file found in list from consolidate worksheet
RowConsolCrnt = RowConsolHospFirst(InxHospCrnt) + InxLocnCrnt - 1
CellValueConsol(RowConsolCrnt, ColConsolCrnt) = _
FileCellValueSrc(InxFileCrnt)(RowSrcCrnt, ColSrcCompositeRate)
Exit For
End If
Next
RowSrcCrnt = RowSrcCrnt + 1
Loop
RowSrcCrnt = RowSrcCrnt + 1 ' Advance to hospital total line
' Assume last location row is for total
RowConsolCrnt = RowConsolHospFirst(InxHospCrnt) + _
UBound(Locn(InxHospCrnt)) - 1
CellValueConsol(RowConsolCrnt, ColConsolCrnt) = _
FileCellValueSrc(InxFileCrnt)(RowSrcCrnt, ColSrcCompositeRate)
End If
Next
' Write new values back to consolidate worksheet
' ==============================================
With Worksheets(WkShtNameConsol)
.UsedRange.Value = CellValueConsol
End With
End Sub