Conditional Subtraction in VBA - vba

Looking for some assistance programming a report. I'm in the early stages. I've hit a wall when attempting to conditionally subtract using VBA. I would like to Subtract 1 from Column C if Column B is greater than 1. Any assistance would be greatly appreciated. The code I have so far is below
Sub UniqueContactReport()
Columns("Z:AQ").EntireColumn.Delete
Columns("X").EntireColumn.Delete
Columns("V").EntireColumn.Delete
Columns("U").EntireColumn.Delete
Columns("J:S").EntireColumn.Delete
Columns("A:H").EntireColumn.Delete
Dim N As Long, i As Long
N = Cells(Rows.Count, "B").End(xlUp).Row
For i = N To 1 Step -1
If Cells(i, "B") > 1 And Cells(i, "D") = 0 Then
Cells(i, "B").EntireRow.Delete
End If
Next i
End Sub

To succinctly address your question:
Sub ModifyColumnC()
Dim N As Long, i As Long
N = Cells(Rows.Count, "B").End(xlUp).Row ' See comment below
For i = 1 to N 'no need to go backwards because you are not deleting
If Cells(i, "B").Value > 1 Then
Cells(i, "C").Value = Cells(i,"C").Value -1
End If
Next i
End Sub
I have added .Value simply because I try to avoid implicit code. But yes, .Value is the default behaviour. I have left the code that determines the end row because all the following rows are going to be blank (thus <1) and this saves processing time (potentially a lot of it).
An alternative piece of code to do exactly the same thing.
Sub ModifyColumnC()
Dim N As Long, i As Long
N = Cells(Rows.Count, "B").End(xlUp).Row
For i = 1 to N
Cells(i, "C").Value = Cells(i,"C").Value - IIf(Cells(i, "B").Value > 1,1,0)
Next i
End Sub
A nuance is that in the IIf command, all paths are evaluated. But in this case, both the true and false statements are simple constants and unlikely to raise any exceptions.

From your question it seems this is what you want.
Sub UniqueContactReport()
Columns("Z:AQ").EntireColumn.Delete
Columns("X").EntireColumn.Delete
Columns("V").EntireColumn.Delete
Columns("U").EntireColumn.Delete
Columns("J:S").EntireColumn.Delete
Columns("A:H").EntireColumn.Delete
Dim N As Long, i As Long
N = Cells(Rows.Count, "B").End(xlUp).Row
For i = N To 1 Step -1
If Cells(i, "B") > 1 And Cells(i, "D") = 0 Then
debug.print Cells(i, "C").value - 1
End If
Next i
End Sub

you could make no loop and use AutoFilter():
Sub UniqueContactReport()
With Range("B1:B" & Cells(Rows.Count, "B").End(xlUp).Row) 'reference column B cells from row 1 down to last not empty one
.AutoFilter field:=1, Criteria1:=">1" ' filter referenced range on their value being >1
If Application.WorksheetFunction.Subtotal(103, .Cells) > 1 Then ' if any filtered cell other then the header (first one)
With .Offset(1).Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible).Offset(, 1) ' reference filtered cells corresponding ones one colum to the right
.Value = .Value - 1 ' lower referenced cell value by one
End With
If .Cells(1, 1).Value > 1 Then .Cells(1, 2) = .cell(1, 2) - 1 ' check if first cell is to be treated, too (first cell of filtered range is assumed as the "header" so doesn't get caught in filter action)
End If
.Parent.AutoFilterMode = False ' remove autofilter
End With
End Sub
BTW you can collapse all those EntireColumn.Delete statements into one:
Range("Z1:AQ1,X1, V1, U1, J:S, A:H").EntireColumn.Delete
but in this case you have to properly adjust the columns reference in the list since columns get deleted in one shot i.e. what-you-list-is-what-gets-deleted while in your previous approach the order of the delete statements affects which original columns gets actually deleted

Related

Highlight cells based on row number on VBA

I want to change the cell style based on the row number. I am still new on VBA.
Here is my code:
Sub format()
FinalRow = Cells(Rows.Count, 1).End(xlUp).Row
For i = 1 To FinalRow
If Rows.Count = 2 * i + 1 Then
Selection.Style = "Good"
ElseIf Rows.Count = 2 * i Then
Selection.Style = "Bad"
End If
ActiveCell.Offset(1, 0).Select
Next i
End Sub
The loop moves to the next cell but does not highlight if a criteria is met. May you please help me.
I suggest the following:
Option Explicit
Public Sub FormatEvenOddRowNumbers()
Dim FinalRow As Long
FinalRow = Cells(Rows.Count, 1).End(xlUp).Row
Dim i As Long
For i = 1 To FinalRow
If i Mod 2 = 0 Then 'even row number
Cells(i, 1).Style = "Good"
Else 'odd row number
Cells(i, 1).Style = "Bad"
End If
Next i
End Sub
To test if a row number is "even" you can use If i Mod 2 = 0 Then also you don't need to test for "odd" because if it is not "even" it must be "odd" so you can just use Else without any criteria.
Try to avoid using .Select it makes your code slow. See How to avoid using Select in Excel VBA. Instead access the cells directly like Cells(row, column).
First, I think you missused Rows.Count.
Rows.Count returns the total number of rows of your sheet. So now your criteria is only highlighting the two rows that are in the middle of the sheet.
If I assume correctly that you want to put "Good" the Rows that are even and "bad" the ones that are odds. then you should change your code to something like this:
Sub format()
FinalRow = Cells(Rows.Count, 1).End(xlUp).Row
For i = 1 To FinalRow
If i/2 = int(i/2) Then
Selection.Style = "Good"
ElseIf (i+1)/2 = int((i+1)/2) Then
Selection.Style = "Bad"
End If
ActiveCell.Offset(1, 0).Select
Next i
End Sub

How do I find the last row in a column I'm currently looping through

c specifies the columns I'm looping through and the Style is a highlight applied to blank cells. If the cell is blank I need the ID of that row (Cells(i,4)) to be copied to a reports page in the column c that I'm currently looping through. For readability I'm trying to copy each instance in the next available cell of that row but as you can imagine I'm getting an error at the Range(c & Rows.Count) portion of the code.
I'm aware that I can put A or any other column letter there but i'm just wondering if i were to be able to put the variable that I'm iterating with there instead. Any tips for this?
For c = 1 To 103
For i = 1 To coor(2)
If Cells(i, c).Style = "60% - Accent2" Then
Cells(i, 4).Copy Sheets("ReportsPage").Range(c & Rows.Count).End(xlUp).Offset(1, 0)
End If
Next i
Next c
Use Cells() instead of Range(). Cells() allows for the use of cardinal location:
For c = 1 To 103
For i = 1 To coor(2)
If Cells(i, c).Style = "60% - Accent2" Then
Cells(i, 4).Copy Sheets("ReportsPage").Cells(Rows.Count,c).End(xlUp).Offset(1, 0)
End If
Next i
Next c
One more note, one should always append any range object with their parent sheet, even if it is the activesheet:
With ActiveSheet
For c = 1 To 103
For i = 1 To coor(2)
If .Cells(i, c).Style = "60% - Accent2" Then
.Cells(i, 4).Copy Sheets("ReportsPage").Cells(Rows.Count,c).End(xlUp).Offset(1, 0)
End If
Next i
Next c
End With
You can use this to find the last column:
Dim LastCol As Integer
With ActiveSheet
LastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
End With

How to select cells NOT containing a certain word

I have an Excel table, in which some of the cells of the C column contain the word "Rinse" (the other cells have various other contents).
Using VBA code, here's how I would select all rows containing the word "Rinse" in the C column - this code works fine.
For i = 3 To 300
If Cells(i, 3).Value = "Rinse" Then
Rows(i).Select
Selection.FormatConditions.Delete
End If
Next
However, I want to do exactly the opposite, namely to select all rows NOT containing the word "Rinse" in the C column. I've tried the following, but it doesn't work.
For i = 3 To 300
If Cells(i, 3).Value = Not "Rinse" Then
Rows(i).Select
Selection.FormatConditions.Delete
End If
Next
How do I get this done?
Use the Instr function, like this:
If Instr(Cells(i, 3).Value, "Rinse") = 0 Then
Change this line of your code ( <> not equals to)
If Cells(i, 3).Value <> "Rinse" Then
The Like operator can be useful here:
If Not Cells(i, 3).Value Like "*Rinse*" Then
If "Rinse" can be found anywhere in your cell value
You could filter out the Rinse values and then select the visible cells.
Could be quicker than looking at each individual cell.
Public Sub Test()
Dim lRow As Long
With ThisWorkbook.Worksheets("Sheet1")
lRow = .Cells(.Rows.Count, 3).End(xlUp).Row
With .Range(.Cells(1, 3), .Cells(lRow, 3))
.AutoFilter Field:=1, Criteria1:="<>*Rinse*"
'Can replace Select in next row with .FormatConditions.Delete
.SpecialCells(xlCellTypeVisible).Select
End With
.ShowAllData
End With
End Sub
The advantage of this code is in its speed. Acceleration is achieved by referencing the sheet only once for every row and only once for the result, and by formatting only the used range columns instead of the entire rows.
Private Sub SelectNonContiguousRange()
Dim RngAddress() As String
Dim i As Long
Dim R As Long
ReDim RngAddress(300) ' this number should be
With ActiveSheet
For R = 3 To 300 ' equal to this number
' use = (equal) or <> (unequal) as required:
If .Cells(R, "C").Value <> "Rinse" Then
' If .Cells(R, "C").Value = "Rinse" Then
RngAddress(i) = .Range(.Cells(R, "A"), _
.Cells(R, .UsedRange.Columns.Count)).Address
i = i + 1
End If
Next R
ReDim Preserve RngAddress(i - 1)
.Range(Join(RngAddress, ",")).FormatConditions.Delete
End With
End Sub
BTW, you can use a variation of this code to select multiple rows simultaneously (like you can do with Ctl+Click), for example all rows containing the word "Rinse".
#Renee - Change the if condition line as shown below.
For i = 3 To 300
If Cells(i, 3).Value <> "Rinse" Then
Rows(i).Select
Selection.FormatConditions.Delete
End If
Next

VBA Excel delete row if one cell of the row starts with certain string

I have a 2 columns which contain specifications for some components.
Basically, I want to delete an entire row if the initial letter of two specified columns is different from S.
My table looks something like this
I want to delete every row if column "from device" AND "to device" Begins with G or C (or, in more specific words, if either column "from" and "to" begins with S, keep the entire row), my code is the following:
Sub FilterData2()
Dim rcount As Long
With Sheet3
rcount = Range("B" & Rows.Count).End(xlUp).Row
With Range("E1:E" & rcount)
If Application.WorksheetFunction.CountIf(Columns(5), "C*") > 0 Then
.AutoFilter field:=1, Criteria1:="C*"
.Offset(1).Resize(.Rows.Count - 1, 1).EntireRow.Delete
.AutoFilter
End If
If Application.WorksheetFunction.CountIf(Columns(5), "G*") > 0 Then
.AutoFilter field:=1, Criteria1:="G*"
.Offset(1).Resize(.Rows.Count - 1, 1).EntireRow.Delete
.AutoFilter
End If
End With
End With
End Sub
However, this only works for column E, meaning that if column G contains a cell starting with S and column E doesnt, the row would still be deleted and I want to keep the row.
Any suggestions ? thanks!
You can combine your if statements in VBA.
Using the AND modifier:
If Application.WorksheetFunction.CountIf(Columns(5), "C*") > 0 AND Application.WorksheetFunction.CountIf(Columns(5), "G*") > 0 Then
Similarly, you can use an OR modifier:
If Application.WorksheetFunction.CountIf(Columns(5), "C*") > 0 OR Application.WorksheetFunction.CountIf(Columns(5), "G*") > 0 Then
To apply this to your code, where you just want to see if the cell's contents start with S in either from or to:
Dim rcount As Long
Dim i As Integer
Dim strE, strG As String
With Sheets("Sheet3")
rcount = .Range("B" & Rows.Count).End(xlUp).Row
For i = rcount to 2 Step -1
strE = Left(.Cells(i, "E").Value, 1)
strG = Left(.Cells(i, "G").Value, 1)
If strE = "S" Or strG = "S" Then
Else
.Rows(i).EntireRow.Delete
End If
Next i
End With
That should simplify the process significantly.

Faster method to compare two columns from 2 different workbooks

So I have some code using for loops currently doing this and it takes roughly 6 minutes to run...
I have many sheets showing the same columns with some different data.
One column comes in either a named form or a numerical form (Depending on how a user input it to a completely separate database).
Another database contains 2 columns: one being the numerical form of data while the other is named.
My database currently compares my "name" column if numerical with the numerical column in this other database and when it finds a match it changes my "name" cell to match the corresponding name cell in the other database.
Is there any faster way to do this than using for loops?
I have to replicate the code around 12 times for different sheets to do the same task.
As previously stated, overall to run across all 12 its taking around 6 minutes
Sub 6mincode()
Workbooks("1").Activate
N = Workbooks("1").Sheets("Data").Cells(Rows.Count, "B").End(xlUp).Row
N2 = Workbooks("2").Sheets("Data Sheet").Cells(Rows.Count, "B").End(xlUp).Row
For I = 2 To N
If (WorksheetFunction.IsNumber(Sheets("Data").Cells(I, "B").Value)) = True Then
For zz = 8 To N2
If StrComp(Sheets("Data").Cells(I, "B").Value, Workbooks("2").Sheets("Data Sheet").Cells(zz, "B").Value) = 0 Then
Workbooks("1").Sheets("Data").Cells(I, "B").Value = Workbooks("2").Sheets("Data Sheet").Cells(zz, "C").Value
End If
Next zz
End If
Next I
End Sub
You can save the second loop and use Application.Match instead, it will save you a lot of time.
See code below, explanations inside the code's comments:
Option Explicit
Sub Sixmincode()
Dim N As Long, N2 As Long, I As Long
Dim Rng As Range, MatchRow
With Workbooks("1").Sheets("Data")
N = .Cells(.Rows.Count, "B").End(xlUp).Row
End With
With Workbooks("2").Sheets("Data Sheet")
N2 = .Cells(.Rows.Count, "B").End(xlUp).Row ' get last row with data in column B
' set the Range to Match with
Set Rng = .Range("B8:B" & N2)
End With
With Workbooks("1").Sheets("Data")
For I = 2 To N
If IsNumeric(.Cells(I, "B").Value) Then ' use IsNumeric
' use Application.Match, if Not IsError means there is a match found in the second workbook
If Not IsError(Application.Match(.Cells(I, "B").Value, Rng, 0)) Then
MatchRow = Application.Match(.Cells(I, "B").Value, Rng, 0)
.Cells(I, "B").Value = Workbooks("2").Sheets("Data Sheet").Cells(MatchRow, "C").Value
End If
End If
Next I
End With
End Sub