Copy data from a variable table - excel

I have been trying to do a report and creating a macro to copy the data from one file to another.
I cannot figure out how to copy the data since the table I need to get my data from varies.
Example one:
What I need to copy is what is below the Alarm text.
But in, example one, I have no critical alarms but there are files that may have. Same applies to major/minor/warning.
The max of lines below the Alarm text are 3, but I can have 1/2/3 or even none.
In example 2, I have no data.
Here I have 2 critical and 3 on all other categories.
I know this may be a weird question, but I have no idea in how to find the these values, since they may vary so much.
All help is appreciated
Here is the code i have, but i am missing the important part,
Sub Copy()
Dim wbOpen As Workbook
Dim wbMe As Workbook
Dim vals As Variant
Set wbMe = ThisWorkbook
Set wbOpen = Workbooks.Open("C:\XXX\Core")
'MSS
vals = wbOpen.Sheets("MSS02NZF").Range("A2:B260").Copy
wbMe.Sheets("MSS02NZF").Range("B5").PasteSpecial Paste:=xlPasteValuesAndNumberFormats, _
Operation:=xlNone, SkipBlanks:=False, Transpose:=False
' wbOpen.Sheets(1).Range("A2:B260").Copy
' wbMe.Sheets(1).Range("B5").PasteSpecial Paste:=xlPasteValuesAndNumberFormats, _
Operation:=xlNone, SkipBlanks:=False, Transpose:=False
'MME
vals = wbOpen.Sheets("MME01NZF").Range("A2:H260").Copy
wbMe.Sheets("MME01NZF").Range("B5").PasteSpecial Paste:=xlPasteValuesAndNumberFormats, _
Operation:=xlNone, SkipBlanks:=False, Transpose:=False
'CSCF
vals = wbOpen.Sheets("CSCF").Range("A2:H2060").Copy
wbMe.Sheets("CSCF").Range("B5").PasteSpecial Paste:=xlPasteValuesAndNumberFormats, _
Operation:=xlNone, SkipBlanks:=False, Transpose:=False

Modify to your own need; code is using A:B as your source, and pastes the results in C:D.
Sub test()
Dim lrow As Long, alarmRow() As Long, alarmEnd() As Long
Dim count As Long, count2 As Long, rowcount As Long
ReDim alarmRow(1 To Application.CountIf(Range("A:A"), "Alarm"))
ReDim alarmEnd(1 To UBound(alarmRow))
With Worksheets("Sheet4") 'Change this to the Sheetname of your source.
lrow = .Cells(Rows.count, 1).End(xlUp).Row
For x = 1 To lrow Step 1
If .Range("A" & x).Value = "Alarm" Then 'Change "A" column to where your source data is.
count = count + 1
alarmRow(count) = x + 1
ElseIf .Range("A" & x).Value = "" Then 'Change "A" column to where your source data is.
count2 = count2 + 1
alarmEnd(count2) = x
End If
alarmEnd(UBound(alarmEnd)) = lrow
Next
For x = 1 To UBound(alarmRow) Step 1
lrow = .Cells(Rows.count, 3).End(xlUp).Row + 1
rowcount = alarmEnd(x) - alarmRow(x)
.Range("C" & lrow & ":D" & lrow + rowcount).Value = .Range("A" & alarmRow(x) & ":B" & alarmEnd(x)).Value ' Change A/B to where your source data is, and C/D to where you want to put the list.
Next
End With
End Sub
It's a bit of a mess, but here's how it works:
It'll look at the list where the word "Alarm" is. Once it finds it, the row number the word is in is registered to an Array. The row of the blank space is also taken to another array. This will serve as the range when copying the data.

Array of Arrays feat. 3-dimensional Jagged Arrays
Option Explicit
'*******************************************************************************
' Purpose: If not open, opens a specified workbook and pastes specific data
' found in two columns from several worksheets into a range specified
' by a cell in worksheets with the same name in this workbook.
'*******************************************************************************
Sub CopyPasteArray()
'***************************************
' List of Worksheet Names in Both Workbooks
Const cStrWsName As String = "MSS02NZF,MME01NZF,CSCF"
' Separator in List of Names of Worksheets in Both Workbooks
Const cStrSplit As String = ","
' Path of Workbook to Be Copied From
Const cStrSourcePath As String = "C:\XXX"
' Name of Workbook to Be Copied From
Const cStrSourceName As String = "Core.xls"
' Address of First Row Range to Be Copied From
Const cStrSourceFirst As String = "A2:B2"
' Target Top Cell Address to Be Pasted Into
Const cStrTopCell As String = "B5"
' Search String
Const cStrSearch As String = "Alarm"
' Target Columns
Const cIntTargetCols As Integer = 2 ' Change to 3 to include Type of Error.
'***************************************
Dim objWbSource As Workbook ' Source Workbook
Dim vntWsName As Variant ' Worksheet Names Array
Dim vntSourceAA As Variant ' Source Array of Arrays
Dim vntTargetAA As Variant ' Target Array of Arrays
Dim vntTargetRows As Variant ' Each Target Array Rows Array
Dim vntTarget As Variant ' Each Target Array
Dim blnFound As Boolean ' Source Workbook Open Checker
Dim lngRow As Long ' Source Array Arrays Rows Counter
Dim intCol As Integer ' Source Array Arrays Columns Counter
Dim intArr As Integer ' Worksheets and Arrays Counter
Dim lngCount As Long ' Critical Data Counter
Dim lngCount2 As Long ' Critical Data Next Row Counter
Dim strPasteCell As String
'***************************************
' Paste list of worksheets names into Worksheet Names Array.
vntWsName = Split(cStrWsName, cStrSplit)
'***************************************
' Check if Source Workbook is open.
For Each objWbSource In Workbooks
If objWbSource.Name = cStrSourceName Then
Set objWbSource = Workbooks(cStrSourceName)
blnFound = True ' Workbook is open.
Exit For ' Stop checking.
End If
Next
' If Source Workbook is not open, open it.
If blnFound = False Then
Set objWbSource = Workbooks.Open(cStrSourcePath & "\" & cStrSourceName)
End If
'***************************************
' Paste data from Source Workbook into Source Array of Arrays.
ReDim vntSourceAA(UBound(vntWsName))
For intArr = 0 To UBound(vntWsName)
With objWbSource.Worksheets(vntWsName(intArr))
vntSourceAA(intArr) = _
.Range( _
.Range(cStrSourceFirst).Cells(1, 1) _
, .Cells( _
.Range( _
.Cells(1, .Range(cStrSourceFirst).Column) _
, .Cells(Rows.Count, .Range(cStrSourceFirst).Column _
+ .Range(cStrSourceFirst).Columns.Count - 1)) _
.Find(What:="*", _
After:=.Range(cStrSourceFirst).Cells(1, 1), _
LookIn:=xlFormulas, Lookat:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlPrevious _
).Row _
, .Range(cStrSourceFirst).Column _
+ .Range(cStrSourceFirst).Columns.Count - 1 _
) _
).Value2
End With
Next
' The Source Array of Arrays is a 3-dimensional (jagged) array containing
' a 0-based 1-dimensional array containing an 'UBound(vntWsName)' number of
' 1-based 2-dimensional arrays.
'***************************************
' Count the number of critical data rows to determine size
' of each Target Array.
ReDim vntTargetRows(UBound(vntWsName))
For intArr = 0 To UBound(vntSourceAA)
For lngRow = 1 To UBound(vntSourceAA(intArr), 1)
If vntSourceAA(intArr)(lngRow, 1) = cStrSearch Then
For lngCount2 = lngRow + 1 To UBound(vntSourceAA(intArr), 1)
If vntSourceAA(intArr)(lngCount2, 1) <> "" Then
' Debug.Print vntSourceAA(intArr)(lngCount2, 1)
lngCount = lngCount + 1
lngRow = lngRow + 1
Else
Exit For
End If
Next
End If
Next
vntTargetRows(intArr) = lngCount
lngCount = 0
Next
'***************************************
' Copy critical data into each Target Array and paste it into
' Target Array of Arrays.
ReDim vntTargetAA(UBound(vntWsName))
For intArr = 0 To UBound(vntSourceAA)
ReDim vntTarget(1 To vntTargetRows(intArr), 1 To cIntTargetCols)
For lngRow = 1 To UBound(vntSourceAA(intArr), 1)
If vntSourceAA(intArr)(lngRow, 1) = cStrSearch Then
If cIntTargetCols = 3 Then
lngCount = lngCount + 1
vntTarget(lngCount, 1) = vntSourceAA(intArr)(lngRow - 1, 1)
lngCount = lngCount - 1
End If
For lngCount2 = lngRow + 1 To UBound(vntSourceAA(intArr), 1)
If vntSourceAA(intArr)(lngCount2, 1) <> "" Then
' Debug.Print vntSourceAA(intArr)(lngCount2, 1)
lngCount = lngCount + 1
vntTarget(lngCount, cIntTargetCols - 1) _
= vntSourceAA(intArr)(lngCount2, 1)
vntTarget(lngCount, cIntTargetCols) _
= vntSourceAA(intArr)(lngCount2, 2)
lngRow = lngRow + 1
Else
Exit For
End If
Next
End If
Next
vntTargetAA(intArr) = vntTarget
lngCount = 0
Next
'***************************************
' Clean up
Erase vntTarget
Erase vntTargetRows
Erase vntSourceAA
'***************************************
' Paste each Target Array into each of this workbook's worksheet's ranges,
' which are starting at the specified cell (cStrTopCell) if no data is below,
' or else at the first empty cell found searching from the bottom.
For intArr = 0 To UBound(vntWsName)
With ThisWorkbook.Worksheets(vntWsName(intArr))
If .Cells(Rows.Count, .Range(cStrTopCell).Column + cIntTargetCols - 2) _
.End(xlUp).Row = 1 Then
' No data in column
strPasteCell = cStrTopCell
Else
' Find first empty cell searching from bottom.
strPasteCell = _
.Cells( _
.Range( _
.Cells(1, .Range(cStrTopCell).Column) _
, .Cells(Rows.Count, .Range(cStrTopCell).Column _
+ cIntTargetCols - 1)) _
.Find(What:="*", _
After:=.Range(cStrTopCell).Cells(1, 1), _
LookIn:=xlFormulas, Lookat:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlPrevious _
).Row + 1 _
, .Range(cStrTopCell).Column _
).Address
' First empty cell is above Target Top Cell Address.
If Range(strPasteCell).Row < Range(cStrTopCell).Row Then _
strPasteCell = cStrTopCell
End If
' Paste into range.
.Range(strPasteCell).Resize( _
UBound(vntTargetAA(intArr)) _
, _
UBound(vntTargetAA(intArr), 2) _
) = vntTargetAA(intArr)
End With
Next
'***************************************
' Clean up
Erase vntTargetAA
Erase vntWsName
Set objWbSource = Nothing
End Sub
'*******************************************************************************

Related

Looping Through 2 Columns & Copying 2nd Column's Data Under the First

I am trying to create a list with 2 columns by placing the values from the 2nd column under the first on a new tab. In my screenshot I have column A "Data 1" and column B "Data 2". Each value under Data 1 has a corresponding value under Data 2. I am trying to make it look like the Second Tab column where the value under Data 1 is copied over first then Data 2 is Copied underneath. There are blanks in between values so im trying to figure out a way to capture all the data excluding the blanks so its 1 organized list. I have tried the following so far but i cant figure it out:
Sub MoveData()
Dim wb As Workbook: Set wb = ThisWorkbook
For i = 1 To 15
wb.Sheets("Sheet1").Range("A2:A" & i).Copy Destination:=wb.Sheets("Sheet2").Range("A1")
wb.Sheets("Sheet1").Range("A2:A" & i).Offset(0, 1).Copy _
Destination:=wb.Sheets("Sheet2").Range("A2" & lastrow).Offset(1, 0)
wb.Sheets("Sheet1").Range("A2:A" & i).Offset(0, 1).Copy _
Destination:=wb.Sheets("Sheet2").Range("A2:A" & i).Offset(1, 0)
Next i
End Sub
With the help of the following function you will find the last non empty row in column 1
Function FindLastRow(rg As Range) As Long
On Error GoTo EH
FindLastRow = rg.Find("*", , Lookat:=xlPart, LookIn:=xlFormulas _
, searchorder:=xlByRows, searchdirection:=xlPrevious).Row
Exit Function
EH:
FindLastRow = rg.Cells(1, 1).Row
End Function
Then you can copy the data into worksheet 2 with the following code
Sub pasteData()
Dim wks1 As Worksheet
Set wks1 = Worksheets("Sheet1")
Dim lastRow As Long
lastRow = FindLastRow(wks1.Columns(1)) ' last non empty row in column 1
Dim rg As Range
Set rg = wks1.Range("A1:B" & lastRow) 'range with the data in question
Dim vdat As Variant
vdat = rg.Value ' copy the data into an arry
' dim array which is big enough for the result
Dim rDat As Variant
ReDim rDat(0 To 2 * lastRow)
' copy the data from the 2-dim array into 1-dim array
Dim i As Long, j As Long
For i = LBound(vdat) To UBound(vdat)
' copy only data where the first column contains data
If Len(vdat(i, 1)) > 0 Then
rDat(j) = vdat(i, 1)
rDat(j + 1) = vdat(i, 2)
j = j + 2
End If
Next i
Dim wks2 As Worksheet
Set wks2 = Worksheets("Sheet2")
' prepare the second range (bigger than needed but does not harm)
Set rg = wks2.Range("A1:A" & 2 * lastRow)
' copy the data into the second sheet
rg = WorksheetFunction.Transpose(rDat)
End Sub

How to increment by 1 a loop in Visual Basic for Application

My first question on Stackoverflow, and a newbe, so please bare with me.
I am trying to sort out an excel sheet with 411,278 rows, about stock market data.
My code is as follows:
Sub Macro1()
'
' Macro1 Macro
'
Range("C6:C1577").Select
Selection.Copy
Range("D5").Select
Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=True
Range("B6:C1577").Select
Range("C6").Activate
Application.CutCopyMode = False
Selection.Delete Shift:=xlUp
End Sub
I am trying to insert the above code in a loop that will increment all the numbers within the loop by 1.
For example (the next phase in the loop will be):
Sub Macro1()
'
' Macro1 Macro
'
Range("C7:C1578").Select
Selection.Copy
Range("D6").Select
Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=True
Range("B7:C1578").Select
Range("C7").Activate
Application.CutCopyMode = False
Selection.Delete Shift:=xlUp
End Sub
Thanks!
A Data Transpose
The following will copy only the values (no formatting or formulas).
Adjust the values in the constants section. Note that you can choose a different worksheet for the result to leave your source data intact.
The Code
Option Explicit
Sub transposeValues()
' Define constants.
Const srcName As String = "Sheet1"
Const srcColumns As String = "B:D"
Const srcFirstRow As Long = 5
Const dstName As String = "Sheet1"
Const dstFirstCell As String = "B5"
Const dCount As Long = 1573
' Define workbook.
Dim wb As Workbook: Set wb = ThisWorkbook
' Define Source Range.
Dim rng As Range
With wb.Worksheets(srcName).Columns(srcColumns)
Set rng = .Resize(.Worksheet.Rows.Count - srcFirstRow + 1) _
.Offset(srcFirstRow - 1).Find( _
What:="*", _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious)
If rng Is Nothing Then
Exit Sub
End If
Set rng = .Resize(rng.Row - srcFirstRow + 1).Offset(srcFirstRow - 1)
End With
' Write values from Source Range to Data Array.
Dim Data As Variant: Data = rng.Value
' Define Result Array.
Dim srCount As Long: srCount = UBound(Data)
Dim Remainder As Long: Remainder = srCount Mod dCount
Dim drCount As Long
If Remainder = 0 Then
drCount = srCount / dCount
Else
drCount = Int(srCount / dCount) + 1
End If
Dim dcCount As Long: dcCount = dCount + 1
Dim Result As Variant: ReDim Result(1 To drCount, 1 To dcCount)
' Declare counters.
Dim i As Long, j As Long, k As Long
' Write values from Data Array to Result Array.
If drCount = 1 And Remainder > 0 Then
i = 1
Else
For i = 1 To drCount + (Remainder <> 0) * 1 ' In VBA 'True = -1'.
k = k + 1
For j = 1 To 2
Result(i, j) = Data(k, j)
Next j
For j = 3 To dcCount
k = k + 1
Result(i, j) = Data(k, 2)
Next j
Next i
End If
' Write the remainder of values in Data Array to Result Array.
If Remainder > 0 Then
k = k + 1
For j = 1 To 2
Result(i, j) = Data(k, j)
Next j
If Remainder > 1 Then
For j = 3 To 1 + Remainder
k = k + 1
Result(i, j) = Data(k, 2)
Next j
End If
End If
' Write values from Result Array to Destination Range.
With wb.Worksheets(dstName).Range(dstFirstCell).Resize(, dcCount)
.Resize(.Worksheet.Rows.Count - .Row + 1).ClearContents
.Resize(drCount).Value = Result
End With
End Sub
Please do not try this code without taking precautions.
Sub Macro1()
Dim Rng As Range
Dim Rstart As Long ' start of range row
Dim Rend As Long ' end of range row
Dim Target As Range ' destination of copy action
Dim DelRng As Range ' range to delete
Rstart = 6
Rend = 1577
Application.ScreenUpdating = False
Do
Set Rng = Range(Cells(Rstart, "C"), Cells(Rend, "C"))
Set Target = Cells(Rstart - 1, "D")
Set DelRng = Range(Cells(Rstart, "B"), Cells(Rend, "C"))
Rng.Copy
TargetPasteSpecial Paste:=xlPasteAll, _
Operation:=xlNone, _
SkipBlanks:=False, _
Transpose:=True
DelRng.Delete Shift:=xlUp
Rstart = Rstart + 1
Loop Until Rstart = Rend
With Application
.CutCopyMode = False
.ScreenUpdating = False
End With
End Sub
Primary precaution to take is to understand the code. Start and End rows are specified at the top. They are used to set 3 ranges in columns C, D and B:C. Initially, they are precisely your 3 ranges Range("C6:C1577"), Range("D5"), Range("B6:C1577"). The code copies from the first range to the second and then deletes the third. It does so without selecting anything because once VBA is told where a range is it doesn't need to select it.
You will notice that there is no equivalent of your Range("C7").Activate. That's because C7 is within the range Range("B6:C1577") which is earmarked for destruction. It makes no difference which cell in it is active. However, I have some doubt about the deletion and whether you actually meant to delete only this cell (which your code doesn't do). Please check my code against your intentions in this regard.
Now the critical part. That's the loop. On each iteration the 3 ranges are moved one row down. The line of code requiring your attention is this one.
Loop Until Rng.Row = Rend
The loop will continue until the first row of the first range is equal to Rend. That means that there will be 1571 loops - probably enough time to have a coffee and a chat even while screen updating is turned off. But even this fearsome number is definitely wrong. Your question gives no clue as to your needs but I guess it should be like Loop Until Rng.Row = (411278 - Rend), give or take 1. I didn't want to enter into an argument about this final, last row (which tends to be omitted in loops) while not believing that your number of rows is invariable. I think it should be replaced with a formula that finds the last existing row in a particular column.
However, your question was how to advance the ranges. My code does that beautifully. Enjoy the show.

Check if all column values exists in another list

The excel vba macro I have created goes through an entire column and searches each value in the column against another column found on another worksheet. I have a T/F column where I mark down "T" if it is found, "F" if it is not found. However, I feel like the way I am doing it might not be very efficient, as the macro takes about 30 minutes to go through 31,000 rows of values to be searched from another column with about 27,000 number of values.
For a simple illustration, I have included a few images which explains what the macro does.
Initially the T/F column will be empty. Only after executing the macro, would it be filled. I loop through each row in column A and try to find the value against the SearchCol in the next picture.
Here is the vba code I am currently using.
Sub CheckIfValuesExist()
Dim ActiveWS As Worksheet, WS2 As Worksheet
Dim ValueColLetter As String, SearchColLetter As String, TFColLetter As String
Dim LastRow As Long, i As Long
Dim target As Variant, rng As Range
Set ActiveWS = ActiveWorkbook.Worksheets(1)
Set WS2 = ActiveWorkbook.Worksheets(2)
ValueColLetter = "A"
SearchColLetter = "A"
TFColLetter = "B"
LastRow = ActiveWS.Cells.Find(What:="*", SearchOrder:=xlRows, _
SearchDirection:=xlPrevious, _
LookIn:=xlFormulas).Row
For i = 2 To LastRow
target = ActiveWS.Range(ValueColLetter & i).Value
If target <> "" Then
With WS2.Range(SearchColLetter & ":" & SearchColLetter) 'searches all of column A
Set rng = .Find(What:=target, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not rng Is Nothing Then
ActiveWS.Range(TFColLetter & i).Value = "T" 'value found
Else
ActiveWS.Range(TFColLetter & i).Value = "F" 'value not found
End If
End With
End If
Next i
End Sub
The macro works as intended, I just find it to be slow. Is there any better way to do the same thing but in a quicker manner?
Check Column Against Column
Array Match Range Version
Sub CheckIfValuesExist()
Const cSheet1 As Variant = 1 ' Value Worksheet Name/Index
Const cSheet2 As Variant = 2 ' Search Worksheet Name/Index
Const cFirst As Long = 2 ' First Row
Const cVal As Variant = "A" ' Value Column
Const cSrc As Variant = "A" ' Search Column
Const cTF As Variant = "B" ' Target Column
Const cT As String = "T" ' Found String
Const cF As String = "F" ' Not Found String
Dim RngS As Range ' Search Range
Dim vntV As Variant ' Value Array
Dim vntT As Variant ' Target Array
Dim LastV As Long ' Value Last Column Number
Dim LastS As Long ' Search Last Column Number
Dim i As Long ' Value/Target Row Counter
Dim dummy As Long ' Match Dummy Variable
With Application
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
On Error GoTo ProcedureExit
With ThisWorkbook.Worksheets(cSheet1)
LastV = .Columns(.Cells(1, cVal).Column).Find("*", , -4123, , 2, 2).Row
vntV = .Range(.Cells(cFirst, cVal), .Cells(LastV, cVal))
End With
With ThisWorkbook.Worksheets(cSheet2)
LastS = .Columns(.Cells(1, cSrc).Column).Find("*", , -4123, , 2, 2).Row
Set RngS = .Range(.Cells(cFirst, cSrc), .Cells(LastS, cSrc))
ReDim vntT(1 To UBound(vntV), 1 To 1)
For i = 1 To UBound(vntV)
On Error Resume Next
If vntV(i, 1) <> "" Then
dummy = Application.Match(vntV(i, 1), RngS, 0)
If Err Then
vntT(i, 1) = cF
Else
vntT(i, 1) = cT
End If
End If
On Error GoTo 0
Next
End With
On Error GoTo ProcedureExit
With ThisWorkbook.Worksheets(cSheet1)
.Range(.Cells(cFirst, cTF), .Cells(.Rows.Count, cTF)).ClearContents
.Cells(cFirst, cTF).Resize(UBound(vntT)) = vntT
End With
ProcedureExit:
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
End Sub
Let us assume that data included in Sheet 1.
Try:
Option Explicit
Sub VlookUp()
Dim LastRowSV As Long, LastRowV As Long, Counts As Long
Dim wsName As String
Dim wsListSV As Range, cellSV As Range, wsListV As Range, cellV As Range
With ThisWorkbook.Worksheets("Sheet1")
'Find the last row of Search Values
LastRowSV = .Cells(.Rows.Count, "D").End(xlUp).Row
'Find the last row of Values
LastRowV = .Cells(.Rows.Count, "A").End(xlUp).Row
'Set the list with the Search Values
Set wsListSV = .Range(Cells(2, 4), Cells(LastRowSV, 4))
'Set the list with the Values
Set wsListV = .Range(Cells(3, 1), Cells(LastRowV, 1))
'Loop each value in Search Values
For Each cellV In wsListV
Counts = Application.WorksheetFunction.CountIf(wsListSV, cellV)
If Counts <> 0 Then
cellV.Offset(0, 1).Value = "T"
Else
cellV.Offset(0, 1).Value = "F"
End If
Next
End With
End Sub
Result:
Why don't you use the MATCH formula?
If your values are in Col A and the search values are at
the cells $F$5:$F$10 the formula is:
=MATCH(A2,$F$5:$F$10,0)
or if you insist on a T/F result:
=IF(ISERROR(MATCH(A2,$F$5:$F$10,0)),"T","F")
Of cause you can insert this formula also with a macro.

Import variable range into Array/Collection?

Is there any way to import a range that looks like this:
I'm trying to import a range with an undetermined number of rows and columns. As the 5th row indicates, the range that I wish to import has in the first column business names and in their subsequent columns, different iterations of the same business.
I've been thinking of using arrays but I can't see it being possible as I would have varying dimensions per element (eg. 3 dimensions for canadian tire and 2 dimensions for mercedes).
I've also thought of using collections/dictionaries but I stumble at using and understanding them.
Ultimately, my intentions are to loop the iterations from this range in a column and, if any of these iterations match a cell in my column, to write in an offset cell the first iteration (business name in bold).
Now, I know, I could do a two dimensional array from a range like this, with repeated first iterations (business name):
However, it's quite cumbersome to rewrite business names.
My code below for what I was using for the two dimensional array:
Option Explicit
Sub VendorFinder()
'variable declaration
Dim msg As String
Dim ans As Integer
Dim rng As Range
Dim DescRng As Range
Dim DescCol As Range
Dim VendorCol As Range
Dim j As Long
Dim Vendor As Variant
Dim wb As Workbook
Dim sFile As String
Dim myVendor As Variant
Dim FirstRow As Range
Dim VendorRng As Range
'import vendors
sFile = "Z:\Vendor List.xlsx"
Application.ScreenUpdating = False
Set wb = Application.Workbooks.Open(sFile)
Vendor = wb.Sheets(1).Range(Cells(1, 1), Cells(Cells(Rows.Count, 2).End(xlUp).Row, 2)).Value2
wb.Close False
Application.ScreenUpdating = True
On Error GoTo BadEntry
TryAgain:
'set columns
Set DescCol = Application.InputBox("Select Description Column", "Obtain Object Range", Type:=8)
Set VendorCol = Application.InputBox("Select Vendor Column", "Obtain Object Range", Type:=8)
Set FirstRow = Application.InputBox("Select First Row with Data", "Obtain Object Range", Type:=8)
'set ranges
Set DescRng = Range(Cells(FirstRow.Row, DescCol.Column), Cells(Cells(Rows.Count, DescCol.Column).End(xlUp).Row, DescCol.Column))
Set VendorRng = Range(Cells(FirstRow.Row, VendorCol.Column), Cells(Cells(Rows.Count, DescCol.Column).End(xlUp).Row, VendorCol.Column))
myVendor = VendorRng.Value2
For Each rng In DescRng
If Cells(rng.Row, VendorCol.Column).Value = "" Then
For j = LBound(Vendor) To UBound(Vendor)
If InStr(1, rng.Value, Vendor(j, 2), vbTextCompare) > 0 Then
myVendor(rng.Row - FirstRow.Row + 1, 1) = Vendor(j, 1)
Exit For
End If
Next j
End If
Next rng
VendorRng.Resize(UBound(myVendor) - LBound(myVendor) + 1, 1) = myVendor
Exit Sub
BadEntry:
msg = "You have clicked on cancel for one of the prompts."
msg = msg & vbNewLine
msg = msg & "Do you wish to try again?"
ans = MsgBox(msg, vbRetryCancel + vbExclamation)
If ans = vbRetry Then Resume TryAgain
End Sub
Thanks a lot!
I think I might have something simpler
Dim arr As New Collection, a
Dim var() As Variant
Dim i As Long
Dim lRows As Long, lCols As Long
Dim lRowCurrent As Long
Dim lCounter As Long
'Get the active range
Set rng = ActiveSheet.UsedRange
lRows = rng.Rows.Count
lCols = rng.Columns.Count
lRowCurrent = 0
'Loop thru every row
For i = 1 To lRows
' Read each line into an array
var() = Range(Cells(i, 1), Cells(i, lCols))
' Create a list of unique names only
On Error Resume Next
For Each a In var
arr.Add a, a
Next
'List all names
lCounter = arr.Count
For b = 1 To lCounter
Cells(lRowCurrent + b, 7) = arr(1)
Cells(lRowCurrent + b, 8) = arr(b)
Next
Set arr = Nothing
lRowCurrent = lRowCurrent + lCounter
Next
Try this:
Sub DoTranspose()
Dim r&, cnt&
Dim rng As Range, rngRow As Range, cell As Range
Set rng = Sheets("Source").Range("A1").CurrentRegion
r = 1
For Each rngRow In rng.Rows
cnt = WorksheetFunction.CountA(rngRow.Cells)
With Sheets("output").Cells(r, 1).Resize(cnt)
.Value = rngRow.Cells(1).Value
.Offset(, 1).Value = Application.Transpose(rngRow.Resize(, cnt).Value)
End With
r = r + cnt
Next
End Sub
Sample workbook.
This seems to be a simple un-pivot operation.
If you have Excel 2010+, you can use Power Query (aka Get&Transform in Excel 2016+), to do this.
Select a single cell in the table
Data / Get & Transform / From Range should select the entire table
Select the first column in the Query table.
Transform / Unpivot other columns
Delete the unwanted column
Save and Load
(Takes longer to type than to do)
This is the M Code, but you can do it all from the PQ GUI:
let
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Changed Type" = Table.TransformColumnTypes(Source,{{"Column1", type text}, {"Column2", type text}, {"Column3", type text}, {"Column4", type text}}),
#"Unpivoted Other Columns" = Table.UnpivotOtherColumns(#"Changed Type", {"Column1"}, "Attribute", "Value"),
#"Removed Columns" = Table.RemoveColumns(#"Unpivoted Other Columns",{"Attribute"})
in
#"Removed Columns"
Original Data
Unpivoted
Range Array Array Range
A Picture is Worth a Thousand Words
The left worksheet is the initial worksheet, and the right the resulting one.
Since cBlnColors is set to True, several ranges are being colored to better understand how this code works and to indicate the upsides of having headers.
The light yellow color represents the data range, while the yellow color represents the rest of the off limits cells.
All not colored cells can be used without affecting the results in the right worksheet.
cBlnHeadersBelow set to True means that the data is above the headers (unusual), while set to False would mean that the data will be below the headers (as usual).
Headers Below Data with Colors
Another Thousand
The following picture shows the same code used with cBlnHeadersBelow set to False.
The yellow range spans down to the last row (not visible).
Again, all not colored cells can be used without affecting the results in the right worksheet.
Headers Above Data with Colors
The Code
Option Explicit
'*******************************************************************************
' Purpose: In a specified worksheet of a specified workbook, transposes a
' range of data (vertical table!?) to a two-column range in a newly
' created worksheet.
' Arguments (As Constants):
' cStrFile
' The path of the workbook file. If "", then ActiveWorkbook is used.
' cVarWs
' It is declared as variant to be able to use both, the title
' (a string e.g. "Sheet1") or the index (a positive whole number e.g. 1)
' of the worksheet. If "", then ActiveSheet is used.
' cStrTitle
' The contents of the first cell in the headers to be searched for.
' cBlnHeaders
' If True, USE headers.
' If False, do NOT use headers i.e. cBlnHeadersBelow has no effect and the
' first data found by searching by column from "A1" is used as first cell
' and the last found data on the worksheet is used for last cell.
' cBlnHeadersBelow
' If True, the data is ABOVE the headers (Data-Then-Headers).
' If False, the data is as usual BELOW the headers (Headers-Then-Data).
' cStrPaste
' The cell address of the first cell of the resulting range in the new
' worksheet.
' cBlnColors
' If True, and cBlnHeaders is True, then colors are being used i.e. one
' color for the data range, and another for off limits ranges.
' If True, and cBlnHeaders is False, all cells are off limits,
' so only the data range is colored.
' Returns
' A new worksheet with resulting data. No threat to the initial worksheet.
' If you don't like the result, just close the workbook.
'*******************************************************************************
Sub VendorFinder()
Application.ScreenUpdating = False
'***************************************
' Variables
'***************************************
Const cStrFile As String = "" ' "Z:\arrInit List.xlsx"
Const cVarWs As Variant = 1 ' "" for ActiveSheet.
Const cStrTitle As String = "Business" ' Contents of First Cell of Header
Const cBlnHeaders As Boolean = True ' True for Headers
Const cBlnHeadersBelow As Boolean = True ' True for Headers Below Data
Const cStrPaste As String = "A1" ' Resulting First Cell Address
Const cBlnColors As Boolean = True ' Activate Colors
Dim objWb As Workbook ' Workbook to be processed
Dim objWs As Worksheet ' Worksheet to be processed
Dim objTitle As Range ' First Cell of Header
Dim objFirst As Range ' First Cell of Data
Dim objLast As Range ' Last Cell of Data
Dim objResult As Range ' Resulting Range
Dim arrInit As Variant ' Array of Initial Data
Dim arrResult() As Variant ' Array of Resulting Data
Dim lngRows As Long ' Array Rows Counter
Dim iCols As Integer ' Array Columns Counter
Dim lngVendor As Long ' Array Data Counter, Array Row Counter
' ' Debug
' Const r1 As String = vbCr ' Debug Rows Separator
' Const c1 As String = "," ' Debug Columns Separator
'
' Dim str1 As String ' Debug String Builder
' Dim lng1 As Long ' Debug Rows Counter
' Dim i1 As Integer ' Debug Columns Counter
'***************************************
' Workbook
'***************************************
'On Error GoTo WorkbookErr
If cStrFile <> "" Then
Set objWb = Workbooks.Open(cStrFile)
Else
Set objWb = ActiveWorkbook
End If
'***************************************
' Worksheet
'***************************************
' On Error GoTo WorksheetErr
If cVarWs <> "" Then
Set objWs = objWb.Worksheets(cVarWs)
Else
Set objWs = objWb.ActiveSheet
End If
With objWs
' Colors
If cBlnColors = True Then
Dim lngData As Variant: lngData = RGB(255, 255, 153)
Dim lngOffLimits As Variant: lngOffLimits = RGB(255, 255, 0)
Else
.Cells.Interior.ColorIndex = xlNone
End If
' Assumptions:
' 1. Headers is a contiguous range.
' 2. The Headers Title is the first cell of Headers i.e. the first cell
' where cStrTitle is found while searching by rows starting from cell
' "A1".
' 3. The Headers Range spans from the Headers Title to the last cell,
' containing data, on the right.
' 4. All cells to the left and to the right of the Headers Range except
' for the cell adjacent to the right are free to be used i.e. no
' calculation is performed on them. If cBlnHeadersBelow is set to True,
' the cells below the Headers Range are free to be used. Similarly,
' if cBlnHeadersBelow is set to False the cells above are free to be
' used.
' 5. When cBlnHeadersBelow is set to True, the first row of data is
' calculated just using the column of the Headers Title
If cBlnHeaders = True Then ' USE Headers.
' Calculate Headers Title (using cStrTitle as criteria).
Set objTitle = .Cells _
.Find(What:=cStrTitle, After:=.Cells(.Rows.Count, .Columns.Count), _
LookIn:=xlFormulas, Lookat:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlNext)
' Calculate initial first and last cells of data.
If cBlnHeadersBelow Then ' Headers are below data.
' Search for data in column of Headers Title starting from the first
' worksheet's row forwards to the row of Headers Title.
' When first data is found, the first cell is determined.
Set objFirst = .Range(.Cells(1, objTitle.Column), objTitle) _
.Find(What:="*", After:=.Cells(objTitle.Row, objTitle.Column), _
LookIn:=xlFormulas, Lookat:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlNext)
' xlToRight, indicating that Headers Range is contiguous, uses the
' last cell of Headers Range while -1 sets the cells' row, one row above
' the Headers Title, resulting in the last cell range.
Set objLast = objTitle.End(xlToRight).Offset(-1, 0)
' Colors
If cBlnColors = True Then
.Cells.Interior.ColorIndex = xlNone
If objFirst.Row > 1 Then
.Range(.Cells(1, objFirst.Column), _
.Cells(objFirst.Row - 1, objLast.Column)) _
.Interior.color = lngOffLimits
End If
If objLast.Column < .Columns.Count Then
.Range(objTitle, .Cells(objTitle.Row, objLast.Column + 1)) _
.Interior.color = lngOffLimits
Else
.Range(objTitle, .Cells(objTitle.Row, objLast.Column)) _
.Interior.color = lngOffLimits
End If
.Range(objFirst, objLast).Interior.color = lngData
End If
Else ' Headers are above data (usually).
' 1 sets the cells' row, one row below the Headers Title
' resulting in the first cell range.
Set objFirst = objTitle.Offset(1, 0)
' Search for data in column of Headers Title starting from the last
' worksheet's row backwards to the row of Headers Title.
' When first data is found, the last row is determined and combined
' with the last column results in the last cell range.
Set objLast = .Cells( _
.Range(objTitle, .Cells(.Rows.Count, _
objTitle.End(xlToRight).Column)) _
.Find(What:="*", After:=objTitle, _
LookIn:=xlFormulas, Lookat:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlPrevious) _
.Row, _
_
objTitle.End(xlToRight) _
.Column)
'Colors
If cBlnColors = True Then
.Cells.Interior.ColorIndex = xlNone
If objLast.Row < .Rows.Count Then
.Range(.Cells(objLast.Row + 1, objFirst.Column), _
.Cells(.Rows.Count, objLast.Column)) _
.Interior.color = lngOffLimits
End If
If objLast.Column < .Columns.Count Then
.Range(objTitle, .Cells(objTitle.Row, objLast.Column + 1)) _
.Interior.color = lngOffLimits
Else
.Range(objTitle, .Cells(objTitle.Row, objLast.Column)) _
.Interior.color = lngOffLimits
End If
.Range(objFirst, objLast).Interior.color = lngData
End If
End If
Else ' Do NOT use headers.
' Search for data in any cell from "A1" by column. When first data is
' found, the first cell is determined.
Set objFirst = _
.Cells _
.Find(What:="*", After:=.Cells(.Rows.Count, .Columns.Count), _
LookIn:=xlFormulas, Lookat:=xlWhole, _
SearchOrder:=xlByColumns, SearchDirection:=xlNext)
' Last cell with data on the worksheet.
Set objLast = .Cells( _
_
.Cells _
.Find(What:="*", After:=.Cells(1, 1), _
LookIn:=xlFormulas, Lookat:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlPrevious) _
.Row, _
_
.Cells _
.Find(What:="*", After:=.Cells(1, 1), _
LookIn:=xlFormulas, Lookat:=xlWhole, _
SearchOrder:=xlByColumns, SearchDirection:=xlPrevious) _
.Column)
' Colors
If cBlnColors = True Then
.Cells.Interior.ColorIndex = xlNone
Range(objFirst, objLast).Interior.color = lngData
End If
End If
End With
'***************************************
' arrInit
'***************************************
' On Error GoTo arrInitErr
' Paste the values (Value2) of initial range into initial array (arrInit).
arrInit = Range(objFirst, objLast).Value2
' ' Debug
' str1 = r1 & "Initial Array (arrInit)" & r1
' For lng1 = LBound(arrInit) To UBound(arrInit)
' str1 = str1 & r1
' For i1 = LBound(arrInit, 2) To UBound(arrInit, 2)
' If i1 <> 1 Then
' str1 = str1 & c1
' End If
' str1 = str1 & arrInit(lng1, i1)
' Next
' Next
' Debug.Print str1
' Count data in arrInit.
For lngRows = LBound(arrInit) To UBound(arrInit)
For iCols = LBound(arrInit, 2) To UBound(arrInit, 2)
If arrInit(lngRows, iCols) <> "" Then
lngVendor = lngVendor + 1
End If
Next
Next
'***************************************
' arrResult
'***************************************
' On Error GoTo arrResultErr
ReDim arrResult(1 To lngVendor, 1 To 2) ' Resize resulting array (arrResult).
lngVendor = 0 ' Reset array data counter to be used as array row counter.
' Loop through arrInit and write to arrResult.
For lngRows = LBound(arrInit) To UBound(arrInit)
For iCols = LBound(arrInit, 2) To UBound(arrInit, 2)
If arrInit(lngRows, iCols) <> "" Then
lngVendor = lngVendor + 1
If iCols = 1 Then
arrResult(lngVendor, 1) = arrInit(lngRows, iCols)
Else
arrResult(lngVendor, 1) = arrResult(lngVendor - 1, 1)
End If
arrResult(lngVendor, 2) = arrInit(lngRows, iCols)
End If
Next
Next
Erase arrInit ' Data is in arrResult.
' ' Debug
' str1 = r1 & "Resulting Array (arrResult)" & r1
' For lng1 = LBound(arrResult) To UBound(arrResult)
' str1 = str1 & r1
' For i1 = LBound(arrResult, 2) To UBound(arrResult, 2)
' If i1 <> 1 Then
' str1 = str1 & c1
' End If
' str1 = str1 & arrResult(lng1, i1)
' Next
' Next
' Debug.Print str1
' Since there is only an infinite number of possibilities what to do with the
' resulting array, pasting it into a new worksheet has been chosen to be able
' to apply the bold formatting of the "Business Names" requested.
'***************************************
' New Worksheet
'***************************************
On Error GoTo NewWorksheetErr
Worksheets.Add After:=objWs
Set objResult = ActiveSheet.Range(Range(cStrPaste), _
Range(cStrPaste).Offset(UBound(arrResult) - 1, _
UBound(arrResult, 2) - 1))
With objResult
' Paste arrResult into resulting range (objResult).
.Value2 = arrResult
' Apply some formatting.
For lngRows = LBound(arrResult) To UBound(arrResult)
' If .Cells(lngRows, 1) = .Cells(lngRows, 2) Then ' Too slow!
If arrResult(lngRows, 1) = arrResult(lngRows, 2) Then
.Cells(lngRows, 1).Font.Bold = True
End If
Next
Erase arrResult ' Data is in objResult.
.Columns.AutoFit
End With
' To suppress the "Do you want to save changes you made to ... ?" - Alert:
objWb.Saved = True
'***************************************
' Clean Up
'***************************************
NewWorksheetExit:
Set objResult = Nothing
WorksheetExit:
Set objLast = Nothing
Set objFirst = Nothing
Set objTitle = Nothing
Set objWs = Nothing
WorkbookExit:
Set objWb = Nothing
Application.ScreenUpdating = True
Exit Sub
'***************************************
' Errors
'***************************************
WorkbookErr:
MsgBox "Workbook Error" & " (Error: '" & Err.Number & "')"
GoTo WorkbookExit
WorksheetErr:
MsgBox "Worksheet Error" & " (Error: '" & Err.Number & "')"
GoTo WorksheetExit
arrInitErr:
MsgBox "arrInit Error" & " (Error: '" & Err.Number & "')"
GoTo WorksheetExit
arrResultErr:
MsgBox "arrResult Error" & " (Error: '" & Err.Number & "')"
GoTo WorksheetExit
NewWorksheetErr:
MsgBox "New Worksheet Error" & " (Error: '" & Err.Number & "')"
GoTo NewWorksheetExit
End Sub
'*******************************************************************************
Extras
While testing the code, there were a little too many many worksheets in the workbook so I wrote this:
'*******************************************************************************
' Purpose: Deletes all Worksheets in the ActiveWorkbook except one.
' Danger: This code doesn't ask anything, it just does. In the end you will
' end up with just one worksheet (cStrWsExcept) in the workbook
' (cStrWbPath). If you have executed this code and the result is not
' satisfactory, just close the workbook and try again or don't. There
' will be no alert like "Do you want to save ..." because of the line:
' ".Saved = True" i.e. "objWb.Saved = True".
' Arguments (As Constants):
' cStrWbPath
' The path of the workbook to be processed. If "", then ActiveWorkbook is
' used.
' cStrWsExcept
' The worksheet not to be deleted. If "", then the Activesheet is used.
'*******************************************************************************
Sub DeleteWorksheetsExceptOne()
Const cStrWbPath = "" ' if "" then ActiveWorkbook
Const cStrWsExcept = "Sheet1" ' if "" then ActiveSheet
Dim objWb As Workbook
Dim objWsExcept As Worksheet
Dim objWsDelete As Worksheet
If cStrWbPath = "" Then
Set objWb = ActiveWorkbook
Else
Set objWb = Workbooks(cStrWbPath)
End If
With objWb
If cStrWsExcept = "" Then
Set objWsExcept = .ActiveSheet
Else
Set objWsExcept = .Worksheets(cStrWsExcept)
End If
' To suppress the "Data may exist in the sheet(s) selected for deletion.
' To permanently delete the data, press Delete." - Alert:
Application.DisplayAlerts = False
For Each objWsDelete In .Worksheets
If objWsDelete.Name <> objWsExcept.Name Then
objWsDelete.Delete
End If
Next
' To suppress the "Do you want to save changes you made to ... ?" - Alert:
.Saved = True
Application.DisplayAlerts = True
End With
End Sub
'*******************************************************************************

Excel VBA opening and merging many workbooks

I have many, over two dozen (and counting), data sets with 15000 rows and 36 columns each, that I would like to combine. These data sets are have the same columns and more or less the same rows. They are monthly snapshots of the same data, with some data leaving and some entering (hence the marginally different number of rows.
I would like the user to select some of them and and combine them. The name of the file contains that date and my code extracts the date and adds it in a new column at the end. Right now, my code works. I collect all the data in a three dimensional array and then paste it in a new workbook. The problem is that since each book has different numbers or rows, I am creating a data array with more rows than needed. So my data has a lot of empy rows right now. I guess I can delete the empty rows in the end. I am new to excel VBA and new to doing data work so I was wondering if there was a smarter, more efficient way of construction my panel.
Dim DataArray As Variant
Sub test()
Dim filespec As Variant, i As Integer
ReDim DataArray(0 To 20000, 0 To 36, 0 To 0)
' Here the user gets to select the files
On Error GoTo EndNow
filespec = Application.GetOpenFilename(FileFilter:="microsoft excel files (*.xls), *.xls", Title:="Get File", MultiSelect:=True)
For i = 1 To UBound(filespec)
ReDim Preserve DataArray(0 To 20000, 0 To 36, 0 To i)
Set wbSource = Workbooks.Open(filespec(i))
Set ws1 = wbSource.Worksheets("Sheet1")
With ws1
'now I store the values in my array
FinalColumn = .Cells(1, .Columns.Count).End(xlToLeft).Column
FinalRow = .Range("B" & .Rows.Count).End(xlUp).Row
For j = 1 To FinalRow
For k = 1 To FinalColumn
DataArray(j, k, i) = .Cells(j, k).Value
Next k
' Now I extract the date data from the file name and store it in the last column of my array.
DataArray(j, FinalColumn + 1, i) = piece(piece(GetFileName(CStr(filespec(i))), "_", 3), ".", 1)
Next j
End With
ActiveWorkbook.Close
Next i
Set wb2 = Application.Workbooks.Add
Set ws2 = wb2.Worksheets("Sheet1")
With ws2
For i = 1 To UBound(DataArray, 3)
FinalRow2 = 20000
FinalColumn2 = 36
For k = 1 To FinalColumn2
' I did this If loop so as to not copy headers every time.
If i = 1 Then
For j = 1 To FinalRow2
.Cells(j, k).Value = DataArray(j, k, i)
Next j
Else
For j = 2 To FinalRow2
.Cells(FinalRow2 * (i - 1) + j, k).Value = DataArray(j, k, i)
Next j
End If
Next k
Next i
wb2.Sheets(1).Name = "FolderDetails Panel Data"
wb2.SaveAs ThisWorkbook.Path & "Folder_Details_Panel_Data" & "_" & Format(Date, "yyyy_mm_dd"), _
FileFormat:=xlOpenXMLWorkbook, CreateBackup:=False
End With
EndNow:
End Sub
' MsgBox GetFileName(filespec(0))
Function GetFileName(filespec As String)
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
GetFileName = fso.GetFileName(filespec)
End Function
Function piece(Searchstring As String, Separator As String, IndexNum As Integer) As String
Dim t
t = Split(Searchstring, Separator)
If UBound(t) > 0 Then piece = t(IndexNum - 1)
End Function
To answer your direct question, I would copy the data from each workbook into the merged workbook as each is processed. I see no advantage in collecting all the data into a 3D array.
There are also many other issues with your code. What follows is a refactor of your code, with changes highlighted.
Option Explicit ' <-- Force declaration of all variables (must be first line in module)
Sub Demo()
Dim filespec As Variant
Dim i As Long ' --> Long is prefered over Integer
Dim DataArray As Variant ' <-- no need to be Module scoped
' --> Declare all your variables
Dim j As Long, k As Long
Dim wbSource As Workbook
Dim ws As Worksheet
Dim wbMerged As Workbook
Dim wsMerged As Worksheet
Dim DataHeader As Variant
Dim FinalRow As Long, FinalColumn As Long
Dim sDate As String
Dim rng As Range
' Here the user gets to select the files
On Error GoTo EndNow
filespec = Application.GetOpenFilename(FileFilter:="microsoft excel files (*.xls), *.xls", Title:="Get File", MultiSelect:=True)
If Not IsArray(filespec) Then
' <-- User canceled
Exit Sub
End If
' Speed up processing <--
' -- Comment these out for debugging purposes
'Application.ScreenUpdating = False
'Application.Calculation = xlCalculationManual
' Create Merged Workbook
Set wbMerged = Application.Workbooks.Add
Set wsMerged = wbMerged.Sheets(1)
wsMerged.Name = "FolderDetails Panel Data"
For i = 1 To UBound(filespec)
Set wbSource = Workbooks.Open(filespec(i))
Set ws = wbSource.Worksheets("Sheet1")
With ws
FinalColumn = .Cells(1, .Columns.Count).End(xlToLeft).Column
FinalRow = .Cells(.Rows.Count, 2).End(xlUp).Row
If i = 1 Then
' Get header from first workbook only
DataHeader = Range(.Cells(1, 1), .Cells(1, FinalColumn)).Value ' <-- Get data header
ReDim Preserve DataHeader(1 To 1, 1 To UBound(DataHeader, 2) + 1) ' <-- Range.Value arrays are 1 based
k = UBound(DataHeader, 2)
DataHeader(1, k) = "Date" ' <-- Header
End If
' Get all data in one go, excluding header
DataArray = Range(.Cells(2, 1), .Cells(FinalRow, FinalColumn)).Value ' <-- Array size matches data size
End With
wbSource.Close False
' Add Date to data
sDate = GetDateFromFileName(filespec(i)) '<-- do it once
' resize data array
ReDim Preserve DataArray(1 To UBound(DataArray, 1), 1 To UBound(DataArray, 2) + 1) ' <-- Range.Value arrays are 1 based
' Add date data
For j = 1 To UBound(DataArray, 1)
DataArray(j, k) = sDate
Next j
' Complete processing of each workbook as its opened
With wsMerged
' Add header row from first workbook
If i = 1 Then
Range(.Cells(1, 1), .Cells(1, UBound(DataArray, 2))) = DataHeader
End If
' <-- Add data to end of sheet
' Size the destination range to match the data
Set rng = .Cells(.Rows.Count, 2).End(xlUp).Offset(1, -1)
Set rng = rng.Resize(UBound(DataArray, 1), UBound(DataArray, 2))
rng = DataArray
End With
Next i
' <-- append \ to path
wbMerged.SaveAs ThisWorkbook.Path & "\" & "Folder_Details_Panel_Data" & "_" & Format(Date, "yyyy_mm_dd"), _
FileFormat:=xlOpenXMLWorkbook, CreateBackup:=False
CleanUp:
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Exit Sub
EndNow:
MsgBox "Oh dear"
GoTo CleanUp
End Sub
' Simplified
' <-- Not entirely sure if this will match your file name pattern.
' Please check
' Assumed file name
' Some\Path\Some_Words_YYYMMDD.xls
Function GetDateFromFileName(Nm As Variant) As String
Dim str As String
str = Mid$(Nm, InStrRev(Nm, "\") + 1)
str = Left$(str, InStrRev(str, ".") - 1)
str = Mid$(str, InStrRev(str, "_") + 1)
GetDateFromFileName = str
End Function

Resources