I have several sheets with different structure that i need to merge using some of the columns headers that are common
I gathered in the one sheet ("Combine") the common headers and tried to write a macro to find the same column and to its data to the combine sheet, the macro is only getting the first column and not proceeding.
Any guidance with this issue will be appreciated
Dim II%, XX%, ZZ%, I% ' Dim as long
Dim Sht As Worksheet ' Every Sheet on This Workbook
Dim Comb As Worksheet ' Combine Sheet
Set Comb = ThisWorkbook.Worksheets("Combine")
II = 2 ' Start on row 2 - Sheet1 & Sheet2
XX = 2 ' Start on row 2 - Combine sheet
'Looping through the worksheets in the workbook
For Each Sht In ThisWorkbook.Worksheets
' ignore Sheet "Combine" and "Val"
If Sht.Name <> "Combine" And Sht.Name <> "Val" Then
For ZZ = 1 To 100
For I = 1 To 100
If Sheets(Sht.Name).Cells(1, I).Value = Comb.Cells(1, ZZ).Value Then
Do Until IsEmpty(Sht.Columns(1).Cells(II))
Comb.Cells(XX, ZZ).Value = Sheets(Sht.Name).Cells(II, I).Value
II = II + 1
XX = XX + 1
Loop
End If
Next I
I = 1
Next ZZ
End If
II = 2 ' Reset 1st Loop to capture the new sheet data
Next
If I understand your question correctly, you have multiple sheets that have a heading row of some number of columns. You then have data rows below this in corresponding columns.
You’ve looked at the headings in each sheet and added those names that are common to a sheet you’ve called Combine. Not all columns on all sheets are found on the Combine sheet. The Combine sheet is a subset of the total column names in the workbook.
The sheets might contain data from several test runs or whatever. The output might contain common columns as well as some additional data. For example, sheet 1 could contain date, time, location, and result. Sheet 2 could contain date, time, and tester.
You want a combined sheet that shows the common fields, in this case Date, Time, Result, and Tester. You’ve already determined the common headings.
I think your problem might be in Do Until IsEmpty(Sht.Columns(1).Cells(II)). You may be encountering an empty cell.
Also, it is much faster to use Excel's built-in functions to perform moving large blocks of data between sheets.
Given you seem to be learning about VBA and have made a pretty good attempt, I took the liberty to provide you with an example that uses a more advanced way for solving the problem
The code below in effect concatenates the data from each sheet and common column on to the Combine sheet. It leaves blanks where a column does not have a data sheet have data that would be copied into the Combine column. This means that there will be blank cells under the columns Result and Test – based on the source data sheet.
I hope you find this helpful and that it answers your question. I have learned a lot from other's example on this site and am trying to pay it forward.
Option Explicit
Public Sub Tester()
'Not needed
'Dim II%, XX%, ZZ%, I% ' Dim as long
Dim Comb As Worksheet ' Combine Sheet
Set Comb = ThisWorkbook.Worksheets("Combine")
'Declare a range object and assign it to contain column names
'from Combine. This range, converted to a list
'below will compare the combined heading names with
'each column heading on each sheet.
Dim rngCombineHeadings As Range
'set combine headings into the range using the function
'EndOfRangeColumn, which is decribed below
Set rngCombineHeadings = EndOfRangeColumn(Comb.Range("A1"))
'Declare a collection to be used in the for loop to compare
'Combine column headings with each source sheets headings
'Only copy those columns that match
Dim colCombinedHeadings As Collection
'Get a collection (aka list of strings) of the column headings
Set colCombinedHeadings = GetCommonHeadings(rngCombineHeadings)
'Declare two ranges to be used as the index inside
'for loops below.
Dim combineColTargetRng As Range
Dim colRng As Range
'Declare a variant to used use the index for looing
'through the Combine sheet headings
Dim vHeading As Variant
'Declare tblRng. It will be set to contain the entire data table
'on each sheet. Row 1 contains the headings, rows 2 - n contain
'the data that may be moved.
Dim tblRng As Range
'This is the range that will be manipulated and copied
'to the Combine sheet
Dim copyRng As Range
'Looping through the worksheets in the workbook
'Index variable used in for each loop below best practice is
'declare you variables near where they are used.
Dim Sht As Worksheet ' Every Sheet on This Workbook
For Each Sht In ThisWorkbook.Worksheets
' ignore Sheet "Combine" and "Val"
If Sht.Name <> "Combine" And Sht.Name <> "Val" Then
'Set the data table to the tblRng object.
Set tblRng = EndOfRangeRow(Sht.Range("A1"))
Set tblRng = EndOfRangeColumn(tblRng)
'For each sheet, loop through each headings on
'the Combined sheet and compare those to the
'headings on the data table on the current sheet
For Each vHeading In colCombinedHeadings
For Each colRng In tblRng.Columns
'if the heading on Combined = the current
'columns heading then, copy the data
'to the combined sheet.
If vHeading = colRng.Value2(1, 1) Then
'Resize the copy range to exclude the heading row
'and to reduce the size by one row, reflecting removal
'of the header row from the range
Set copyRng = ResizeTheRange(colRng.Offset(1, 0))
'Find the column on the Combine sheet that
'matches the current value in vHeading
Set combineColTargetRng = rngCombineHeadings.Find(colRng.Value2(1, 1))
'Copy the current sheet-current column to the clipboard
copyRng.Copy
'The if statement below determines if this is the first
'column of data being copied to the Combine sheet
'if it is, the row 2 current column is empty
'otherwise it has a value and we need to move the paste point
'to the end of the current Combine sheet column
If combineColTargetRng.Offset(1, 0).Value2 = "" Then
Set combineColTargetRng = combineColTargetRng.Offset(1, 0)
Else
Set combineColTargetRng = EndOfRangeRow(combineColTargetRng)
Set combineColTargetRng = _
combineColTargetRng.Offset( _
combineColTargetRng.Rows.Count, 0)
End If
'Paste the values copied from the current sheet
'that are under the same column heading as on the combined sheet
'There are a number of options for pasteSpecial
'See https://learn.microsoft.com/en-us/office/vba/api/excel.range.pastespecial
combineColTargetRng.PasteSpecial Paste:=xlPasteAll
End If
Next
Next
End If
Next
End Sub
'*****************************************************************************
'**
'** This function demonstrates use of the ParamArray. It enables the
'** calling routine, to provide the range as an Excel Range, a Collection
'** an Array, or a list of strings.
'**
'** Calling the Function:
'** Dim aCol as Collection
'** Set aCol = GetCommonHeadings(aCol)
'** Dim rngExcelRange as Range
'** set rngExcelRange = Range("A1:X1")
'** Set aCol = GetCommonHeadings(rngExcelRange)
'** Dim vArr() as Variant
'** vArr = Array("H1", "H2", "H3", "H4")
'** Set aCol = GetCommonHeadings(vArr)
'** Set aCol = GetCommonHeadings("Title1", "Title2", "Title3", "Title4")
Public Function GetCommonHeadings(ParamArray mRange() As Variant) As Collection
'Instantiate the return collection
Dim retVal As New Collection
Dim nDx As Long
If UBound(mRange) < 0 Then
'Cannot do anything without the heading range
Set retVal = Nothing
ElseIf TypeOf mRange(0) Is Range Then
'Heading Range is an Excel Range
Dim rngMaster As Range
Dim colRng As Range
Set rngMaster = mRange(0)
For Each colRng In rngMaster.Columns
retVal.Add colRng.Value2
Next
ElseIf TypeOf mRange(0) Is Collection Then
'Heading Range is a collection of text strings
Set retVal = mRange(0)
ElseIf VarType(mRange(0)) = vbArray + vbVariant Then
'Heading Range passed is an array of strings
Dim varArr() As Variant
varArr = mRange(0)
For nDx = 0 To UBound(varArr)
retVal.Add varArr(nDx)
Next
ElseIf VarType(mRange(0)) = vbString Then
'mRange contains an array of strings
For nDx = 0 To UBound(mRange)
retVal.Add mRange(nDx)
Next
Else
Set retVal = Nothing
End If
Set GetCommonHeadings = retVal
End Function
'****************************************************************************
'**
'** The Functions EndOfRangeColumn, EndOfRangeRow, StartOfRangeColumn, and
'** StartOfRangeRow take one parameter which is an Excel Range. Based on
'** the funtions name it will return the cell that is at the other end.
'** These are just wrappers to make the code more readable. The real work
'** is done by the Private Function GetRangeAtEnd. The private function
'** takes an Excel Range and the direction you want to move.
Public Function EndOfRangeColumn(ByRef mStartOfRange As Range) As Range
Set EndOfRangeColumn = GetRangeAtEnd(mStartOfRange, xlToRight)
End Function
Public Function EndOfRangeRow(ByRef mStartOfRange As Range) As Range
Set EndOfRangeRow = GetRangeAtEnd(mStartOfRange, xlDown)
End Function
Public Function StartOfRangeColumn(ByRef mEndOfRange As Range) As Range
Set StartOfRangeColumn = GetRangeAtEnd(mStartOfRange, xlToLeft)
End Function
Public Function StartOfRangeRow(ByRef mEndOfRange As Range) As Range
Set StartOfRangeRow = GetRangeAtEnd(mStartOfRange, xlUp)
End Function
Private Function GetRangeAtEnd(ByRef mRange As Range, ByVal mDirection As XlDirection) As Range
Set GetRangeAtEnd = Range(mRange, mRange.End(mDirection))
End Function
'***************************************************************
'**
'** The Private Function ResizeTheRange takes an Excel range
'** provide in the parameter. In effect it removes the first
'** row from the provided range, and reduces the size by one.
Private Function ResizeTheRange(ByRef mRange As Range) As Range
Dim retVal As Range
Set retVal = mRange.Offset(1, 0)
Set retVal = retVal.Resize(retVal.Rows.Count - 1, 1)
Set retVal = EndOfRangeRow(retVal)
Set ResizeTheRange = retVal
End Function
Related
I filtered out some of my data using the Autofilter function. As a result, the filtered data consists of a non-contiguous range of cells.
Consequently, for example, when I use the CountIfs function to count the number of 03-In Analysis from Column C that belong to 07-customer noticed from column A, the CountIfs function counts the unfiltered data.
Filtered Data
When I use SpecialCells(xlCellTypeVisible), I get an error due to the non-contiguous range of cells.
Dim sh, ws As Worksheet
Dim count
Dim range1, range2 As Range
Set range1 = ws.Range("A2:A297")
Set range2 = ws.Range("C2:C297")
count = WorksheetFunction.CountIfs(range1, "07-customer noticed", range2, "03-In Analysis")
sh.Range("A1") = count
Arrays work faster for me than worksheet functions.
I tried and tested the code below and it works for me.
Option Explicit
Private Sub Test()
Dim sRange$
Dim count&, iLastUsedRow&, iRow&
Dim aData As Variant
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("B")
With ws
'last used row of data on this sheet
iLastUsedRow = .Range("A" & Rows.count - 1).End(xlUp).Row
'cells containing data
sRange = "A2:C" & iLastUsedRow
'transferring data to array
aData = .Range(sRange)
End With
For iRow = 1 To UBound(aData)
If Range_IsVisibleInWindow(ws.Range("A" & iRow + 1)) Then
If aData(iRow, 1) = "07-customer noticed" And aData(iRow, 3) = "03-In Analysis" Then
count = count + 1
End If
End If
Next
End Sub
I copied this function from here and upvoted their answer. You may want to thank them too in this way, if this works for you?
Function Range_IsVisibleInWindow(ByVal target As Excel.Range) As Boolean
' Returns TRUE if any cell in TARGET (Range) is visible in the Excel window.
'
' Visible means (1) not hidden, (2) does not have row height or column width of
' zero, (3) the view is scrolled so that the Range can be seen by the user at
' that moment.
'
' A partially visible cell will also return TRUE.
If target Is Nothing Then
' Parameter is invalid. Raise error.
Err.Raise 3672, _
"Range_IsVisibleInWindow()", _
"Invalid parameter in procedure 'Range_IsVisible'."
Else
' Parameter is valid. Check if the Range is visible.
Dim visibleWinLarge As Excel.Range
Dim visibleWinActual As Excel.Range
On Error Resume Next
Set visibleWinLarge = Excel.ActiveWindow.VisibleRange ' active window range -INCLUDING- areas with zero column width/height
Set visibleWinActual = visibleWinLarge.SpecialCells(xlCellTypeVisible) ' active window range -EXCLUDING- areas with zero column width/height
Range_IsVisibleInWindow = Not Intersect(target, visibleWinActual) Is Nothing ' returns TRUE if at least one cell in TARGET is currently visible on screen
On Error GoTo 0
End If
End Function
I have data organized into rows and in column B I have data titles. I want to select the data after the titles and then give them range names based on that title. I was able to code a solution that could name column ranges dynamically this way, but when altering it to name the rows of data I run into a 1004 error, specifically at the rng.CreateNames point.
Sub RowNames()
Dim ws As Worksheet, firstCol As Long, lastCol As Long, rowNum As Long, r As Integer, n As Integer, rng As Range, rngName As Range
Set ws = ThisWorkbook.Sheets("MonthlySales")
Set rng = ws.Range("B2:N41")
For n = 1 To rng.Rows.Count
For r = rng.Columns.Count To 1 Step -1
rowNum = rng.Rows(n).Row
firstCol = rng.Columns(1).Column
lastCol = rng.Columns(r).Column
If Cells(firstCol, rowNum).Value <> "" Then
Set rngName = Range(Cells(firstCol, rowNum), Cells(lastCol, rowNum))
rngName.CreateNames Left:=True
Exit For
End If
Next r
Next n
End Sub
Naming Row Ranges
Range.CreateNames Method
Frankly, never heard of it. Basically, in this case, you take a range and write different names in its first column and when you loop through the rows, for each row you write something like Range("A1:D1").CreateNames Left:=True to create a named range whose name is the value in A1 and it will refer to the range B1:D1.
To mix it up, this example (I think OP also) assumes that there might be blank cells in the first column, and the number of cells in each row range may vary. Each row range will be checked backwards for a value which will define its size.
The Code
Option Explicit
Sub RowNames()
' Define worksheet.
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("MonthlySales")
' Define Source Range.
Dim rng As Range
Set rng = ws.Range("B2:N41")
' Define Columns Count.
Dim ColumnsCount As Long
ColumnsCount = rng.Columns.Count
Dim RowRange As Range ' Current Row Range
Dim r As Long ' Source Range Rows Counter
Dim c As Long ' Source Range / Current Row Range Columns Counter
' Loop through rows of Source Range.
For r = 1 To rng.Rows.Count
' Create a reference to the current Row Range.
Set RowRange = rng.Rows(r)
' Check if first cell of current Row Range contains a value,
' making it a possible candidate for a defined name.
If RowRange.Cells(1).Value <> "" Then
' Loop through cells (columns) of current Row Range backwards.
For c = ColumnsCount To 2 Step -1
' Check if current cell in current Row Range contains a value.
If RowRange.Cells(c) <> "" Then
' Create a named range from value in first cell. The range
' is defined from the second cell to to current cell
' in current Row Range.
RowRange.Cells(1).Resize(, c).CreateNames Left:=True
' Exit loop, we got what we came for (the named range).
Exit For
End If
Next c
End If
Next r
End Sub
I have two sheets say (Sheet1)=Sheets("Jan") and sheet2=Sheets("Feb")
I want to copy only that data from range b5:b81 from sheets ("Jan") to sheets("Feb") if it meets the condition in range AN5:AN81.
I am using this code but not working
Sub CopyRows()
Dim Rng As Range
Dim Rng2 As Range
Dim Cl As Range
Dim str As String
Dim RowUpdCrnt As Long
Set Rng = Sheets("Jan").UsedRange 'the range to search ie the used range
Set Rng2 = Sheets("Jan").Range("B")
str = "WRK." 'string to look for
Sheets("Feb").Range("B5:B81").Value = ""
RowUpdCrnt = 5
' In my test data, the "Yes"s are in column AN. This For-Each only selects column AN.
' I assume all your "Yes"s are in a single column. Replace "B" by the appropriate
' column letter for your data.
For Each Cl In Rng.Columns("AN").Rows
If Cl.Text = str Then
'if the cell contains the correct value copy it to next empty row on sheet 2 & delete the row
Cl.cell(2, 5).Copy Sheets("Feb").Cells(RowUpdCrnt, 1)
RowUpdCrnt = RowUpdCrnt + 1
End If
Next Cl
End Sub
I have an excel document with over 50 worksheets all with a similar naming convention.
As this will be so unfriendly for users to navigate to, I wrote a VBA macro which creates a worksheet called summary with the list of all the worksheets hyperlinked in a tabular form with Sheet A B and C as the Column and Sheet 1 and 2 as rows.
Now I am trying to go through each row of a specific column in Sheet 1 and Sheet 2 and look for any reference to SheetB, SheetC and SheetD and for each reference found and I want to mark that creating a matrix.
I am not sure how to achieve this. Any assistance will be much appreciated.
I have managed to search Sheet 1 and 2 for any reference to SheetB as shown below but I am not sure how to update the corresponding cell in my summary sheet.
Function findWord(word As String, wSheet As String) As Boolean
Dim LastRow As Long
Dim i As Long
LastRow = Worksheets(wSheet).Cells(Rows.Count, "D").End(xlUp).Row
For i = LastRow To 1 Step -1
If Worksheets(wSheet).Range("D" & i).Value = word Then
findWord = True
Exit Function
End If
Next i
End Function
For Each wsSheet In wbBook.Worksheets
If (wsSheet.Name <> wsActive.Name) And (Left(wsSheet.Name, 4) <> "fact") Then
For i = 2 To lastColumn
MsgBox wsSheet.Name
If findWord(columnNames(counter2), wsSheet.Name) Then
'Update summary sheet
End If
counter = counter2 + 1
Next i
End If
Next wsSheet
If the result in "Summary sheet" you are looking for is similar to this :
Then you can use something like this (read the comments inside the code for explanations)
Sub MarkReferencesToSheets()
Dim wsSummary As Worksheet 'sheet with summary table matrix
Dim wsSheetRow As Worksheet 'sheets in which we will search references to other sheets
Dim strSheetColumnName As String 'name of the reference we are looking for
Dim intSheetRow As Integer 'for loop purposes
Dim intSheetColumn As Integer 'for loop purposes
Set wsSummary = Sheets("Summary")
For intSheetRow = 2 To 3 'change to suit; headers for rows in summary sheet
Set wsSheetRow = Worksheets(wsSummary.Cells(intSheetRow, 1).Value)
For intSheetColumn = 2 To 4 'change to suit; headers for columns in summary sheet
strSheetColumnName = wsSummary.Cells(1, intSheetColumn) 'name of sheet we are looking for
If Not wsSheetRow.Columns(4).Find(strSheetColumnName) Is Nothing Then 'look only in column "D", or 4
wsSummary.Cells(intSheetRow, intSheetColumn) = "X" ' if we found it, mark it
Else
'if you want something else in the cell when reference is not found, put it here
End If
Next intSheetColumn
Next intSheetRow
End Sub
I'm trying to make a macro in Excel VBA 2007 that searches through the selected field and if it finds a certain string anywhere in a row, it copies and pastes that row into another sheet.
However, I'm getting a compile error: object required right on the sub level (line 1). My code so far is below.
Sub SearchCopyPaste()
'
' SearchCopyPaste Macro
' Searches for a string. If it finds that string in the line of a document then it copies and pastes it into a new worksheet.
'
' Keyboard Shortcut: Ctrl+Shift+W
'
Dim sourceSheet, destinationSheet As Worksheet
Set sourceSheet = Worksheets(1) 'Define worksheets
Set destinationSheet = Worksheets(2)
Dim selectedRange As Range 'Define source range
Set selectedRange = Selection
Dim numRows, numColumns As Integer 'Determine how many rows and columns are to be searched
Set numRows = Range(selectedRange).Rows.Count
Set numColumns = Range(selectedRange).Columns.Count
Set destinationRowCount = 1 'Counter to see how many lines have been copied already
'Used to not overwrite, can be modified to add header,etc
Dim searchString As String 'String that will be searched. Will eventually be inputted
Set searchString = "bccs" 'Will eventually be put into msgbox
For rowNumber = 1 To numRows
If InStr(1, selectedRange.Cells(i, numColumns), searchString) > 0 Then
selectedRange.Cells(rowNumber, numColumns).Copy Destination:=destinationSheet.Range(Cells(destinationRowCount, numColumns))
destinationRowCount = destinationRowCount + 1
Next rowNumber
End Sub
You are using the Set keyword wrong. In your code above you only need to use it to assign Worksheet and Range objects, not for integers and strings. Remove the Set keyword on those lines.
Also, you're missing an End If in your For loop.