Merge Excel Sheets Using VBA - vba

I have a Excel Sheet(Say OG.xls) which has some data already in it with some 5000 rows with headings in the first row and Upto "AN" Columns.
This No of rows(5000) doesn't change for a whole year.
Now i have 5 XL files(Say A,B,C,D,E) and the data from these files has to be appended to this OG file just starting from 5001st row every time.
All these 5 files has different no of columns but identical to that of OG File.
I have to pull data from these files and place them in OG File.
From File A : Column A,B,C,D,E,F,G&H goes to Column F,G,T,U,V,W,X&Y Of OG.xls File.
Likewise the other files data has to be extracted according to the corresponding column with OG.xls
The second file data has to be appended right below the next row where the File A ends.(Say after filling the data from File A now the OG.xls has 5110 rows,
the File B data has to filled from 5111 st row of OG.xls.
The same follows for the other files too.
The data of these 5 files has to be filled row after row but should match the columns to that of OG.xls
Each time the same operation is repeated by filling the data from 5001st row of OG.xls. For convenience we can have all these files in a same folder.
How can we do this.
Please help me in this!!!
Also let me know for any clarifications.

If you need a more presice answer, you would need to try something first and then ask for help in area you have got stuck. My suggestion is you begin by;
1. Start writing a VBA script in OG.XLS, as a first step try to access the file A.xls and reading the columns and pasting them (they can initially be at any location in any order).
2. Once you are able to do this, next step is to see if you put the data in right column (say 5000 in your example) by setting up right kind of variables and using them and incrementing them.
3. Your next step should be to to read the column headings in A.XLS and finding them OG.XLS and identifying them. Initially you can begin by doing a simple string comparision, later you can refine this to do a VLOOKUP.
4. During this process, if you encounter any specific problem, raise it so that you will get a better answer.
Few from the community would go to the extent of writing the entire code for you.

Why does Column A end up in Column F, and why does C end up in T? Is there a rule around this such as the first row is a header with with the same text in it?
Maybe a picture might help.
Based on what i can guess, i'd throw each sheet into a RecordSet with meaningful field names (you'll need to reference Microsoft ActiveX Data Objects 2.8 Library) . Once done it will be very easy to append each RecordSet and throw them into a single sheet.
You'll need to be able to find the last column and last row in each sheet to do this cleanly so have a look at How can i find the last row...
Edit...
Below is a cleaned up example of how you could do what you need in VBA. The devil is in the details such as empty sheets, and how to handle formulas (this ignores them completely), and how to merge you columns in an appropriate way (again ignored).
This has been tested in Excel 2007.
Option Explicit
Const MAX_CHARS = 1200
Sub MergeAllSheets()
Dim rs As Recordset
Dim mergedRS As Recordset
Dim sh As Worksheet
Dim wb As Workbook
Dim fieldList As New Collection
Dim rsetList As New Collection
Dim f As Variant
Dim cols As Long
Dim rows As Long
Dim c As Long
Dim r As Long
Dim ref As String
Dim fldName As String
Dim sourceColumn As String
Set wb = ActiveWorkbook
For Each sh In wb.Worksheets
Set rs = New Recordset
ref = FindEndCell(sh)
cols = sh.Range(ref).Column
rows = sh.Range(ref).Row
If ref <> "$A$1" Or sh.Range(ref).Value <> "" Then '' This is to catch empty sheet
c = 1
r = 1
Do While c <= cols
fldName = sh.Cells(r, c).Value
rs.Fields.Append fldName, adVarChar, MAX_CHARS
If Not InCollection(fieldList, fldName) Then
fieldList.Add fldName, fldName
End If
c = c + 1
Loop
rs.Open
r = 2
Do While r <= rows
rs.AddNew
c = 1
Do While c <= cols
rs.Fields(c - 1) = CStr(sh.Cells(r, c).Value)
c = c + 1
Loop
r = r + 1
Debug.Print sh.Name & ": " & r & " of " & rows & ", " & c & " of " & cols
Loop
rsetList.Add rs, sh.Name
End If
Next
Set mergedRS = New Recordset
c = 1
sourceColumn = "SourceSheet"
Do While InCollection(fieldList, sourceColumn) '' Just in case you merge a merged sheet
sourceColumn = "SourceSheet" & c
c = c + 1
Loop
mergedRS.Fields.Append sourceColumn, adVarChar, MAX_CHARS
For Each f In fieldList
mergedRS.Fields.Append CStr(f), adVarChar, MAX_CHARS
Next
mergedRS.Open
c = 1
For Each rs In rsetList
If rs.RecordCount >= 1 Then
rs.MoveFirst
Do Until rs.EOF
mergedRS.AddNew
mergedRS.Fields(sourceColumn) = "Sheet No. " & c
For Each f In rs.Fields
mergedRS.Fields(f.Name) = f.Value
Next
rs.MoveNext
Loop
End If
c = c + 1
Next
Set sh = wb.Worksheets.Add
mergedRS.MoveFirst
r = 1
c = 1
For Each f In mergedRS.Fields
sh.Cells(r, c).Formula = f.Name
c = c + 1
Next
r = 2
Do Until mergedRS.EOF
c = 1
For Each f In mergedRS.Fields
sh.Cells(r, c).Value = f.Value
c = c + 1
Next
r = r + 1
mergedRS.MoveNext
Loop
End Sub
Public Function InCollection(col As Collection, key As String) As Boolean
Dim var As Variant
Dim errNumber As Long
InCollection = False
Set var = Nothing
Err.Clear
On Error Resume Next
var = col.Item(key)
errNumber = CLng(Err.Number)
On Error GoTo 0
'5 is not in, 0 and 438 represent incollection
If errNumber = 5 Then ' it is 5 if not in collection
InCollection = False
Else
InCollection = True
End If
End Function
Public Function FindEndCell(sh As Worksheet) As String
Dim cols As Long
Dim rows As Long
Dim maxCols As Long
Dim maxRows As Long
Dim c As Long
Dim r As Long
maxRows = sh.rows.Count
maxCols = sh.Columns.Count
cols = sh.Range("A1").End(xlToRight).Column
If cols >= maxCols Then
cols = 1
End If
c = 1
Do While c <= cols
r = sh.Cells(1, c).End(xlDown).Row
If r >= maxRows Then
r = 1
End If
If r > rows Then
rows = r
End If
c = c + 1
Loop
FindEndCell = sh.Cells(rows, cols).Address
End Function

Related

vba Deleting specific rows and columns in a word table

I need to parse most of a word doc tables and delete the cells containing the string "Deleted". The tables have different formats so i need to change the start index depending on the format.i wrote a code where i start first deleting rows with all cells containing the "Deleted" string. The same strategy is used to delete the columns. It worked for the rows but not for the columns delete. When running through the table cells the column index is bigger than the actual available columns due to deleted columns.i dont know why it occured in the column delete part and not in the row delete one. The code below works for some of the tables but not all of them.
Dim msWord As Word.Application
Dim myDoc As Word.Document
Dim wordTable As Table
Dim r As Long
Dim c As Long
Dim col_del_cnt As Long
Dim row_del_cnt As Long
Dim i As Long
Dim col_index As Long
Dim param As Long
Dim Tbl As Table
Dim Tmpfile As String
Tmpfile = ThisWorkbook.Path & "\Template.docx"
With msWord
.Visible = True
Set myDoc = .Documents.Open(Filename:=Tmpfile)
End With
With myDoc
For i = 7 To 21
Set wordTable = .Tables(i)
If wordTable.Columns.Count < 2 Then
col_index = 1
param = wordTable.Columns.Count
Else
col_index = 2
param = wordTable.Columns.Count - 1
End If
For r = 2 To wordTable.Rows.Count
col_del_cnt = 0
For c = col_index To wordTable.Columns.Count
If InStr(1, wordTable.Cell(r, c).Range.Text, "Deleted", 1) Then
col_del_cnt = col_del_cnt + 1
End If
Next c
If col_del_cnt = param Then
If r > wordTable.Rows.Count Then
wordTable.Rows(wordTable.Rows.Count).Delete
Else
wordTable.Rows(r).Delete
End If
End If
Next r
Next
End With
With myDoc
For i = 7 To 21
Set wordTable = .Tables(i)
If wordTable.Columns.Count < 2 Then
col_index = 1
Else
col_index = 2
End If
For c = col_index To wordTable.Columns.Count
row_del_cnt = 0
For r = 2 To wordTable.Rows.Count
If InStr(1, wordTable.Cell(r, c).Range.Text, "Deleted", 1) Then '\Error located here'
row_del_cnt = row_del_cnt + 1
End If
Next r
If row_del_cnt = wordTable.Rows.Count - 1 Then
If c > wordTable.Columns.Count Then
wordTable.Columns(wordTable.Columns.Count).Delete
Else
wordTable.Columns(c).Delete
End If
End If
Next c
Next
End With
I hope someone could help me to find the solution.
When deleting something indexed, you have to do it backwards.
Change
For i = 7 To 21
to
For i= 21 to 7 Step -1
and so on.
It appears you're trying to delete both the row and the column when a cell has 'Deleted' in it. Obviously, if you use one loop to delete a row that has 'Deleted' in it, then a second loop to delete a column that has 'Deleted' in it, the second loop won't find anything. Try something based on:
Dim t As Long, r As Long, c As Long, ArrCols() As String
With myDoc
For t = 21 To 7 Step -1
With .Tables(t)
If InStr(1, .Range.Text, "Deleted", 1) Then
ReDim ArrCols(.Columns.Count)
For r = .Rows.Count To 1 Step -1
With .Rows(r)
If InStr(1, .Range.Text, "Deleted", 1) Then
For c = 1 To .Cells.Count
If InStr(1, .Cells(c).Range.Text, "Deleted", 1) Then
ArrCols(c) = c
End If
Next
.Delete
End If
End With
Next r
For c = UBound(ArrCols) To 1 Step -1
If ArrCols(c) <> "" Then .Columns(c).Delete
Next
End If
End With
Next
End With
Note how all the loops involving deletions run backwards.
The fact your own code didn't throw errors with the row deletions was just a coincidence.

How to number format only certain cells that contain data using VBA

The following code reads lines from a csv file and reformats row headings. I hope I can articulate this, but I'd like to format cells to two decimals in rows below headings in which the vct_FEMAP_Results() function returns a value.
Example:
ID "CSys ID" "Set ID" Plate Top VM Stress Plate Bot VM Stress
------ ----------- ---------- ----------------------- ----------------------
4591 0 20 229.9488 244.8103
4592 0 20 323.5026 315.1129
I'm trying to format the cells containing the decimals without affecting the data in column headings ID, CSys ID, or Set ID. The code below formats all columns to 2 decimals. Not sure why.
Sub cmdOpen_Click()
Dim wrdArray() As String, txtstrm As TextStream, line As String
Dim wrd As Variant, myWrd As String
Dim col As Long, colCount As Long
Dim count As Long
Dim row As Long, temp As Long
Dim str As String, regex As RegExp
Dim matches As MatchCollection, lineMatch As match, wrdMatch As match
Dim i As Long, j As Long, x As Long
Dim strPath As String, strLine As String
Set regex = New RegExp
regex.Pattern = "\d+"
regex.Global = True
'Remove Filters and Add Custom Filters
Call Application.FileDialog(msoFileDialogOpen).Filters.Clear
Call Application.FileDialog(msoFileDialogOpen).Filters.Add("Text Files", "*.txt")
Call Application.FileDialog(msoFileDialogOpen).Filters.Add("Dat Files", "*.dat")
Call Application.FileDialog(msoFileDialogOpen).Filters.Add("Comma Delimited Files", "*.csv")
'only allow the user to select one file
Application.FileDialog(msoFileDialogOpen).AllowMultiSelect = False
'make the file dialog visible to the user
intChoice = Application.FileDialog(msoFileDialogOpen).Show
'determine what choice the user made
If intChoice <> 0 Then
'get the file path selected by the user
strPath = Application.FileDialog(msoFileDialogOpen).SelectedItems(1)
End If
'------------------------------------------------------------
If strPath <> "" Then
Set txtstrm = FSO.OpenTextFile(strPath)
Else
Exit Sub
End If
row = 1
Do Until txtstrm.AtEndOfStream
line = txtstrm.ReadLine
x = 1
col = 1
count = 0
wrdArray() = Split(line, ",")
For Each wrd In wrdArray()
count = count + 1
myWrd = wrd
ActiveSheet.Cells(row, col) = wrd
col = col + 1
Next wrd
If (row = 1) Then
For i = 0 To count - 1
Set matches = regex.Execute(wrdArray(i))
For Each wrdMatch In matches
If wrdMatch Then
ActiveSheet.Cells(1, i + 1) = vct_FEMAP_Results(wrdMatch.Value)
x = x + 1
End If
Next
Next i
End If
row = row + 1
Loop
txtstrm.Close
For i = 1 To row - 1
For j = x To col - 1
ActiveSheet.Cells(i, j).NumberFormat = "0.00"
Next j
Next i
End Sub
Your code is formatting all your columns because you are looping through the columns with this bit:
For i = 1 To row - 1
For j = x To col - 1
ActiveSheet.Cells(i, j).NumberFormat = "0.00"
Next j
Next i
If you already know which columns need to be formatted, just do it like this:
ActiveSheet.Range("d:d, e:e").EntireColumn.NumberFormat = "0.00"
That will reformat only columns D and E, as per your sample data. Change the d's and e's if you need other columns.
I actually prefer to avoid using "ActiveSheet" and always reference the specific worksheet explicitly, so I'm always sure which worksheet my code is targeting. Whereas, when you use ActiveSheet(or cell or workbook) the active sheet can change, sometimes in unexpected ways.
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
ws.Range("d:d, e:e").EntireColumn.NumberFormat = "0.00"
I hope that helps!

Issue with if loop in vba to compare first word from two text strings

the code below is part of a larger Macro, all variables have been specified earlier on but this is the part i'm having the problem with. it's basically meant to loop through a column of company Names and for each company, add up all charges to that company listed on another workbook (essentially like a cost summary for each). Everything seems to work fine except the two rows with ** next to them, here im getting the "Invalid Procedure Call or argument" error and im not sure why. This particular section is meant to compare only the first word in a company name on each workbook (this means different branches are also summed up for a head office total, e.g. so "Company x Group" would include "Company x Manchester" and "Company x London" in its total).
I've tested the two problematic lines in a smaller test macro to see if it actually does represent the first word of the string and it works fine but when i try to use it in this larger macro this is the part that stops it working.
I'm very new to VBA so i understand if the code is a bit clunky and messy but any help would be greatly appreciated. Also apologies for the long winded explanation.
Thanks in Advance!
(the "If Not" part is so only companies that have had sales in this particular week but do not have an amount next to it are taken through the extra loop i.e. "number of sales" isn't empty but "money made" is 0)
Dim AgeName As Range
Dim AgeNameB As Range
Dim AgeNameAdd As String
Dim Lrow As Long
Dim J As Integer
Dim K As Double
Dim PostingRange As Range
Dim Postingaddress As String
Dim MarginValueBook As String
Dim MarginValueSheet As String
Dim WENum As String
Dim Postinglocation As Range
Dim L As Integer
Dim M As Double
Dim FirstNameAgeA As String
Dim FirstNameAgeB As String
Dim WENumb As String
Dim AgeComparison As String
Dim FirstWordArrA As String
Dim FirstWordArrB As String
MarginValueBook = "W.E. " & Format(dtTestDate, "DD.MM") & ".csv"
MarginValueSheet = "W.E. " & Format(dtTestDate, "DD.MM")
For i = 2 To y
K = 0
Workbooks("Average Margin Data.xlsm").Activate
Set ws = ThisWorkbook.Worksheets("Breakdown")
Set AgeName = ws.Range(celladdress).Offset(i)
AgeNameAdd = AgeName.Address
Set PostingRange = Range(AgeNameAdd).Offset(0, 3)
Postingaddress = PostingRange.Address
Workbooks(MarginValueBook).Activate
Set ws = Worksheets(MarginValueSheet)
Lrow = ActiveSheet.UsedRange.Rows.Count
For J = 2 To Lrow
WENum = "A" & J
If ws.Range(WENum) = UCase(AgeName) Then
K = K + ws.Range(WENum).Offset(0, 4).Value
End If
Next J
Set Postinglocation = Range(Postingaddress).Resize(1, 1)
Postinglocation.Value = K
Set ws = ThisWorkbook.Worksheets("Breakdown")
If Not ws.Range(AgeNameAdd).Offset(0, 2) = "" Then
If ws.Range(AgeNameAdd).Offset(0, 3) = 0 Then
For L = 2 To Lrow
Set AgeName = ws.Range(celladdress).Offset(i)
FirstWordArrA = AgeName.Value
'FirstNameAgeA = Trim$(Left$(FirstWordArrA, InStr(FirstWordArrA, " ") - 1))
AgeComparison = UCase(FirstNameAgeA)
Set wb = Workbooks(MarginValueBook)
Set ws = wb.Worksheets(MarginValueSheet)
WENumb = "A" & L
Set AgeNameB = ws.Range(WENumb)
FirstWordArrB = AgeNameB.Value
'FirstNameAgeB = Trim$(Left$(FirstWordArrB, InStr(FirstWordArrB, " ") - 1))
If AgeComparison = FirstNameAgeB Then
M = M + ws.Range(WENumb).Offset(0, 4).Value
End If
Next L
Set Postinglocation = Range(Postingaddress).Resize(1, 1)
Postinglocation.Value = M
M = 0
End If
End If
Next i
End Sub
Look at the InStr values. Someone might be null. Try to put a onError statement in order to return the correct value.

Excel VB subscript out of range repairing macro

Here is the declaration of navrec
Public navrec(1 To 100000, 100) As Variant
navrec(r,c) = Cells(r,c)
I encounter a subscript out of range error on the above for loop line within the below procedure
I am attempting to repair this macro with limited working knowledge of VB-Excel. I am assuming the preceding for loop ranges' navreclr & navreclc are referencing too large of a data area?
Sub import_navr()
EntityList = mywkb.Sheets("Source Files").Range("nrlist")
navreclr = 0
days = 0
fname = navrecloc
If Dir(fname) = "" Then
MsgBox ("Please save current PVAL. Macro will end")
End
End If
Workbooks.Open fname, ReadOnly:=True
Set tempbk = ActiveWorkbook
navreclr = Cells(1048576, 1).End(xlUp).Row
navreclc = Sheets(1).Cells(1, 1).End(xlToRight).Column
For r = 1 To navreclr
For c = 1 To navreclc
navrec(r, c) = Cells(r, c)
Next c
Next r
For c = 1 To navreclc
If navrec(1, c) = "ENTITY_ID" Then einr = c
If navrec(1, c) = "SHARE_CLASS" Then scnr = c
If navrec(1, c) = "LEDGER_ITEMS" Then linr = c
If navrec(1, c) = "BALANCE_CHANGE" Then bcnr = c
Next c
Set ofs = CreateObject("Scripting.FileSystemObject")
mywkb.Sheets("Source Files").Range("nrlist").Cells(1, 2) = ofs.GetFile(fname).Datelastmodified
tempbk.Close savechanges:=False
End Sub
The problem is that your array, navrec, is not the correct size to hold all of the cells. You can fix this by adding the following code directly above your loop (I've included the loop below):
ReDim navrec(navreclr, navreclc) As Variant
For r = 1 To navreclr
For c = 1 To navreclc
navrec(r, c) = Cells(r, c)
Next c
Next r
When you are determining the dimensions of your array dynamically, as in this example, it is generally best to declare the array dynamically as well, and not use a constant size. Try not to overutilize memory unless there is a good reason to do so.
You will also need to declare the variable dimensionless in the global module, or you will get an error stating "Array already dimensioned."
Replace:
Public navrec(1 To 100000, 100) As Variant
with
Public navrec() As Variant
to eliminate this error.
You can pick up the value of a rectangular array directly in one operation:
Set tempbk = Workbooks.Open(fname, ReadOnly:=True)
With tempbk.Sheets(1)
'get row/col counts
navreclr = .Cells(.Rows.Count, 1).End(xlUp).Row
navreclc = .Cells(1, .Columns.Count).End(xlToLeft).Column
'assign range value to array
nacrec = .cells(1).Resize(navreclr, navreclc).Value
End With

Excel UDF is returning #value! when changing worksheets and copying and pasting

I have a sheet that has a table on it, the following function will be used to search another sheet and return the number of times the agents name shows up;
The problem I'm having is that if I try to copy and paste the value it will change from a number to '#Value!'. Also, when switching worksheets and then switching back to the worksheet that has the UDF being called it changes all values to '#Value!'
Here is the function and how it is called. Any help would be greatly appreciated.
Public Function GetMatrixCount(AgentName As String) As Integer
Dim matrixSheet As Worksheet, mContainer() As String, c As Integer, m As Integer, y As Integer
Dim fullRange As Range, l As Range, lastRow As Integer
Dim firstThree As String, curAgent As String
'toDo
'return zero if the matrix updates worksheet doesn't exist or the input string is empty
On Error Resume Next
Set matrixSheet = Sheets("Matrix Updates")
On Error GoTo 0
If matrixSheet Is Nothing Or Not Trim(AgentName) <> "" Then
GetMatrixCount = 0
Exit Function
End If
'get month number user wants to input from the title at the top of the page - used to do value check on matrix updates data
mContainer() = Split(Range("B1").Value, " ")
m = month(DateValue(mContainer(UBound(mContainer) - 1) & " 1"))
y = mContainer(UBound(mContainer))
firstThree = Left(AgentName, 3)
lastRow = matrixSheet.Cells(Rows.Count, 1).End(xlUp).Row
c = 0
Set fullRange = matrixSheet.Range("B2:B" & lastRow)
For Each l In fullRange.Cells
curAgent = l.Offset(0, 1).Value
If month(l.Value) = m And year(l.Value) = y And Left(curAgent, 3) = firstThree And Mid(curAgent, InStrRev(curAgent, " ") + 1) = Mid(AgentName, InStrRev(AgentName, " ") + 1) Then
c = c + 1
End If
If l.Value = "" Then
Exit For
End If
Next
GetMatrixCount = c
End Function
Usage:
=GetMatrixCount(B4)
B4: John Doe
UPD:
Try to write following:
Set wb = ThisWorkbook
Set matrixSheet = wb.Sheets("Matrix Updates")
It should fix the problem when you switch workbooks.