I am currently using this code which goes through my worksheet and checks in the range O15:O300 to see if there are any cells that match the current date. If there is then it copies the entire row to worksheet "Today's Actions" then copies the site number (Situated in cell C3) to column AA in "Todays Actions".
I use the below code which works fine for this task for one specific sheet:
Sub rangecheck()
Application.ScreenUpdating = False
For Each cell In Range("O15:O300")
If cell.Value = Date Then
matchRow = cell.Row
Rows(matchRow & ":" & matchRow).Select
Selection.Copy
Sheets("Today's Actions").Range("A" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
ActiveSheet.Range("C3").Copy
Sheets("Today's Actions").Range("AA" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
End If
Next
Application.ScreenUpdating = True
End Sub
However, there are multiple sheets that I need to action this code for. So I use the below code to run this across all sheets:
Sub rangecheck_Set()
Dim ws As Worksheet
Dim starting_ws As Worksheet
Set starting_ws = ActiveSheet
Application.ScreenUpdating = False
For Each ws In ThisWorkbook.Worksheets
ws.Activate
Call rangecheck
Next
starting_ws.Activate 'activate the worksheet that was originally active ("Today's Actions")
Application.ScreenUpdating = True
End Sub
This issue I'm having is that it seems to work fine but randomly whenever there are a lot of dates that match todays date in range O15:O300, it duplicates some lines up to or slightly exceeding 300 rows (So as an example, if there were 15 rows that 'should' be brought back to "Today's action" tab, it would bring them back but then have a few other rows randomly duplicated down to around row 300).
I get this might be due to the range going down to 300 but I even edited the range to go to 'last row' and it still brings back the same issue. Any thoughts? I've been trying to solve this for days now. Any help appreciated
Don't use implicit references to worksheets and ranges. It is most likely that this is the reason for your problem.
Also you don't need to select and copy - another source for unforeseeable errors.
Another reason for your error could be that you don't exclude "Today's Actions"-sheet from the copying routine.
I re-wrote your sub that is copying the data:
Sub copyTodaysRows(wsSource As Worksheet, wsTarget As Worksheet)
If wsSource is wsTarget then Exit Sub 'don't run this for the target sheet
Dim c As Range, wsTargetNewRow As Long
For Each c In wsSource.Range("O15:O300")
If c.Value = Date Then
With wsTarget
wsTargetNewRow = .Range("A" & .Rows.Count).End(xlUp).Row + 1
c.EntireRow.Copy Destination:=.Range("A" & wsTargetNewRow)
.Range("AA" & wsTargetNewRow).Value = wsSource.Range("C3").Value
End With
End If
Next
End Sub
It takes the source sheet and the target sheet as input parameters.
You will call it like this within your "outer" routine:
Sub rangecheck_Set()
Application.ScreenUpdating = False
Dim wsSource as worksheet
Dim wsTarget as worksheet
Set wsTarget = Thisworkbook.Worksheets("Today's Actions")
For Each wsSource In ThisWorkbook.Worksheets
copyTodaysRows wsSource, wsTarget
Next
Application.ScreenUpdating = True
End Sub
Copy Values of Criteria (Dates) Rows From Multiple Worksheets
Option Explicit
Sub RetrieveTodaysActions()
' Calls 'RetrieveTodaysActionsCall'.
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sws As Worksheet
For Each sws In ThisWorkbook.Worksheets
RetrieveTodaysActionsCall sws
Next sws
MsgBox "Today's actions retrieved.", vbInformation
End Sub
Sub RetrieveTodaysActionsCall(ByVal sws As Worksheet)
' Define constants.
' Source
Const sCriteriaColumnAddress As String = "O15:O300"
Const sCol1 As String = "A"
Const sCell2Address As String = "C3"
' Destination
Const dName As String = "Today's Actions"
Const dCol1 As String = "A"
Const dCol2 As String = "AA"
' Both
' Write the criteria date to a variable ('CriteriaDate').
Dim CriteriaDate As Date: CriteriaDate = Date ' today
' Exclude the destination worksheet.
If StrComp(sws.Name, dName, vbTextCompare) = 0 Then Exit Sub
' Reference the source criteria column range ('scrg').
Dim scrg As Range: Set scrg = sws.Range(sCriteriaColumnAddress)
' Check the number of matches, the number of rows to be copied
' to the destination worksheet.
If Application.CountIf(scrg, Date) = 0 Then Exit Sub
' Reference the range ('surg'), the range from the first cell
' in the source column ('sCol1') to the last cell of the used range.
Dim surg As Range
With sws.UsedRange
Set surg = sws.Range(sCol1 & 1, .Cells(.Rows.Count, .Columns.Count))
End With
' Reference the source range ('srg').
Dim srg As Range: Set srg = Intersect(scrg.EntireRow, surg)
If srg Is Nothing Then Exit Sub
' Write the number of columns of the source range to a variable (cCount).
Dim cCount As Long: cCount = srg.Columns.Count
' Write the criteria column number to a variable ('CriteriaColumn').
Dim CriteriaColumn As Long: CriteriaColumn = scrg.Column
' Write the values from the source range to an array ('Data').
Dim Data() As Variant: Data = srg.Value
Dim sValue As Variant ' Criteria Value in the Current Source Row
Dim sr As Long ' Current Source Row
Dim c As Long ' Current Source/Destination Column
Dim dr As Long ' Current Destination Row
' Loop through the rows of the array.
For sr = 1 To UBound(Data, 1)
' Write the value in the current row to a variable.
sValue = Data(sr, CriteriaColumn)
' Check if the current value is a date.
If IsDate(sValue) Then
' Check if the current value is equal to the criteria date.
If sValue = CriteriaDate Then
dr = dr + 1
' Write the values from the source row to the destination row.
For c = 1 To cCount
Data(dr, c) = Data(sr, c)
Next c
End If
End If
Next sr
' Reference the destination worksheet ('dws').
Dim dws As Worksheet: Set dws = sws.Parent.Worksheets(dName)
' Reference the destination first cell ('dfCell').
Dim dfCell As Range
Set dfCell = dws.Cells(dws.Rows.Count, dCol1).End(xlUp).Offset(1)
' Reference the destination range ('drg').
Dim drg As Range: Set drg = dfCell.Resize(dr, cCount)
' Write the values from the array to the destination range.
drg.Value = Data
' Reference the destination range 2 ('drg2').
Dim drg2 As Range: Set drg2 = drg.EntireRow.Columns(dCol2)
' Write the source cell 2 value to the destination range 2 ('drg2')
' (the same value to all cells of the range).
drg2.Value = sws.Range(sCell2Address).Value
End Sub
My process was different from the other responses, so I will still post it. I have also added a way of logging that a row has been logged because otherwise I saw that rows could be duplicated to the "Today's Actions" sheet.
Sub rangecheck(ByVal checkedSheet As Worksheet)
'#PARAM checkedSheet is the sheet to iterate through for like dates.
'Instantiate counter variables
Dim matchRow As Integer
matchRow = 0
Dim pasteRow As Integer
pasteRow = 0
Application.ScreenUpdating = False
For Each cell In checkedSheet.Range("O15:O300")
If cell.Value = Date Then
matchRow = cell.Row
'Checks if the row has been logged already (I use column "A" because I
'have no data in it, but this can be amy column in the row)
If checkedSheet.Cells(matchRow, 1) = "Logged" Then
'Do nothing
Else
'Sets value of "pasteRow" to one lower than the lowest used row in
column "AA"
pasteRow = Sheets("Today's Actions").Cells(Rows.Count,
27).End(xlUp).Row + 1
'Copies the values of the matchRow to the pasteRow
Sheets("Today's Actions").Rows(pasteRow).Value =
checkedSheet.Rows(matchRow).Value
'Copies the value of the Site Number to the paste row column "AA"
Sheets("Today's Actions").Cells(pasteRow, 27).Value =
checkedSheet.Cells(3, 3).Value
'Log that a row has been added to the "Today's Actions" sheet
checkedSheet.Cells(matchRow, 1) = "Logged"
End If
End If
Next
Application.ScreenUpdating = True
End Sub
I have also modifed your sub which calls the copying sub to check if it is trying to copy the "Today's Actions" sheet.
Sub rangecheck_Set()
Dim ws As Worksheet
Dim starting_ws As Worksheet
Set starting_ws = Worksheets("Today's Actions")
Application.ScreenUpdating = False
For Each ws In ThisWorkbook.Worksheets
'Check if the ws to check is "Today's Actions"
If ws.Name = "Today's Actions" Then
'Do Nothing
Else
Call rangecheck(ws)
End If
Next
starting_ws.Activate 'activate the worksheet that was originally active
Application.ScreenUpdating = True
End Sub
Related
I am trying to learn vba and I have this list. For every NAME there will be an individual worksheet created. If the name of the worksheet match the name on the list I need to put the length in cell J1. I tried to use if ... elseif ... but knowing I have 430 different names the code will be too long what should I do? What alternative code can I use?
Sub length()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
If ws.Name = "A" Then
ws.Activate
Range("J1").Select
ActiveCell.FormulaR1C1 = 4153
ElseIf ws.Name = "B" Then
ws.Activate
Range("J1").Select
ActiveCell.FormulaR1C1 = 2273
Next
MsgBox "DONE"
End Sub
Sub length()
Dim Cell as Range
For Each Cell In Range("Name")
On Error Resume Next
ThisWorkbook.Sheets(Cell.Value).Range("J1") = Cell.Offset(0, 1)
If Err.Number <> 0 Then Debug.Print "Sheet " & Cell & " wasn't found"
On Error GoTo 0
Next Cell
MsgBox "DONE"
End Sub
Range("Name") needs to be changed to refer to the real range with the sheet names.
On Error ... can be removed if you're sure that all mentioned worksheets exist.
Write Values to Worksheets From a List
This will loop through the list of names. There may be worksheets whose names are not on the list.
Adjust the name of the worksheet containing the list (Sheet1) and the column (A) and row (2) of the first name.
Option Explicit
Sub CopyLengths()
' Reference the workbook ('wb').
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the source worksheet ('sws').
Dim sws As Worksheet: Set sws = wb.Worksheets("Sheet1")
' Calculate the last row ('slRow'), the row of the last non-empty cell
' in the worksheet names column.
Dim slRow As Long: slRow = sws.Cells(sws.Rows.Count, "A").End(xlUp).Row
' Reference the source range ('srg'), the one-column range
' containing the worksheet names.
Dim srg As Range: Set srg = sws.Range("A2", sws.Cells(slRow, "A"))
Dim dws As Worksheet ' Destination Worksheet
Dim sCell As Range ' Current Source (Name) Cell
Dim sString As String ' Current Source String
' Copy lenghts.
' Loop through the cells ('sCell') of the source range...
For Each sCell In srg.Cells
' Convert the current cell's value to a string ('CStr')
' and write the string to a variable ('sString').
sString = CStr(sCell.Value)
' Check if the string is not an empty string...
If Len(sString) > 0 Then ' the cell is not blank
' Attempt to reference the worksheet named after the string.
On Error Resume Next
Set dws = wb.Worksheets(sString)
On Error GoTo 0
If Not dws Is Nothing Then ' worksheet found (referenced)
' Copy (write) the length.
dws.Range("J1").Value = sCell.Offset(, 1).Value
Set dws = Nothing ' reset the variable
'Else ' worksheet not found (not referenced); do nothing or e.g. ...
'Debug.Print "Worksheet '" & sString & "' not found"
End If
'Else ' the cell is blank; do nothing
End If
Next sCell
' Inform.
MsgBox "Lengths copied.", vbInformation
End Sub
Sub UpdateSheets()
Dim vTable As Variant
Dim iRow As Long
' SHEET_NAME: The sheets name containing the table: Name, LENGTH
' RANGE: the range where the sheet names are listed (without the header)
' vTable: the table with 'Name' & 'LENGTH' column values in memory
vTable = ThisWorkbook.Worksheets("SHEET_NAME").Range("I2:J7").Value2
For iRow = LBound(vTable, 1) To UBound(vTable, 1) ' iRow: current row in table
' vTable(iRow, 1): 'Name' column value
' vTable(iRow, 2): 'LENGTH' column value
On Error Resume Next
ThisWorkbook.Worksheets(CStr(vTable(iRow, 1))).Range("J1").Value2 = vTable(iRow, 2)
On Error GoTo 0
If Err.Number <> 0 Then
' DO SOMETHING IF ERROR IS THROWN (CREATE MISSING WORKSHEET)
Err.Clear
End If
Next iRow
MsgBox "DONE"
End Sub
If you have any questions don't hesitate!
Cheers, Peter.
Assuming that the columns are as on the image, you can do this:
Sub fnPickLength()
Dim ws As Excel.Worksheet
Dim oCell As Excel.Range
Dim oRng As Excel.Range
Set oRng = Range("rngTheNames") 'name the range with this name
For Each ws In ThisWorkbook.Worksheets
For Each oCell In oRng
If UCase(ws.Name) = UCase(oCell.Value) Then
ws.Activate
Range("J1").Select
ActiveCell.FormulaR1C1 = oCell.Offset(0, 1).Value
Exit For
End If
Next
Next
MsgBox "DONE"
End Sub
Please adapt the named range as you want. I've choosen as "rngTheNames".
The ws name is compared with each oCell value. If matched, the Offset property reads the sibling cell of the evaluated name and put its value on the ws.
I've been struggling for a while with this:
I'm trying to write a script that copies over a whole row from a sheet called 'search' into a sheet called 'order' at the click of a button. Based on if there is a value entered into that row in column M.
I have written the if statement so that it pulls the rows over that have a value bigger than 0.
However - it always pulls in ONLY the top lines of the 'search' source database - never the ones that have a value in.
The data in the source are all formulas - could this be an issue? Otherwise is there a way to copy and paste data as values?
For instance - in the below picture I want to pull over ID 1359399 and 1359403. But it will always pull over the top two lines (1359394 and 1359395).
Thanks for any help.
Sub CopySomeCells()
Dim SourceSheet As Worksheet
Dim DestinationSheet As Worksheet
Dim SourceRow As Long
Dim DestinationRow As Long
Set SourceSheet = ActiveWorkbook.Sheets("Search")
Set DestinationSheet = ActiveWorkbook.Sheets("Order")
DestinationRow = DestinationSheet.Cells(DestinationSheet.Rows.Count, 2).End(xlUp).Row + 1
For SourceRow = 2 To SourceSheet.UsedRange.Rows.Count
If SourceSheet.Range("M" & SourceRow).Value > 0 Then
SourceSheet.Range(SourceSheet.Cells(SourceRow, 1), SourceSheet.Cells(SourceRow, 29)).Copy _
DestinationSheet.Cells(DestinationRow, 2)
DestinationRow = DestinationRow + 1
End If
Next SourceRow
Range("M2:M7000").Clear
End Sub
Copy Criteria Rows Using AutoFilter
Copies rows of data that meet a criterion in a column, to another worksheet.
Option Explicit
Sub CopySomeRows()
' Source
Const sName As String = "Search"
Const sCol As Long = 13 ' M
Const sCriteria As String = ">0" ' or "<>" for not blank, ' or "=" for blank
' Destination
Const dName As String = "Order"
Const dfCol As Long = 2 ' B
' Both
Const cCount As Long = 29
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
If sws.AutoFilterMode Then sws.AutoFilterMode = False
Dim strg As Range ' Source Table Range (headers)
Set strg = sws.Range("A1").CurrentRegion.Resize(, cCount)
If strg.Rows.Count = 1 Then Exit Sub ' no data or just headers
Dim sdrg As Range ' Source Data Range (no headers)
Set sdrg = strg.Resize(strg.Rows.Count - 1).Offset(1)
strg.AutoFilter sCol, sCriteria
Dim sdvrg As Range ' Source Data Visible Range
On Error Resume Next
Set sdvrg = sdrg.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
sws.AutoFilterMode = False
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim dfCell As Range
Set dfCell = dws.Cells(dws.Rows.Count, dfCol).End(xlUp).Offset(1)
If Not sdvrg Is Nothing Then
sdvrg.Copy
dfCell.PasteSpecial xlPasteValues
dws.Activate
dfCell.Select
'Application.CutCopyMode = False ' the next line does the job
sdrg.Columns(sCol).ClearContents ' or .Clear
'sws.Activate
MsgBox "Data copied.", vbInformation
Else
MsgBox "No data found.", vbExclamation
End If
End Sub
I have an estimate sheet and an invoice. I am trying to write code to search the units column ("L") in the estimate sheet. When a number is found, copy the description from a different column ("A") to the invoice sheet in a certain range. I am able to get the search to loop through column L and it can determine whether the number is >0. It will even copy the first description over to the invoice. But, it will not copy anything over beyond that. I am looking for help please. Here is my code thus far.
Sub CopyToInvoice()
Dim rng As Range
Dim i As Long
Dim a As Long
Dim rng_dest As Range
Application.ScreenUpdating = False
i = 1
Set rng_dest = Sheets("Estimate").Range("L5")
'Find first cell with value in column L on sheet Estimate
Range("L5").Select
Do Until WorksheetFunction.CountA(rng_dest.Rows(i)) = 100
i = i + 1
Set rng = Sheets("Invoice").Range("C22:C36")
'Copy rows containing values to sheet Invoice
For a = 1 To rng.Rows.Count
If ActiveCell.Value > 0 Then
Sheets("Estimate").Range("A5").Copy Sheets("Invoice").Range("C22")
End If
'Step down 1 row from present location
ActiveCell.Offset(1, 0).Select
i = i + 1
Next a
Application.ScreenUpdating = True
Loop
End Sub
You are pasting to same line in Invoice sheet in each iteration.
Replace your line:
Sheets("Estimate").Range("A5").Copy Sheets("Invoice").Range("C22")
with
Sheets("Estimate").Range("A" & 4 + a).Copy Sheets("Invoice").Range("C" & 21 + a)
Write From Another Column If Criteria Met
Option Explicit
Sub CopyToInvoice()
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Source
Dim sws As Worksheet: Set sws = wb.Worksheets("Estimate")
Dim slRow As Long: slRow = sws.Cells(sws.Rows.Count, "L").End(xlUp).Row
If slRow < 5 Then Exit Sub ' no data in column range
' Destination
Dim dws As Worksheet: Set dws = wb.Worksheets("Invoice")
Dim dCell As Range: Set dCell = dws.Range("C22")
Application.ScreenUpdating = False
Dim r As Long
For r = 5 To slRow ' rows in 'L'
If IsNumeric(sws.Cells(r, "L").Value) Then ' numeric
If sws.Cells(r, "L").Value > 0 Then ' check 'L>0'
dCell.Value = sws.Cells(r, "A").Value ' write 'A' to destination
Set dCell = dCell.Offset(1) ' next destination
'Else ' L <= 0
End If
'Else ' not numeric
End If
Next r
Application.ScreenUpdating = True
MsgBox "Data copied.", vbInformation
End Sub
Hi I'm relatively new to VBA and programing and im having an "overflow" issue with my code
I'm trying to to go through the first 31 work sheets search for the term "Power On" in column C and when it find a match copy the entire row and paste it into Sheet33 it was working at one point for just a single sheet but now i cant get it to work after modifying it for the first 31 sheets
any help would be greatly appreciated!
Sub test()
Dim LSearchRow As Integer
Dim LCopyToRow As Integer
Dim ws1 As Worksheet
Dim I As Integer
LCopyToRow = 1
For I = 1 To 31
Set ws1 = ActiveSheet
LSearchRow = 1
While Len(Range("A" & CStr(LSearchRow)).Value) > 0
'If value in column C = "Power On", copy entire row to Sheet33
If Range("C" & CStr(LSearchRow)).Value = "Power On" Then
'Select row in ws1 to copy
Rows(CStr(LSearchRow) & ":" & CStr(LSearchRow)).Select
Selection.Copy
'Paste row into Sheet33 in next row
Sheets("Sheet33").Select
Rows(CStr(LCopyToRow) & ":" & CStr(LCopyToRow)).Select
ActiveSheet.Paste
LCopyToRow = LCopyToRow + 1
'Go back to ws1
Sheets(ws1).Select
End If
LSearchRow = LSearchRow + 1
Wend
Exit Sub
Next I
End Sub
'Overflow' error happens when your declared data variable of a certain datatype can no longer hold the SIZE of the value you are putting in it.
Based on your code, LSearchRow and LCopyToRow are declared as INTEGER which can hold up to 32767 (rows). to fix this declare it as LONG instead of INTEGER:
Dim LSearchRow As Long
Dim LCopyToRow As Long
Here's an update to my answer. I made an alternative version of your code:
Sub GetPowerOn()
Dim ws As Worksheet
Dim wsResult As Worksheet
Dim nrow As Long
Dim actvCell As Range
Dim actvLrow As Long
Set wsResult = ThisWorkbook.Worksheets("Sheet33")
Application.ScreenUpdating = False
For Each ws In ThisWorkbook.Worksheets '~Loop through the sheets of the workbook
If Not ws.Name = "Sheet33" Then '~As long as the sheet is not Sheet33, fire the search,copy,paste function below
actvLrow = ws.Range("A" & Rows.Count).End(xlUp).Row '~ Set the lastrow of the active sheet
For Each actvCell In ws.Range("C1:C" & actvLrow) '~ Loop through the cells of column C
If actvCell.Value = "Power On" Then '~Look for criteria
ws.Rows(actvCell.Row & ":" & actvCell.Row).Copy '~Copy the row that matches the criteria
nrow = wsResult.Range("A" & Rows.Count).End(xlUp).Offset(1).Row '~Get the lastrow empty row of the output sheet
wsResult.Range("A" & nrow).PasteSpecial xlPasteValuesAndNumberFormats '~Paste to the next empty row
Application.CutCopyMode = False
End If
Next actvCell
End If
Next ws
Application.ScreenUpdating = True
End Sub
' The reason you are getting the same sheet is you are setting WS1 to ActiveSheet
' 31 times in a row -- not getting the first 31 sheets.
' ActiveSheet is whatever sheet you last happened to have in focus. Unless you
' know you want that (almost never), you should not use it.
' You want to avoids things like copy / paste / select. These are slow.
' You also want to avoid processing things row by row.
' Here is an example that should do what you want.
Sub ThirtyOneFlavors()
Const PowerColNum = 3 ' if you are sure it will always be column 3
Dim WS1 As Worksheet, WS33 As Worksheet
Dim PowerColumn As Range, PowerCell As Range, FirstCell As Range, R As Long
Set WS33 = ThisWorkbook.Sheets("Sheet33") ' Maybe this could use a clever name
WS33.Cells.Delete ' only if you want this
' using ThisWorkbook avoids accidentally getting some other open workbook
For Each WS1 In ThisWorkbook.Sheets
' here, put the names of any sheets you don't want to process
If WS1.Name <> WS33.Name Then
Set PowerColumn = WS1.UsedRange.Columns(PowerColNum)
' I am assuming Power On is the whole column
Set PowerCell = PowerColumn.Find("Power On", LookIn:=xlValues, lookat:=xlWhole, MatchCase:=False)
If Not PowerCell Is Nothing Then ' if you found something
' we need to keep track of the first one found,
' otherwise Excel will keep finding the same one repeatedly
Set FirstCell = PowerCell
End If
While Not PowerCell Is Nothing ' if you keep finding cells
R = R + 1 ' next row
'.Value will hold all of the values in a range (no need to paste)
WS33.Cells(R, 1).EntireRow.Value = PowerCell.EntireRow.Value
' get the next one
Set PowerCell = PowerColumn.Find("Power On", after:=PowerCell, LookIn:=xlValues, lookat:=xlWhole, MatchCase:=False)
If PowerCell.Address = FirstCell.Address Then
' if we found the first one again, kill the loop
Set PowerCell = Nothing
End If
Wend
End If
Next WS1
End Sub
'Consolidate' Data
Option Explicit
Sub ConsolidateData()
' Source
Const sfIndex As Long = 1
Const slIndex As Long = 31
Const sFirstCell As String = "C2"
Const sCriteria As String = "Power On"
' Destination
Const dIndex As Long = 33
Const dFirstCell As String = "A2" ' has to be column 'A' ('EntireRow')
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Create a reference to the initial destination cell.
Dim dws As Worksheet: Set dws = wb.Worksheets(dIndex)
Dim dfCell As Range: Set dfCell = dws.Range(dFirstCell)
Dim dCell As Range: Set dCell = RefLastCellInColumn(dfCell)
If dCell Is Nothing Then ' no data found
Set dCell = dfCell
Else ' data found
Set dCell = dCell.Offset(1)
End If
Dim sws As Worksheet
Dim srg As Range
Dim scrg As Range
Dim sCell As Range
Dim n As Long
Application.ScreenUpdating = False
' Process each source worksheet...
For n = sfIndex To slIndex
Set sws = wb.Worksheets(n)
Set scrg = RefColumn(sws.Range(sFirstCell))
' Test for data...
If Not scrg Is Nothing Then ' data in column found
' Process each cell in source column range...
For Each sCell In scrg.Cells
' Check current cell agains criteria. To ignore case,
' i.e. 'POWER ON = power on', 'vbTextCompare' is used.
If StrComp(CStr(sCell.Value), sCriteria, vbTextCompare) = 0 Then
' Combine current cell into current source range.
' The combining is restricted to per worksheet ('Union').
Set srg = RefCombinedRange(srg, sCell)
End If
Next sCell
' Test for matches...
If Not srg Is Nothing Then ' match found
' Copy. This will work only if all source cells contain values.
' If some of them contain formulas, the results may be mixed
' (some rows containing the formulas, some only values) due to
' the source range being non-contiguous.
' This is prevented by either not combining the cells or
' by using 'PasteSpecial'.
srg.EntireRow.Copy dCell
' Create a reference to the next destination cell.
Set dCell = dCell.Offset(srg.Cells.Count)
' Unreference source range (before processing next worksheet).
Set srg = Nothing
'Else ' no match found
End If
'Else ' no data in column found
End If
Next n
' Activate destination worksheet.
'If Not dws Is ActiveSheet Then dws.Activate
' Save workbook.
'wb.Save
Application.ScreenUpdating = True
MsgBox "Data consolidated.", vbInformation, "Consolidate Data"
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the bottom-most non-empty cell
' in the one-column range from the first cell ('FirstCell')
' through the bottom-most cell of the worksheet column.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefLastCellInColumn( _
ByVal FirstCell As Range) _
As Range
If FirstCell Is Nothing Then Exit Function
With FirstCell.Cells(1)
Dim lCell As Range
Set RefLastCellInColumn = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
End With
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the one-column range from the first cell
' of a range ('FirstCell') to the bottom-most non-empty cell
' of the first cell's worksheet column.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefColumn( _
ByVal FirstCell As Range) _
As Range
If FirstCell Is Nothing Then Exit Function
With FirstCell.Cells(1)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Function
Set RefColumn = .Resize(lCell.Row - .Row + 1)
End With
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to a range combined from two ranges.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefCombinedRange( _
ByVal CombinedRange As Range, _
ByVal AddRange As Range) _
As Range
If CombinedRange Is Nothing Then
Set RefCombinedRange = AddRange
Else
Set RefCombinedRange = Union(CombinedRange, AddRange)
End If
End Function
An alternative method using Find and `FindNext'
Option Explicit
Sub test()
Const MAX_SHT = 3
Const PASTE_SHT = 4
Const TERM = "Power On"
Const COL = "C"
Dim wb As Workbook, ws As Worksheet
Dim n As Integer, LastRow As Long, count As Long
Dim rngFound As Range, rngTarget As Range, sFirst As String
Set wb = ThisWorkbook
' check number of sheets
If wb.Sheets.count < MAX_SHT Then
MsgBox "Too few sheets", vbCritical
Exit Sub
End If
' copy destination
With wb.Sheets(PASTE_SHT)
LastRow = .Cells(Rows.count, COL).End(xlUp).Row
Set rngTarget = .Cells(LastRow + 1, "A")
End With
' first 31 sheets
For n = 1 To MAX_SHT
Set ws = wb.Sheets(n)
LastRow = ws.Cells(Rows.count, COL).End(xlUp).Row
With ws.Range("C1:C" & LastRow)
' search for term
Set rngFound = .Find(TERM, lookin:=xlValues, LookAt:=xlWhole)
If Not rngFound Is Nothing Then
sFirst = rngFound.Address
Do
ws.Rows(rngFound.Row).EntireRow.Copy rngTarget
Set rngTarget = rngTarget.Offset(1)
Set rngFound = .FindNext(rngFound)
count = count + 1
Loop While rngFound.Address <> sFirst
End If
End With
Next
MsgBox count & " rows copied", vbInformation
End Sub
ok just try the following code
many fixes are made and speedUps
Sub test()
' in a x64 environement better forget Integers and go for Longs
Dim LSearchRow As Long
Dim LCopyToRow As Long
Dim ws1 As Worksheet
Dim I As Long
Dim vldRng As Range
Dim maxRw As Long
Dim maxClmn As Long
Dim rngDest As Range
'2 Lines to speed code Immensly. Don't use them while debugging
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
LCopyToRow = 1
Set rngDest = ThisWorkbook.Sheets("Sheet33").Cells(1, 1)
'Set rngDest = ThisWorkbook.Sheets(33).Range("A1") 'Alternative 01
'Set rngDest = Sheets(33).Range("A1") 'Alternative 02
For I = 1 To 31
Set ws1 = ThisWorkbook.Sheets(I)
Set vldRng = ws1.UsedRange ' Get range used instead of searching entire Sheet
maxRw = vldRng.Rows.Count
maxClmn = vldRng.Columns.Count
For LSearchRow = 1 To maxRw
'If value in column C = "Power On", copy entire row to Sheet33
If vldRng.Cells(LSearchRow, 3).Value = "Power On" Then
'Select row in ws1 to copy
vldRng.Cells(LSearchRow, 1).Resize(1, maxClmn).Copy
'Paste row into Sheet33 in next row
rngDest.Offset(LCopyToRow - 1, 0).PasteSpecial xlPasteValues
LCopyToRow = LCopyToRow + 1
End If
Next LSearchRow
Next I
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
There are 3 groups (A,B,C) of data in excel sheet1, and in my workbook i already create 3 sheets named (A, B, C).
I have no problem to copy group A,B,C data into their corresponding sheet, e.g. copy group A data into sheet A, however i find in some cases the selected cell at the beginning of each sheet is not in A1, e.g. the selected cell at the beginning maybe at somewhere other cells in excel (e.g. B10), this make the presentation looks messy, i want the all the data in each sheet start at A1. I know some of you may said using the code Range("a1").selected can manage this situation, however we need to use the "Do loop" to loop over each row in sheet1 to identify that row is belong to A,B or C, then we paste that row into the corresponding sheet. I found if i include the code Range("a1").selected, then each time the program will paste the row in Sheet1 into the cell A1 in sheet A,B and C, and at the end there will only one row appear in each sheets. What should i improve the program below so that each time the data in each group can be appeared at the beginning of cell A1 in their worksheet even sometimes the selected cell of each sheet is not in cell A1? Thanks.
Sub data_category()
Dim y As Integer
Dim x As String
Sheets("sheet1").Activate
Range("a3").Select
Do Until ActiveCell.Value = ""
y = ActiveCell.Offset(0, 3).Value
If y < 90 Then
x = "A"
ElseIf y < 120 Then
x = "B"
Else
x = "C"
End If
ActiveCell.Offset(0, 4).Value = x
Range(ActiveCell, ActiveCell.End(xlToRight)).Copy
Sheets(x).Activate
Range("a1").Select
ActiveCell.PasteSpecial
ActiveCell.Offset(1, 0).Select
Sheets("sheet1").Activate
ActiveCell.Offset(1, 0).Select
Loop
End Sub
Rather than the Do-Loop approach, I would do this slightly different for a faster execution.
Logic
Find last row in Sheet1 of Col A
Insert formula =IF(D3<90,"A",IF(D3<120,"B","C")) in Col E starting at row 3
Next I will use autofilter to filter column E on A first and copy all data in one go to Sheet A. I will repeat the process for B and C
My Assumptions
Row 2 has headers. If not, tweak the code accordingly.
Code
I have commented the code so you will not have a problem understanding it, but if you do, then simply ask.
Option Explicit
Dim ws As Worksheet
Dim rng As Range
Sub Sample()
Set ws = ThisWorkbook.Sheets("Sheet1")
Dim lRow As Long
With ws
'~~> Remove any filters
.AutoFilterMode = False
'~~> Find last row
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Insert formula in Col E
With .Range("E3:E" & lRow)
.Formula = "=IF(D3<90,""A"",IF(D3<120,""B"",""C""))"
.Value = .Value
End With
'~~> Identify the range to work with
Set rng = .Range("A2:E" & lRow)
'~~> Copy rows with relevant criteria
CopyData "A"
CopyData "B"
CopyData "C"
.AutoFilterMode = False
End With
End Sub
Private Sub CopyData(shName As String)
Dim rngToCopy As Range
'~~> Filter column E on the search string
With rng
.AutoFilter Field:=5, Criteria1:=shName
Set rngToCopy = .Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow
End With
'~~> Copy all data in one go
If Not rngToCopy Is Nothing Then rngToCopy.Copy ThisWorkbook.Sheets(shName).Rows(1)
ws.AutoFilterMode = False
End Sub
In Action
Following from Sid's comment:
Sub data_category()
Dim y As Long
Dim x As String, c As Range, ws As Worksheet, wb As Workbook, cDest As Range
Set wb = ActiveWorkbook 'or ThisWorkbook: always good to be specific here
Set c = wb.Worksheets("sheet1").Range("a3") 'get a reference to the starting cell
Do Until Len(c.Value) = 0
y = c.Offset(0, 3).Value
Select Case y 'tidier then if...else if
Case Is < 90: x = "A"
Case Is < 120: x = "B"
Case Else: x = "C"
End Select
c.Offset(0, 4).Value = x
'direct copy to next empty row with no select/activate
Set cDest = wb.Worksheets(x).Cells(Rows.Count, 1).End(xlUp)
If Len(cDest).Value > 0 Then Set cDest = cDest.Offset(1, 0)
c.EntireRow.Copy cDest
Set c = c.Offset(1,0) '<<<<<<<<<<<<< edit - added
Loop
c.Parent.Activate
End Sub
Update Category Reports
Option Explicit
Sub UpdateCategoryReports()
Const sfRow As Long = 3 ' First Row (headers are in row 'sfRow - 1')
Const sfCol As Long = 1
Const dfRow As Long = 2 ' First Row (headers are in row 'dfRow - 1')
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sws As Worksheet: Set sws = wb.Worksheets("Sheet1")
Dim slRow As Long: slRow = sws.Cells(sws.Rows.Count, "A").End(xlUp).Row
If slRow < sfRow Then Exit Sub ' no data (highly unlikely)
Dim slCol As Long
slCol = sws.Cells(sfRow - 1, sws.Columns.Count).End(xlToLeft).Column
Application.ScreenUpdating = False
Dim dws As Worksheet ' Destination Worksheet
Dim durg As Range ' Destination 'UsedRange'
Dim dcrg As Range ' Destination Clear Range
' Clear destination data.
For Each dws In wb.Worksheets(Array("A", "B", "C"))
Set durg = dws.UsedRange ' Destination Used Range
If durg.Rows.Count > 1 Then
' You don't want to clear the headers:
' e.g. if 'durg' is 'A1:J10' then 'dcrg' will be 'A2:J10'.
Set dcrg = durg.Resize(durg.Rows.Count - 1).Offset(1)
dcrg.Clear
End If
Next dws
Dim srrg As Range ' Source Row Range
Dim sRow As Long ' Source Row
Dim dfCell As Range ' Destination First Cell (Range)
Dim dRow As Long ' Destination (Available) Row
Dim sValue As Double ' Source Value
Dim dwsName As String ' Destination Worksheet Name
For sRow = sfRow To slRow
If IsNumeric(sws.Cells(sRow, "D").Value) Then
sValue = sws.Cells(sRow, "D").Value
If sValue < 90 Then
dwsName = "A"
ElseIf sValue < 120 Then
dwsName = "B"
Else
dwsName = "C"
End If
Set srrg = sws.Range(sws.Cells(sRow, "A"), sws.Cells(sRow, slCol))
sws.Cells(sRow, "E").Value = dwsName ' ?
Set dws = wb.Worksheets(dwsName)
dRow = dws.Cells(dws.Rows.Count, "A").End(xlUp).Row + 1
Set dfCell = dws.Cells(dRow, "A")
' This will copy values, formats, and formulas. You may need another
' way. If there are formulas in source and you only need values,
' copying by assignment is the most efficient way. If you also need
' the formats you will have to use the least efficient PasteSpecial.
srrg.Copy Destination:=dfCell
'Else ' sValue is not numeric: do nothing
End If
Next sRow
'sws.Activate
'sws.Cells(1).Activate
Application.ScreenUpdating = True
MsgBox "Category reports updated.", vbInformation, "Category Reports"
End Sub