I have the following VBA Macro -
Sub CopyData()
Application.ScreenUpdating = False
Dim CRow As Integer
Dim CColBRange As String
Dim PColBRange As String
Dim Continue As Boolean
'Select Sheet1
With Sheets("KG9New")
.Select
'Initialize variables
Continue = True
CRow = 1
While Continue = True
CRow = CRow + 1
'Test B2:
If CRow = 2 And Cells(CRow, 2).Value = 0 Then
Range("A" & CStr(CRow) & ":C" & CStr(CRow)).Copy
Sheets("Sheet2").Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
CRow = CRow + 1
End If
CColBRange = "B" & CStr(CRow)
PColBRange = "B" & CStr(CRow - 1)
'Break loop upon finding blank cell.
If Len(Range(CColBRange).Value) = 0 Then
Continue = False
End If
'Copy first instance of each changing Value in MachineRunning.
If Range(CColBRange).Value <> Range(PColBRange).Value Then
Range("A" & CStr(CRow) & ":C" & CStr(CRow)).Copy
Sheets("Sheet2").Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
End If
Wend
End With
Application.ScreenUpdating = True
End Sub
Basically, This scans through Column B of my table and copies values across to a new sheet when the value changes from a 1 to a 0 or 0 to 1.
My issue is that this assumes that the first value (in B2) will be a 1. I would like it to return Row 2 values if B2=0.
I tried changing the initialized CRow to 1, but this returns row 2 whether it is a 1 or 2 (due to it being different from the header, I guess).
Could somebody help me out please?
Change your CRow to 1, like you thought. You can't test B2 if you are never at that cell. Then you just need to do an IF statement.
Sub CopyData()
Application.ScreenUpdating = False
Dim CRow As Integer
Dim CColBRange As String
Dim PColBRange As String
Dim Continue As Boolean
'Select Sheet1
Sheets("KG9New").Select
'Initialize variables
Continue = True
CRow = 1
While Continue = True
CRow = CRow + 1
'Test B2:
If CRow=2 and Cells(CRow, 2).value = 0 Then
CRow = 3
End if
CColBRange = "B" & CStr(CRow)
PColBRange = "B" & CStr(CRow - 1)
'Break loop upon finding blank cell.
If Len(Range(CColBRange).Value) = 0 Then
Continue = False
End If
'Copy first instance of each changing Value in MachineRunning.
If Range(CColBRange).Value <> Range(PColBRange).Value Then
Range("A" & CStr(CRow) & ":C" & CStr(CRow)).Copy
Sheets("Sheet2").Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
End If
Wend
Application.ScreenUpdating = True
End Sub
The additional If statement just tests to see if we are on row 2 and if that value is 0. If it is, then change CRow to 3 and it will continue on.
I also removed the superfluous With block. I couldn't see anywhere else in the Macro where that format was being used.
Related
Variance Table Sample I'm working on an Excel Macros (VBA) to look through every 3rd cell of each row in a data set and perform a copy paste action based on conditions (Please see the code at the bottom).
The source data is in a another worksheet (Variance). It has 1300+ IDs (rows) and 3 columns for each value component (col 1 - value 1, col 2 - value 2, and col 3 - the difference between the 2 values) and likewise there are 500+ columns.
My code basically looks through every third column (the difference column) of each row to find out if the value is a number, not equal to zero, and if it's not an error (there are errors in the source sheet). If yes, it copies the Emp ID, the column Name, and both the values into another worksheet called vertical analysis (one below the other).
The code works fine, but it takes 6 to 7 minutes for a data set with 1000+ rows and 500+ columns.
Can someone please tell me if there is a faster way to do this than to loop through each row?
Please let me know if you need more information. Thanks in advance.
Code:
Sub VerticalAnalysis()
Dim EmpID As Range
Dim i As Long
Dim cell As Range
Dim lastrow As Range
Dim LastCol As Long
Dim curRow As Long
Dim c As Long
Set lastrow = ThisWorkbook.Worksheets("Variance").Cells(Rows.Count, 2).End(xlUp)
Set EmpID = ThisWorkbook.Worksheets("Variance").Range("B4", lastrow)
LastCol = ThisWorkbook.Worksheets("Variance").Cells(3, Columns.Count).End(xlToLeft).Column
Application.ScreenUpdating = False
MsgBox "Depending on the size of the record, your excel will not respond for several minutes during Vertical Analysis. Please don't close the workbook", , "Note: Please Don't Close the Workbook"
Worksheets("Vertical").Select
Range("B3", "H" & Rows.Count).ClearContents
Range("B3", "H" & Rows.Count).ClearFormats
ThisWorkbook.Worksheets("Variance").Select
c = 1
For Each cell In EmpID
i = 2
Do Until i >= LastCol
cell.Offset(0, i).Select
If IsError(ActiveCell) Then
ElseIf ActiveCell <> "" Then
If IsNumeric(ActiveCell) = True Then
If ActiveCell <> 0 Then
cell.Copy
Worksheets("Vertical").Range("B" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
ActiveCell.Offset(-c, -2).Copy
Worksheets("Vertical").Range("C" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
ActiveCell.Offset(0, -2).Copy
Worksheets("Vertical").Range("D" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
ActiveCell.Offset(0, -1).Copy
Worksheets("Vertical").Range("E" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
End If
End If
End If
i = i + 4
Loop
c = c + 1
Next cell
ThisWorkbook.Worksheets("Vertical").Select
Range("B2").Select
MsgBox "Analysis complete " & vbCrLf & Worksheets("Vertical").Range("B" & Rows.Count).End(xlUp).Row - 2 & " Components have variations", , "Success!"
Application.ScreenUpdating = True
End Sub
You might try to use SQL. In order to learn how to use sql in EXCEL VBA, I suggest you to follow this tuto and to apply your learn on your macro. They will be faster =)
https://analystcave.com/excel-using-sql-in-vba-on-excel-data/
Better not to hit the sheet so many times.
Below is tested and should run in a few seconds, but you may need to tweak the column positions etc:
Sub VerticalAnalysis()
Const BLOCK_SIZE As Long = 30000
Dim lastrow As Long
Dim LastCol As Long
Dim c As Long, wsVar As Worksheet, wsVert As Worksheet, n As Long
Dim data, r As Long, empId, v, rwVert As Long, dataVert, i As Long
Set wsVar = ThisWorkbook.Worksheets("Variance")
Set wsVert = ThisWorkbook.Worksheets("Vertical")
lastrow = wsVar.Cells(Rows.Count, 2).End(xlUp).Row
LastCol = wsVar.Cells(3, Columns.Count).End(xlToLeft).Column
'get all the input data as an array (including headers)
data = wsVar.Range("A3", wsVar.Cells(lastrow, LastCol)).Value
'clear the output sheet and set up the "transfer" array
With wsVert.Range("B3", "H" & Rows.Count)
.ClearContents
.ClearFormats
End With
rwVert = 3 'first "vertical" result row
ReDim dataVert(1 To BLOCK_SIZE, 1 To 4) 'for collecting matches
i = 0
n = 0
For r = 2 To UBound(data, 1) 'loop rows of input array
empId = data(r, 2) 'colB ?
c = 7 'first "difference" column ?
Do While c <= UBound(data, 2)
v = data(r, c)
If Not IsError(v) Then
If IsNumeric(v) Then
If v > 0.7 Then
i = i + 1
n = n + 1
dataVert(i, 1) = empId
dataVert(i, 2) = data(1, c) 'header
dataVert(i, 3) = data(r, c + 2) 'value1
dataVert(i, 4) = data(r, c + 1) 'value2
'have we filled the temporary "transfer" array?
If i = BLOCK_SIZE Then
wsVert.Cells(rwVert, 2).Resize(BLOCK_SIZE, 4).Value = dataVert
i = 0
ReDim dataVert(1 To BLOCK_SIZE, 1 To 4)
rwVert = rwVert + BLOCK_SIZE
End If
End If
End If
End If
c = c + 4 'next difference
Loop
Next r
'add any remaining
If i > 0 Then wsVert.Cells(rwVert, 2).Resize(BLOCK_SIZE, 4).Value = dataVert
wsVert.Select
wsVert.Range("B2").Select
MsgBox "Analysis complete " & vbCrLf & n & " Components have variations", , "Success!"
End Sub
I have a Sheet with data and another contains filter words in the field.
My code copy data from Sheet "Data" to Sheet2 on criteria words E2:E10 in the SheetFilt. Now it works only with one criteria in E2 Cell. How can I use range E2:E10 (or Name of the range)
Sub Copy()
Dim i, LastRow
LastRow = Sheets("Data").Range("A" & Rows.Count).End(xlUp).Row
Sheets("Sheet2").Cells.Clear
Worksheets("Data").Rows(1).Copy Destination:=Worksheets("Sheet2").Rows(1)
For i = 2 To LastRow
If InStr(Sheets("Data").Cells(i, "H"), Sheets("SheetFilt").Range("E2")) <> 0 Then
Sheets("Data").Cells(i, "A").EntireRow.Copy Destination:=Sheets("Sheet2").Range("A" & Rows.Count).End(xlUp).Offset(1)
End If
Next i
End Sub
I'd like to use range E2:E10 instead of E2, something like this (but this ends with error)
If InStr(Sheets("Data").Cells(i, "H"), Sheets("SheetFilt").Range("E2:E10")) <> 0
How can I realize it ?
Try this one:
Sub Copy()
Dim i, j, LastRow as Long
LastRow = Sheets("Data").Range("A" & Rows.Count).End(xlUp).Row
Sheets("Sheet2").Cells.Clear
Worksheets("Data").Rows(1).Value = Worksheets("Sheet2").Rows(1).Value
For i = 2 To LastRow
For j = 2 to 10
If InStr(Sheets("Data").Cells(i, 8), Sheets("SheetFilt").Cells(j, 5)) <> 0 Then
Sheets("Sheet2").Cells(1, Rows.Count).End(xlUp).Offset(1).Value = _
Sheets("Data").Cells(i, 1).EntireRow.Value =
End If
Next j
Next i
End Sub
If there is any bug, I am just trying to follow your "idea", just let me know if something is wrong.
Hope it helps
Thanks to David. Final working variant. (Note, there is no empty cells in Filter words range E2:E50, I use any symbol instead, like "~")
Sub Copy()
Dim i, LastRow
LastRow = Sheets("Data").Range("A" & Rows.Count).End(xlUp).Row
Sheets("Sheet2").Cells.Clear
Worksheets("Data").Rows(1).Copy Destination:=Worksheets("Sheet2").Rows(1)
For i = 2 To LastRow
For j = 2 To 50
If InStr(Sheets("Data").Cells(i, "H"), Sheets("SheetFilt").Cells(j, "E")) <> 0 Then
Sheets("Data").Cells(i, "A").EntireRow.Copy Destination:=Sheets("Sheet2").Range("A" & Rows.Count).End(xlUp).Offset(1)
End If
Next j
Next i
End Sub
What I want to do is if column O contains "weekend" then change the value of column M cells to "3".
Sub weekly_weekend()
lastrow = Sheet1.Cells(Rows.Count, 1).End(xlUp).Row
Application.ScreenUpdating = False
For x = 2 To lastrow
If InStr(1, Sheet1.Range("O" & x).Value, UCase("weekend"), 1) > 0 Then
Sheet1.Range("M" & x).Value = "3"
Next x
Application.ScreenUpdating = True
End Sub
The problem with your code is that you're getting the last row of the column A, and this will prevent the For to be executed. To fix your code, you can proceed in multiple ways.
Using Range
One is to use the Range property, so you can explicitly write your column name, like this:
Sub weekly_weekend()
lastrow = Sheet1.Range("O" & Sheet1.Rows.Count).End(xlUp).Row
Application.ScreenUpdating = False
For x = 2 To lastrow
If InStr(1, Sheet1.Range("O" & x).Value, UCase("weekend"), 1) > 0 Then Sheet1.Range("M" & x).Value = "3"
Next x
Application.ScreenUpdating = True
End Sub
Picking up the right column
Or you can simply pick the right number of the column you want (in this case column O is 15), like this:
Sub weekly_weekend()
lastrow = Sheet1.Cells(Sheet1.Rows.Count, 15).End(xlUp).Row
Application.ScreenUpdating = False
For x = 2 To lastrow
If InStr(1, Sheet1.Range("O" & x).Value, UCase("weekend"), 1) > 0 Then Sheet1.Range("M" & x).Value = "3"
Next x
Application.ScreenUpdating = True
End Sub
Note: Please note that if you add or remove columns, with the second method you'll need to remember to change the column index in your code accordingly.
Hope this helps.
I have problem with closing one userform and going to next. UserForm3 after clicking command button should be closed and UserForm4 should be shown. Unfortunately I get "Run time Error 91 object variable or with block variable not set". I've dug deep into internet and I am pretty sure that problem is with Userform4, although code for UserForm3 is highlighted as bugged. Basicly I want UserForm4 to be displayed and have all the textboxes filled with data from sheet "Log", based on choice from Combobox from UserForm3. Choice from UserForm3 is saved to cell E1 on "Log" Sheet.
Code from UserForm3
Private Sub CommandButton1_Click()
Sheets("Log").Range("E1") = ComboBox2.Text
Unload Me
UserForm4.Show <- ERROR DISPLAYED HERE
End Sub
In UserForm4 I want to find value from E1 in cells below and later on fill textboxes in Userform4 with data from the row, in which E1 value was found.
Code for UserForm4
Private Sub UserForm_Initialize()
Dim Name As String
Dim rng As Range
Dim LastRow As Long
Dim wart As Worksheet
wart = Sheets("Log").Range("E1")
LastRow = ws.Range("B3" & Rows.Count).End(xlUp).Row + 1
Name = Sheets("Log").Range("E1")
UserForm4.TextBox8.Text = Name
nazw = Application.WorksheetFunction.VLookup(wart, Sheets("Log").Range("B3:H" & LastRow), 1, False)
UserForm4.TextBox1.Text = ActiveCell.Offset(, 1)
UserForm4.TextBox2.Text = ActiveCell.Offset(, 1)
UserForm4.TextBox3.Text = ActiveCell.Offset(, 1)
UserForm4.TextBox4.Text = ActiveCell.Offset(, 1)
UserForm4.TextBox5.Text = ActiveCell.Offset(, 1)
UserForm4.ComboBox1.Text = ActiveCell.Offset(, 1)
UserForm4.TextBox6.Text = ActiveCell.Offset(, 1)
UserForm4.TextBox7.Text = ActiveCell.Offset(, 1)
End Sub
The code below is to avoid to run-time errors mentioned in the code above, it's not debugged for the VLookup function part.
Option Explicit
Private Sub UserForm_Initialize()
Dim Name As String
Dim LastRow As Long
Dim wart As Variant
Dim ws As Worksheet
Dim nazw As Long
' set ws to "Log" sheets
Set ws = Sheets("Log")
With ws
wart = .Range("E1")
' method 1: find last row in Column "B" , finds last row even if there empty rows in the middle
LastRow = .Cells(.Rows.Count, "B").End(xlUp).Row + 1
' method 2 to find last row, equivalent to Ctrl + Shift + Down
' LastRow = .Range("B3").CurrentRegion.Rows.Count + 1
' a little redundant with the line 2 above ?
Name = .Range("E1")
End With
With Me
.TextBox8.Text = Name
' ****** Need to use Match instead of Vlookup VLookup Section ******
If Not IsError(Application.Match(wart, ws.Range("B1:B" & LastRow - 1), 0)) Then
nazw = Application.Match(wart, ws.Range("B1:B" & LastRow - 1), 0)
Else ' wart record not found in range
MsgBox "Value in Sheet " & ws.Name & " in Range E1 not found in Column B !", vbInformation
Exit Sub
End If
.TextBox1.Text = ws.Range("B" & nazw).Offset(, 1)
.TextBox2.Text = ws.Range("B" & nazw).Offset(, 1)
.TextBox3.Text = ws.Range("B" & nazw).Offset(, 1)
.TextBox4.Text = ws.Range("B" & nazw).Offset(, 1)
.TextBox5.Text = ws.Range("B" & nazw).Offset(, 1)
.ComboBox1.Text = ws.Range("B" & nazw).Offset(, 1)
.TextBox6.Text = ws.Range("B" & nazw).Offset(, 1)
.TextBox7.Text = ws.Range("B" & nazw).Offset(, 1)
End With
End Sub
the code below works 100%. It scans for a match in Column B and copies and renames a group of cells when a match is found. However the is a line For lRow = Sheets("HR-Calc").Cells(Cells.Rows.count, "b").End(xlUp).Row To 7 Step -1
Where the step -1 will scan row by row from the bottom of the sheet until a match is found. It would be much easier if the step was set to End.(xlUp) instead of -1. searching every row is overkill because of how the data is set up End.(xlUp) would massive cut down the run time.
Is something like this possible?
Sub Fill_CB_Calc()
M_Start:
Application.ScreenUpdating = True
Sheets("summary").Activate
d_input = Application.InputBox("select first cell in data column", "Column Data Check", Default:="", Type:=8).Address(ReferenceStyle:=xlA1, RowAbsolute:=True, ColumnAbsolute:=False)
data_col = Left(d_input, InStr(2, d_input, "$") - 1)
data_row = Right(d_input, Len(d_input) - InStr(2, d_input, "$"))
Application.ScreenUpdating = False
Sheets("summary").Activate
Range(d_input).End(xlDown).Select
data_last = ActiveCell.Row
If IsEmpty(Range(data_col & data_row + 1)) = True Then
data_last = data_row
Else
End If
For j = data_row To data_last
CBtype = Sheets("summary").Range(data_col & j)
Sheets("HR-Calc").Activate
For lRow = Sheets("HR-Calc").Cells(Cells.Rows.count, "b").End(xlUp).Row To 7 Step -1
If Sheets("HR-Calc").Cells(lRow, "b") = CBtype Then
CBend = Sheets("HR-Calc").Range("C" & lRow).End(xlDown).Row + 1
Sheets("HR-Calc").Rows(lRow & ":" & CBend).Copy
CBstart = Sheets("HR-Calc").Range("c50000").End(xlUp).Row + 2
ActiveWindow.ScrollRow = CBstart - 8
Sheets("HR-Calc").Range("A" & CBstart).Insert Shift:=xlDown
CBold = Right(Range("c" & CBstart), Len(Range("C" & CBstart)) - 2)
box_name = Sheets("summary").Range(data_col & j).Offset(0, -10)
CBnew = Right(box_name, Len(box_name) - 2) & "-" ' <--this is custom and can be changed based on CB naming structure
If CBnew = "" Or vbCancel Then
End If
CBend2 = Range("c50000").End(xlUp).Row - 2
Range("C" & CBstart + 1 & ":" & "C" & CBend2).Select
Selection.Replace What:=CBold & "-", Replacement:=CBnew, LookAt:=xlPart, _
SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
Range("C" & CBstart).FormulaR1C1 = "CB" & Left(CBnew, Len(CBnew) - 1)
GoTo M_Start2
Else
End If
Next lRow
M_Start2:
Next j
YN_result = MsgBox("Fill info for another block/inverter?", vbYesNo + vbExclamation)
If YN_result = vbYes Then GoTo M_Start
If YN_result = vbNo Then GoTo jumpout
jumpout:
' Sheets("summary").Range(d_input).Select
Application.ScreenUpdating = True
End Sub
I'm not sure if this will help but I've had a great performance increase with pulling the entire range you need to loop through into a variant array and then looping through the array. If I need to loop through large data sets, this method has worked out well.
Dim varArray as Variant
varArray = Range(....) 'set varArray to the range you're looping through
For y = 1 to uBound(varArray,1) 'loops through rows of the array
'code for each row here
'to loop through individual columns in that row, throw in another loop
For x = 1 to uBound(varArray, 2) 'loop through columns of array
'code here
Next x
Next y
You can also define the column indexes prior to executing the loop. Then you only need to execute the you need to pull those directly in the loop.
'prior to executing the loop, define the column index of what you need to look at
Dim colRevenue as Integer
colRevenue = 5 'or a find function that searches for a header named "Revenue"
Dim varArray as Variant
varArray = Range(....) 'set varArray to the range you're looping through
For y = 1 to uBound(varArray,1) 'loops through rows of the array
tmpRevenue = CDbl(varArray(y, colRevenue))
Next y
Hope this helps.
Look at doing a .find from the bottom up.
Perform a FIND, within vba, from the bottom of a range up
That will eliminate the need to do the for loop from the last row to the first occurrence of the value you want to locate.