VBA Excel Fill Down on multiple sheets - excel

Hi I could not find the answer to my question from searching.
I have multiple worksheets and would like to create a column at the very beginning with a fill-down type approach of a specific string.
For example,
If worksheet name contains "Zebra" - insert a new column at the very beginning and input "Zebra's" across all cells down up until the last data point on the adjacent column.
I need to do this for four different worksheets:
Zebra
Elephant
Rhino
Snake
Here is what I have thus far, I cannot get it to work:
Sub addAnimal()
Dim ws As Worksheet
Dim N As Long
For Each ws In ActiveWorkbook.Worksheets
If ws.Name Like "zebra*" Then
Application.Goto ActiveWorkbook.Sheets(ws.Name).Cells(2, 1)
ActiveCell.EntireColumn.Insert
ActiveCell.Value = "Zebra"
Dim lastRow As Long, lastUsedRow As Long
Dim srcRange As Range, fillRange As Range
With Worksheets(ws.Name)
lastUsedRow = .Range("A" & .Rows.Count).End(xlUp).Row
lastRow = .Range("B" & .Rows.Count).End(xlUp).Row
' Fill values from A:D all the way down to lastUsedRow
Set srcRange = .Range("A" & lastUsedRow)
Set fillRange = .Range("A" & lastRow)
fillRange.Value = srcRange.Value
End With
End If
Next ws

Due to the array of animals compared to the collection of worksheet names there is going to be some repetition but a helper sub procedure can eliminate much of it.
Option Explicit
Sub addAnimalMain()
Dim w As Long, grr As Variant
grr = Array("Zebra", "Elephant", "Rhino", "Snake")
For w = 1 To ThisWorkbook.Worksheets.Count
With ThisWorkbook.Worksheets(w)
Select Case True
Case CBool(InStr(1, .Name, grr(0), vbTextCompare))
addAnimalHelper ThisWorkbook.Worksheets(w), grr(0)
Case CBool(InStr(1, .Name, grr(1), vbTextCompare))
addAnimalHelper ThisWorkbook.Worksheets(w), grr(1)
Case CBool(InStr(1, .Name, grr(2), vbTextCompare))
addAnimalHelper ThisWorkbook.Worksheets(w), grr(2)
Case CBool(InStr(1, .Name, grr(3), vbTextCompare))
addAnimalHelper ThisWorkbook.Worksheets(w), grr(3)
End Select
End With
Next w
End Sub
Sub addAnimalHelper(ws As Worksheet, grrr As Variant)
With ws
.Columns(1).EntireColumn.Insert
.Range(.Cells(1, "A"), .Cells(.Rows.Count, "B").End(xlUp).Offset(0, -1)) = grrr
End With
End Sub

Related

I would like to detect the first and last column containing a specific value

I'm new to VBA.
I would like to detect the first and last column containing the value "FMD 1991", because I need to copy paste the value of each cells below cells containing the "FMD 1991 value" in a destination sheet.
Here's what I've done
Private Sub CommandButton1_Click()
Dim FMD91 As String
Dim FMD97 As String
Dim FMD13 As String
Dim IECMIL As String
Dim MIL As String
Dim i As Integer
Dim firstcol
Dim finalcol As Integer
FMD91 = "FMD 1991"
Worksheets("FailureModeDistribution_FMD").Select
firstcol = Find(what:="FMD 1991", lookat:=xlWhole, searchorders:=xlByColumns)
finalcol = Find(what:="FDM 1991", lookat:=xlWhole, searchdirection:=xlPrevious)
For i = 2 To finalcol
If Cells(2, i) = FMD91 Then
Range(Cells(2, i)).Copy
FeuilleDonnees.Select
Range("A2").End(xlToRight).PasteSpecial xlPasteFormulasAndNumberFormats
End If
Next i
End Sub
May someone help me with that please?
Please, test the next code. It assumes that there can be more occurrences of the string to be searched for. If only two, the code can be simplified, replacing the iteration with a single code line:
Private Sub CommandButton1_ClickSSS()
Dim sh As Worksheet, shDest As Worksheet, FMD91 As String, firstRng As Range
Dim lastRng As Range, mtch, prevMtch, i As Long
Set sh = ActiveSheet 'Worksheets("FailureModeDistribution_FMD")
Set shDest = sh.Next 'Use your destination sheet (FeuilleDonnees)
FMD91 = "FMD 1991"
mtch = Application.match(FMD91, sh.rows("1:1"), 0)
If IsError(mtch) Then
MsgBox "No any match for " & FMD91 & " in the first row...": Exit Sub
Else
prevMtch = mtch
End If
Set firstRng = sh.Range(sh.cells(2, mtch), sh.cells(sh.rows.count, mtch).End(xlUp)) 'set the first range to be copyed
For i = mtch To sh.UsedRange.Columns.count 'iterate between the rest of columns (in case of more occurrences):
mtch = Application.match(FMD91, sh.Range(sh.cells(1, prevMtch + 1), sh.cells(1, sh.UsedRange.Columns.count)), 0)
If IsNumeric(mtch) Then 'set all occurences as the last range to be copied
Set lastRng = sh.Range(sh.cells(2, mtch + prevMtch), sh.cells(sh.rows.count, mtch + prevMtch).End(xlUp))
prevMtch = prevMtch + mtch
Else
Exit For 'exit the loop and use the last set lastRng
End If
Next i
If lastRng Is Nothing Then MsgBox "No secong match for " & FMD91 & " could be found in the first row...": Exit Sub
'copying the ranges:
firstRng.Copy: shDest.Range("A2").End(xlToRight).Offset(0, 1).PasteSpecial xlPasteFormulasAndNumberFormats
lastRng.Copy: shDest.Range("A2").End(xlToRight).Offset(0, 1).PasteSpecial xlPasteFormulasAndNumberFormats
End Sub
Please, take care of using your real sheets to set sh and shDest. I used ActiveSheet and ActiveSheet.Next only to test the above code.
If only two occurrences of the string to be searched for, please state it and I will simplify the code. It will work with only two occurrences, too. If only one may exist, it can also be adapted to process only that one.
It will return in the next empty column of shDest.

Excel VBA Multiple Sheet Search using Data from one Column

I am trying to search for values listed in a column from multiple sheets in my excel workbook. If excel finds a match I would like it to return sheet names of the tabs that had the value.
Here is what i have done so far. I decided to start off by using one keyword to search multiple tabs, copy and paste the sheet name. The code below only paste the first resulting sheet name when there are other sheets containing the same keyword. I would like to know how i can pull the other sheet names that contain the same keyword.
I would also like to know how i can set up the keyword to use information in Column A of the Field List.
Sub FinalAppendVar()
Dim ws As Worksheet
Dim arr() As String
Keyword = "adj_veh_smart_tech_disc"
Totalsheets = Worksheets.Count
For i = 1 To Totalsheets
If Worksheets(i).Name <> "Main" Or InStr(1, Worksheets(i).Name, " Checks") Or Worksheets(i).Name
<>_ "Field Lists" Then
lastrow = Worksheets(i).Cells(Rows.Count, 4).End(xlUp).Row
For j = 2 To lastrow
If Worksheets(i).Cells(1, 3).Value = Keyword Then
Worksheets("Field Lists").Activate
lastrow = Worksheets("Field Lists").Cells(Rows.Count, 4).End(xlUp).Row
Worksheets("Field Lists").Cells(lastrow + 1, 5).Value = Worksheets(i).Name
Worksheets("Field Lists").Cells(lastrow + 2, 5).Value = Worksheets(i).Name
End If
Next
End If
Next
End Sub
The following code should work for what you described.
A couple feedback items:
Tabbing out loops and if statements significantly improves code readability
Never reuse variable names (i.e. lastrow), it makes it hard to read and can cause issues that are difficult to find later on
Follow all Next with the loop variable (i.e. Next i), this improves readability and helps you keep track of the ends of loops
.Activate and .Select are generally never required in vba, its better to be explicit in what you are referencing
Sub FinalAppendVar()
Dim searchSheet As Excel.Worksheet
Dim pasteSheet As Excel.Worksheet
Dim keyword As String
Dim lastSearchRow As Integer
Dim lastPasteRow As Integer
' set the worksheet to paste to
Set pasteSheet = ThisWorkbook.Worksheets("Field Lists")
' set keyword to look for
keyword = "adj_veh_smart_tech_disc" '<-- manual entry
'keyword = pasteSheet.Range("A1").Value '<-- use value in cell A1 on the defined pasteSheet
' loop through all sheets in the workbook
For i = 1 To ThisWorkbook.Worksheets.Count
' set the current worksheet we are looking at
Set searchSheet = ThisWorkbook.Worksheets(i)
' check if the current sheet is one we want to search in
If searchSheet.Name <> "Main" Or InStr(1, searchSheet.Name, " Checks") Or searchSheet.Name <> "Field Lists" Then
' current worksheet is one we want to search in
' find the last row of data in column D of the current sheet
lastSearchRow = searchSheet.Cells(1048576, 4).End(xlUp).Row
' loop through all rows of the current sheet, looking for the keyword
For j = 2 To lastSearchRow
If searchSheet.Cells(j, 3).Value = keyword Then
' found the keyword in row j of column C in the current sheet
' find the last row of column D in the paste sheet
'lastPasteRow = pasteSheet.Cells(1048576, 4).End(xlUp).Row
lastPasteRow = pasteSheet.Cells(1048576, 5).End(xlUp).Row '<-- update based on OPs comment
' paste the name of the current search sheet to the last empty cell in column E
pasteSheet.Cells(lastPasteRow + 1, 5).Value = searchSheet.Name
' not sure if the next line is needed, looks like it pastes again immediately below the previous
pasteSheet.Cells(lastPasteRow + 2, 5).Value = searchSheet.Name
' to save time consider exiting the search in the current sheet since the keyword was just found
' this will move to the next sheet immediately and not loop through the rest of the rows on the current
' search sheet. This may not align with the usecase so it is currently commented out.
'Exit For '<--uncomment this to move to the next sheet after finding the first instance of the keyword
Else
' the keyoword was not in row j of column C
' do nothing
End If
Next j
Else
' current sheet is one we don't want to search in
' do nothing
End If
Next i
End Sub
Please try this variant (Don't worry that the code is so long - the longer the programmer thought and the more wrote, the better the program works ... usually it is):
Option Explicit
Sub collectLinks()
Const LIST_SHEET_NAME As String = "Field Lists"
Dim wsTarget As Worksheet
Dim wsEach As Worksheet
Dim keywordCell As Range
Dim sKeyword As String
Dim linkCell As Range
Dim aFound As Range
Dim aCell As Range
On Error Resume Next
Set wsTarget = ActiveWorkbook.Worksheets(LIST_SHEET_NAME)
On Error GoTo 0
If wsTarget Is Nothing Then
MsgBox "'" & LIST_SHEET_NAME & "' not exists in active workbook", vbCritical, "Wrong book or sheet name"
Exit Sub
End If
Rem Clear all previous results (from column B to end of data)
wsTarget.UsedRange.Offset(0, 1).ClearContents
Rem Repeat for each cell of column A in UsedRange:
For Each keywordCell In Application.Intersect(wsTarget.UsedRange, wsTarget.Columns("A")) ' It can be changed to "D", "AZ" or any other column
sKeyword = keywordCell.Text
If Trim(sKeyword) <> vbNullString Then
Application.StatusBar = "Processed '" & sKeyword & "'"
Set linkCell = keywordCell
For Each wsEach In ActiveWorkbook.Worksheets
If wsEach.Name <> LIST_SHEET_NAME Then
Application.StatusBar = "Processed '" & sKeyword & "' Search in '" & wsEach.Name & "'"
Set aFound = FindAll(wsEach.UsedRange, sKeyword)
If Not aFound Is Nothing Then
For Each aCell In aFound
Set linkCell = linkCell.Offset(0, 1) ' Shift to rught, to the next column
linkCell.Formula2 = "=HYPERLINK(""#" & aCell.Address(False, False, xlA1, True) & """,""" & _
aCell.Worksheet.Name & " in cell " & aCell.Address(False, False, xlA1, False) & """)"
Next aCell
End If
End If
Next wsEach
End If
Next keywordCell
Application.StatusBar = False
Rem Column width
wsTarget.UsedRange.Columns.AutoFit
End Sub
Function FindAll(SearchRange As Range, FindWhat As Variant) As Range
Dim FoundCell As Range
Dim FirstFound As Range
Dim LastCell As Range
Dim ResultRange As Range
Dim Area As Range
Dim MaxRow As Long
Dim MaxCol As Long
For Each Area In SearchRange.Areas
With Area
If .Cells(.Cells.Count).Row > MaxRow Then
MaxRow = .Cells(.Cells.Count).Row
End If
If .Cells(.Cells.Count).Column > MaxCol Then
MaxCol = .Cells(.Cells.Count).Column
End If
End With
Next Area
Set LastCell = SearchRange.Worksheet.Cells(MaxRow, MaxCol)
Rem If your keyword can be a part of cell then change parameter xlWhole to xlPart:
Set FoundCell = SearchRange.Find(FindWhat, LastCell, xlValues, xlWhole, xlByRows)
If Not FoundCell Is Nothing Then
Set FirstFound = FoundCell
Do Until False ' Loop forever. We'll "Exit Do" when necessary.
If ResultRange Is Nothing Then
Set ResultRange = FoundCell
Else
Set ResultRange = Application.Union(ResultRange, FoundCell)
End If
Set FoundCell = SearchRange.FindNext(after:=FoundCell)
If (FoundCell Is Nothing) Then
Exit Do
End If
If (FoundCell.Address = FirstFound.Address) Then
Exit Do
End If
Loop
End If
Set FindAll = ResultRange
End Function
You can see how it works in this demo workbook - Create Links To Keywords.xlsm
EDIT By the way, the second part of this code, the FindAll() function, is a slightly shortened version of the Chip Pearson macro. Keep this link for yourself, there are many useful things to help you in future development.

Script to Copy and paste entirerows and mergedrows?

The following code is the one that I'm trying to work with, but I still can't make it work with merge rows. The main idea is to create a loop to check each row from D1:D150 and if the criteria are met then copy the entire row.
This is how my data looks like
Sub attributes()
'--------------------------------------------------------------------
Dim Cel, aCell1, aCell2, aCell3, aCellAsses As Range, ws, ws0 As Worksheet
Dim strAsses1 As Boolean
Dim num As Integer
'------------------------------
Set ws = ActiveWorkbook.Sheets("Contract Attributes")
Set ws0 = ActiveWorkbook.Sheets("ReviewerTab")
ws.Activate
Set aCell1 = ActiveWorkbook.Sheets("Contract Attributes").Range("A1:A150")
'Set aCell1 = ActiveWorkbook.Sheets("Contract Attributes").Range("D1:D150")
'Set aCell2 = ActiveWorkbook.Sheets("Contract Attributes").Range("D:D").Find("Current Modifications", LookIn:=xlValues)
'--------------------------------------------------------------------
strName1 = InputBox("Which contract modification would you like to review?")
num = 5
For Each Cel In aCell1
If InStr(1, Cel, strName1, vbTextCompare) > 0 Or InStr(1, Cel, "x") > 0 Then
Cel.MergeArea.Select
Selection.EntireRow.Copy
ws0.Activate
Rows(num).Insert
ws.Activate
num = num + 1
End If
Next Cel
'--------------------------------------------------------------------
'ws0.Columns(4).Delete
'aCell2.Select
'ActiveCell.EntireRow.Copy
'Sheets("ReviewerTab").Range("A5").Insert
End Sub
TIPS
To begin with, I would recommend that you see How to avoid using Select in Excel VBA. Next you need to identify the range object that you need to copy and then copy them across.
Dim Cel, aCell1, aCell2, aCell3, aCellAsses As Range You need to declare them explicitly else the first four objects are declared as Variant and not Range. For example Dim Cel As Range, aCell1 As Range, aCell2 As Range, aCell3 As Range, aCellAsses As Range
Do not copy the rows in a loop. It will be slow. Identify the rows you want to copy and then copy them in one go. Below is an example
SAMPLE SCENARIO
To demonstrate how this works, I am taking the below sample.
CODE
I have come up with a basic code. I have commented it so you should not have a problem understanding it. But if you do then feel free to ask :).
Option Explicit
Sub Sample()
Dim wsInput As Worksheet
Dim wsOuput As Worksheet
Dim RangeToCopy As Range
Dim lRow As Long, i As Long, num As Long
Dim searchText As Variant
'~~> Row in output sheet where the rows will be copied
num = 5
'~~> Set your input and output sheets
Set wsInput = ThisWorkbook.Sheets("Contract Attributes")
Set wsOuput = ThisWorkbook.Sheets("ReviewerTab")
'~~> Take the input from the user
searchText = InputBox("Which contract modification would you like to review?")
If Len(Trim(searchText)) = 0 Then Exit Sub
With wsInput
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Loop through the cells and check for criteria
For i = 1 To lRow
If InStr(1, .Range("A" & i).Value2, searchText, vbTextCompare) Then
'~~> identify the rows you need to copy and store them
'~~> in a range object
If RangeToCopy Is Nothing Then
Set RangeToCopy = .Range("A" & i).MergeArea.EntireRow
Else
Set RangeToCopy = Union(RangeToCopy, .Range("A" & i).MergeArea.EntireRow)
End If
End If
Next i
End With
'~~> Copy them across. You can insert them as well
If Not RangeToCopy Is Nothing Then
RangeToCopy.Copy wsOuput.Rows(num)
End If
End Sub
IN ACTION
You need to include the merge area before "Select".
After you copy the rows, you need to count how many merged rows in the copy. I add a new variable num2 to do so. The loop cannot just simply num=num+1, it varies from what rows copied.
You may try the below code.
Sub attributes()
'--------------------------------------------------------------------
Dim Cel, aCell1, aCell2, aCell3, aCellAsses As Range, ws, ws0 As Worksheet
Dim strAsses1 As Boolean
Dim num As Integer
Dim num2 As Integer
Set ws = ActiveWorkbook.Sheets("Contract Attributes")
Set ws0 = ActiveWorkbook.Sheets("ReviewerTab")
ws.Activate
Set aCell1 = ActiveWorkbook.Sheets("Contract Attributes").Range("A1:A150")
strName1 = InputBox("Which contract modification would you like to review?")
num = 5
For Each Cel In aCell1
If InStr(1, Cel, strName1, vbTextCompare) > 0 Or InStr(1, Cel, "x") > 0 Then
Range(Cells(Cel.Row, 1), Cells(Cel.Row, Cells(Cel.Row, Columns.Count).End(xlToLeft).Column)).Select
num2 = Selection.Rows.Count
Selection.EntireRow.Copy
ws0.Activate
Rows(num).Insert
ws.Activate
num = num + num2
End If
Next Cel
End Sub

Re-run the same macro until last row of data

I'm a beginner. Just learning by Googleing, but cannot find a solution for this. Please help.
I want to run the below macro.
I have multiple cells named "CV_=CVCAL" in the same column.
What I want is for the macro to find the first cell with the value "CV_=CVCAL" and offset to the adjacent cell. If the adjacent cell has a particular value, if the value is below lets say "1.5" i want to fill it will a cell style 'bad'.
I want the macro to go through all the cells that have the name CV_=CVCAL and do the same thing until there is no more cells named CV_=CVCAL.
Sub If_CV()
Range("A1").Select
Set FoundItem = Range("C1:C1000").Find("CV_=CVCAL")
FoundItem.Offset(columnOffset:=1).Select
If ActiveCell.Value >= 1.5 Then
ActiveCell.Style = "Bad"
End If
End Sub
Sounds like you want to loop through your values.
Determine the end of your range
Loop through your range and check your criteria
Sub If_CV()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim lr As Long, i As Long
lr = ws.Range("C" & ws.Rows.Count).End(xlUp).Row
For i = 2 To lr
If ws.Range("C" & i) = "CV_=CVCAL" Then
If ws.Range("D" & i) >= 1.5 Then
ws.Range("D" & i) = "Bad"
End If
End If
Next i
End Sub
A basic loop would be simpler:
Sub If_CV()
Dim c As Range, ws As Worksheet
For Each ws in ActiveWorkbook.Worksheets
For Each c in ws.Range("C1:C1000").Cells
If c.Value = "CV_=CVCAL" Then
With c.offset(0, 1)
If .Value >= 1.5 Then .Style = "Bad"
End With
End If
Next ws
Next c
End Sub

Named Range Can't Be Deleted After Small Code Change

I recently split my code into two sections to stop the date automatically being entered, as on a Monday, we need to do 3 days worth of data
All I did was Add a new sub and redefine the variables - now i can't delete a named range
My code:
Option Explicit
Sub Import()
Dim ws As Worksheet, lastRowC As Long
Set ws = Worksheets("Report")
lastRowC = ws.Cells(ws.Rows.Count, 3).End(xlUp).Row + 1 ' bottom populated cell of Column "C", plus 1
With ws.QueryTables.Add(Connection:= _
"TEXT;N:\Operations\001 Daily Management\Cemex\FMSQRY.CSV", Destination:= _
ws.Cells(lastRowC, 3))
.Name = "FMSQRY"
' etc
' etc
.Refresh BackgroundQuery:=False
End With
With ActiveWorkbook
.Connections("FMSQRY").Delete
.Names("FMSQRY").Delete
End With
End Sub
Sub TodaysDate()
Dim ws As Worksheet, lastRowC As Long, lastRowH As Long
Set ws = Worksheets("Report")
lastRowH = ws.Cells(ws.Rows.Count, 8).End(xlUp).Row + 1 ' bottom populated cell of Column "H", plus 1
lastRowC = ws.Cells(ws.Rows.Count, 3).End(xlUp).Row ' bottom populated cell of Column "C"
With ws.Range(ws.Cells(lastRowH, 8), ws.Cells(lastRowC, 8))
.FormulaR1C1 = "=TODAY()"
.Value = .Value
End With
End Sub
So nothing to do with the Named Range was actually touched
.Name = "FMSQRY" still names my range, but when .Names("FMSQRY").Delete comes around I get a 1004 Error
ANSWER:
With ActiveWorkbook
.Connections("FMSQRY").Delete
With ws
.Names("FMSQRY").Delete
End With
End With
Your Name is on sheet-level and not Workbook-level.(you could have the same name on different sheets)
so:
ActiveWorkbook.Worksheets("Report").Names("FMSQRY").Delete
I am not sure why that code doesn't work.
But if you write code like below then it works...
Dim nm As Name
For Each nm In ActiveWorkbook.Names
If nm.Name = "FMSQRY" Then nm.Delete
Next nm
Try the below code without the .connections:
Option Explicit
Sub test()
With ThisWorkbook
.Names("FMSQRY").Delete
End With
End Sub

Resources