How To Combine <> and X variable in Excel VBA? - excel

I am working on excel VBA and I want all rows that does not equal to my cell value/reference (Cell E5) to be deleted but what is happening right, it deletes all rows in that sheet.
Sub DeleteNotEqualTo()
Dim ws As Worksheet
x = Range("E5").Value
Set ws = ThisWorkbook.Worksheets("Conso")
ws.Range("B8:Z5000").AutoFilter Field:=11, Criteria1:="<> & x"
Application.DisplayAlerts = False
ws.Range("B9:Z5000").SpecialCells(xlCellTypeVisible).Delete
Application.DisplayAlerts = True
On Error Resume Next 'Clear Filter
ws.ShowAllData
On Error GoTo 0
End Sub

I'm assuming that you're setting the reference value in cell E5 (row 5) and starting from row 8 ("B8:Z5000") you crosscheck if for each record field 11 corresponds to that specific value.
I don't recommend this approach because:
when manipulating spreadsheets, you shouldn't use the auto filter. The auto filter is rather designed for visibility when you look at a spreadsheet - not for a programmatic approach. You should be aware that, even if data is filtered out of the visible range, if you'd loop through the records, the program would still take the non visible records into consideration.
by setting a static range, you are always going to be limited by the number of rows you set. You can easily use a dynamic range instead, like that your script will always crosscheck all rows that are not empty.
Option Explicit
'always recommend to use this option, it forces you to
'declare the type of every single variable
Sub DeleteNotEqualTo()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Conso")
Dim x As String 'declaring string variable, you may have to adapt
'x = Range("E5").Value you could also with the cells object, check next line
x = ws.Cells(5, 5).Value '5,5 = row 5, column 5
'now comes the loop
'you have to declare a so called iterator variable to loop, in this case "i"
'the iterator drives the loop through the records
'you also need a variable that determines the last row to be checked
'in this case you define this by checking the so called "UsedRange"
'the "UsedRange" counts from the first row that contains to the last
'check out the indicated link for more info regarding finding the last row
Dim i As Integer 'iterator variable
Dim lrow As Integer 'variable for the last row
lrow = ws.UsedRange.Rows.Count + 4 'assuming that row 5 is the first to contain a value
'now loop through the records
'here I'm assuming field 11 of your auto filter correspond to column 11 in the sheet
'important sidenote: as you're deleting rows, you have to loop bottom up
'if you loop top to bottom, the loop may skip rows that shift up
For i = lrow To 8 Step -1 'assuming that the first row to be checked is row 8
If ws.Cells(i, 11).Value = x Then
ws.Cells(i, 11).EntireRow.Delete
End If
Next i
End Sub
Here's a link with more info regarding finding the last row in a spreadsheet. Very helpful with dynamic ranges:
https://www.automateexcel.com/vba/find-last-row-column-cell/

What worked for me in your workbook is changing this line:
ws.Range("B8:Z5000").AutoFilter Field:=11, Criteria1:="<> & x"
to:
ws.Range("B8:Z5000").AutoFilter Field:=11, Criteria1:="<>*" & x & "*"
The presence of * wildcard changes the criteria from an exact match to a partial match. (This shouldn't have been necessary, but specifying an exact match didn't filter the range correctly for reasons I'm unsure of.)
Option Explicit
Private Sub DeleteNotEqualTo()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Conso")
Dim x As Variant
x = Range("E5").Value
ws.Range("B8:Z5000").AutoFilter Field:=11, Criteria1:="<>*" & x & "*"
Application.DisplayAlerts = False
ws.Range("B9:Z5000").SpecialCells(xlCellTypeVisible).Delete
Application.DisplayAlerts = True
On Error Resume Next 'Clear Filter
ws.ShowAllData
On Error GoTo 0
End Sub

Related

Find a column based on the value of the first cell in that column, then define that column as a range that can be used in a macro function

I am running a data cleaning macro that goes to column G, and deletes all the rows which contain 'Y'.
This is neat and fast - however, it depends on having the right data in column G (as this is the range I use in my code). However, I wish for my macro to be a little smarter and more versatile. I'd like for it to go through the first-row, read the values (headers) and find the column with the header 'Opt Out' and then run my delete all rows macro on that column.
That means that even if we add another extra column before column G in our data, the macro should still be able to handle that.
The only slightly feasible answer I managed to find for this was to use the WorksheeetFunction.Match method - however, the problem with this method is that it would not set the whole column where it finds my lookup_value as a range and therefore my macro comes back with an error or doesn't run.
I have read a lot of questions here and other sources but didn't find something that would allow me to define a range like that. Please let me know if my question is not clear.
I am not great with VBA syntax, but quite proficient with Excel and PowerQuery. Please excuse if there is a basic solution to this that I just don't see.
Thank you.
D
' ***************************************************************
' Delete rows based on cell value
'****************************************************************
Sub deleteOptOutRows()
'Disable certain Excel features whilst the macro is running
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Application.ScreenUpdating = False
' Declare variables
Dim deleteRow As String
Dim ws As Worksheet
'Set objects
Set ws = ActiveSheet
'Loop through the rows of data, in order to delete rows with a Y
'Y in column G. Our data commences on row 2
For deleteRows = ws.Range("G" & Rows.Count).End(xlUp).Row To 2 Step -1
If ws.Range("G" & deleteRows).Value = "Y" Then
Rows(deleteRows).EntireRow.Delete
End If
' Mode to next cell in the range, which is being looped
Next deleteRows
' Re-enable the Excel features we've disabled at the top of our macro
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
This is what you need:
Option Explicit
Sub deleteOptOutRows()
' ***************************************************************
' Delete rows based on cell value
'****************************************************************
'Disable certain Excel features whilst the macro is running
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Application.ScreenUpdating = False
' Declare variables
Dim i As Long 'use i to count loops
Dim LastRow As Long, Col As Integer 'add a column and last row variable
Dim ws As Worksheet
'Set objects
Set ws = ActiveSheet
With ws
Col = .Cells.Find("Opt Out").Column 'find the column value for that header
LastRow = .Cells(.Rows.Count, Col).End(xlUp).Row 'fin the last row
'Loop through the rows of data, in order to delete rows with a Y
'Y in column G. Our data commences on row 2
For i = LastRow To 2 Step -1
If .Cells(i, Col) = "Y" Then
.Rows(i).EntireRow.Delete
End If
' Mode to next cell in the range, which is being looped
Next i
End With
' Re-enable the Excel features we've disabled at the top of our macro
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub

How to use .address function in order to delete a row?

I have a bunch of .xls files that have 6 rows of junk at the beginning of the files and 1 line with the text "Not Classified" in an arbitrary row of column A. I have the below code which deletes the first 6 rows and then finds the cell with the proper text, but I don't know how to then select that row, since my understanding is the Find function is returning like A10 and I do not know how to split the reference in order to Select row 10.
I believe the address function should be able to help in this regard but I am having trouble getting it to work. In the above path is the variable that stores the location of my files and x is the cell with the offending text.
Do while files <>""
Workbooks.Open(path & files).ActiveSheet.Rows("1:6").Delete
Set x = ActiveWorkbook.ActiveSheet.Range("A:A").Find("Not Classified")
If Not x Is Nothing Then
x.Clear
'Obviously this only clears the cell with the offending text and I
'want to delete the whole row
End If
ActiveWorkbook.Close savechanges:=True
files = Dir()
Loop
I believe the address function should be able to help in this regard but I am having trouble getting it to work. In the above path is the variable that stores the location of my files and x is the cell with the offending text.
Option Explicit
Sub test()
Dim strSearchValue As String
Dim LastRow As Long
Dim rngSearch As Range, rngPosition As Range
With ThisWorkbook.Worksheets("Sheet1")
strSearchValue = "Not Classified"
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row '<- Find lastrow to create a range. No need to use thw whole column.
Set rngSearch = .Range("A1:A" & LastRow) '<- Set your range
Set rngPosition = rngSearch.Find(strSearchValue) '<- Find the position of the value
If Not rngPosition Is Nothing Then '<- To avoid error check if the position is not nothing
MsgBox rngPosition.Address '<- Message box with the address
End If
'How to delete row a row. Have in mind that when you delete you must go backwards - from bottom to top to avoid breaking indexing.
.Rows(rngPosition.Row).EntireRow.Delete
End With
End Sub
You could produce a Union of both ranges (rows 1:6 and the row containing "Not Classified") then delete them.
dim r as variant
Do while files <>""
with Workbooks.Open(path & files)
with .worksheets(1) '<~~ know what worksheet you're dealing with
r = application.match("Not Classified", .range("A:A"), 0)
if iserror(r) then
.range("A1:A6").entirerow.Delete
else
.range("A1:A6, A" & r).entirerow.Delete
end if
end with
.Close savechanges:=True
end with
files = Dir()
Loop

How can I do a Calculation in Microsoft Excel VBA?

I'm 15 and I'm doing a Internship as a Developer and I've got a kinda hard exercise.
I have a Table with 3 columns, A is "Number" B is "percent" and C is "Value". The column "value" is blank and I Need to calculate the value with a macro button. I've tried this, but it was wrong because I didn´t calculate it in VBA:
Public Sub PushButton ()
Range("C2:C11").Formula = "=A2*B2/100"
Range("C2:C11").Value = Range("C1:C6).Value
End Sub
How do I solve this?
You are using a defined range, you could do it with a dynamic range like this:
Option Explicit
Sub PushButton()
Dim i As Long, LastRow As Long
With ThisWorkbook.ActiveSheet
LastRow = .Cells(.Rows.Count, 1).End(xlUp).Row 'first you need to find the last row on the active sheet
For i = 2 To LastRow 'then iterate through all the rows starting from 2, if row 1 has headers
.Cells(i, 3) = .Cells(i, 1) * .Cells(i, 2) / 100
Next i
End With
End Sub
If you need help understanding this code, let me know.
Edit: Explanation
Well, the first thing you must do is Dimension all your variables, and to help that you can use the Option Explicitright above all your code.
I've dimensioned 1 variable for the loop and another one to find the last row with text.
To find the last row what you are actually doing is going to excel, select the last row (1048576) and the column where it will have text, in this case 1 or column "A" and then pushing ctrl+Up excel and vba will get you to the last cell with text.
To do that you use Cells(Row, column) instead of manually inserting row 1048576 you can just use rows.count and it will be the same.
Once you get the last row you just iterate with a For iloop meaning For a variable called i which equals 2 (For i = 2) To LastRow (to the last row you calculated) VBA will repeat the code in between the ForAnd Next adding 1 number to i everytime the loop restarts.
In this case is just adding a number to the rows on Cells(i, 3) so you can modify that cell depending of its i value.
I think you need to question why Excel needs to calculate on demand rather than automatically like normal. Failing that there are a few options
You could change your calculation method to Manual using the following in the ThisWorkbook object
Option Explicit
Dim xlCalcMethod As XlCalculation
Private Sub Workbook_Open()
With Application
' Store users current method for when closing the workbook
xlCalcMethod = .Calculation
.Calculation = xlCalculationManual
End With
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
' Reset calculation
Application.Calculation = xlCalcMethod
End Sub
and then when the button is pressed use the following code to calculate placed in a Module
Option Explicit
Public Sub Button_Click()
Application.Calculate
End Sub
Another option to do this without looping would be:
Sub CalculateRange()
Dim rng As Range
' Update for your Range
With ActiveSheet
Set rng = .Range("C2:C" & .Cells(.Rows.Count, "A").End(xlUp).Row)
End With
rng.Value2 = Evaluate(rng.Offset(0, -2).Address & "*" & rng.Offset(0, -1).Address & "/100")
End Sub
Finally, the way you've come up with is perfectly acceptable as VBA

copy data based on criteria to another sheet and clear the contents

This code is working to copy the filtered data of "Award" column marked "Yes" to another sheet; however, I'm receiving an error of "Type Mismatch." I'm not 100% now that the code is working properly to filter the data and copy correctly. I currently have 23 rows of test data for proper functionality. If I only put one row of data, then it doesn't copy and paste the data correctly. I am left with the copied 1st row of data plus the 2nd empty row of data. Additionally, it is not clearing the contents of the rows after the paste, so I may add new data as the days progress.
Sub CopySheet()
Dim i As Integer
Dim LastRow As Integer
Dim Search As String
Dim Column As Integer
Sheets("MasterData").Activate
Sheets("MasterData").Range("A1").Select
'Sets an Autofilter to sort out only your Yes rows.
Selection.AutoFilter
'Change Field:=5 to the number of the column with your Y/N.
Sheets("MasterData").Range("$A$1:$G$200000").AutoFilter Field:=7, Criteria1:="Yes"
'Finds the last row
LastRow = Sheets("MasterData").Cells(Sheets("MasterData").Rows.Count, "A").End(xlUp).row
i = 1
'Change the 3 to the number of columns you got in Sheet2
Do While i <= 11
Search = Sheets("ActiveJobStatus").Cells(1, i).Value
Sheets("MasterData").Activate
'Update the Range to cover all your Columns in MasterData.
If IsError(Application.Match(Search, Sheets("MasterData").Range("A1:G1"), 0)) Then
'nothing
Else
Column = Application.Match(Search, Sheets("MasterData").Range("A1:G1"), 0)
Sheets("MasterData").Cells(2, Column).Resize(LastRow, 1).Select
Selection.Copy
Sheets("ActiveJobStatus").Activate
Sheets("ActiveJobStatus").Cells(2, i).Select
ActiveSheet.Paste
End If
i = i + 1
Loop
'Clear all Y/N = Y
'Update the Range to cover all your Columns in MasterData.
Sheets("MasterData").Activate
Column = Application.Match("Award", Sheets("MasterData").Range("A1:F1"), 0)
Sheets("MasterData").Cells(2, Column).Resize(LastRow, 1).Select
Selection.ClearContents
End Sub
Sorry to change your code up so much, but it looks like you might be over-complicating how to do it.
This is some code from a previous question I answered where someone wanted to highlight a specific range whenever the word "Total" was found.
I changed the find to "Yes". Change the SearchRange to your column. (I think G is right).
Also, for future reference, Select should [almost never] be used.
It slows down code execution quite a bit and is not required.
I know the macro recorder likes to use it, but everything can be referenced without using select.
Brief example:
Sheets("ActiveJobStatus").Activate
Sheets("ActiveJobStatus").Cells(2, i).Select
ActiveSheet.Paste
Can Be replaced by:
Sheets("ActiveJobStatus").Cells(2, i).Paste
This code is working to copy the filtered data of "Award" column marked "Yes" to another sheet.
Sub CopyAwardsToActiveJobStatusSheet()
Dim SearchRange, First, Finder As Range
Dim PasteRow as Integer 'Add this to increment the rows we paste your data to
Set SearchRange = Sheets("MasterData").Range("G:G") 'Search This Range for "Yes"
Set Finder = SearchRange.Find("Yes") 'This is what we're looking for
If Finder Is Nothing Then Exit Sub 'We didn't find any "Yes" so we're done
'Drastically increases speed of every macro ever
'(well, when the sheets are modified at least - and it doesn't hurt)
Application.ScreenUpdating = False
First = Finder.Address 'Grab the address of the first "Yes" so we know when to stop
'Get the last row of column "A" on ActiveJobStatusSheet and start pasting below it
PasteRow = Sheets("ActiveJobStatus").Cells(Sheets("ActiveJobStatus").Rows.Count, "A").End(xlUp).Row + 1
Do
'Copy the entire row and paste it into the ActiveJobStatus sheet
'Column A and PasteRow (the next empty row on the sheet)
'You can change these if needed
Finder.EntireRow.Copy Sheets("ActiveJobStatus").Range("A" & PasteRow)
'If you just want A:G, you can use this instead:
'Finder returns the cell that contains "Yes",
'So we offset/resize to get the 6 cells before it and just copy that
'Resize doesn't like negative numbers so we have to combine:
'Finder.Offset(,-6).Resize(,7).Copy Sheets("ActiveJobStatus").Range("A" & PasteRow)
'Look for the next "Yes" after the one we just found
Set Finder = SearchRange.FindNext(after:=Finder)
PasteRow = PasteRow + 1 'Faster than looking for the end again
'Do this until we are back to the first address
Loop While Not Finder Is Nothing And Finder.Address <> First
'Clear MasterData
Sheets("MasterData").Range("A2:G" & Sheets("MasterData").UsedRange.Rows.Count).ClearContents
Application.ScreenUpdating = True 'Drastically increases speed of every macro ever.
End Sub
Just the code:
Sub CopyAwardsToActiveJobStatusSheet()
Dim SearchRange, First, Finder As Range
Dim PasteRow as Integer
Set SearchRange = Sheets("MasterData").Range("G:G")
Set Finder = SearchRange.Find("Yes")
If Finder Is Nothing Then Exit Sub
Application.ScreenUpdating = False
First = Finder.Address
PasteRow = Sheets("ActiveJobStatus").Cells(Sheets("ActiveJobStatus").Rows.Count, "A").End(xlUp).Row + 1
Do
Finder.EntireRow.Copy Sheets("ActiveJobStatus").Range("A" & PasteRow)
Set Finder = SearchRange.FindNext(after:=Finder)
PasteRow = PasteRow + 1
Loop While Not Finder Is Nothing And Finder.Address <> First
Sheets("MasterData").Range("A2:G" & Sheets("MasterData").UsedRange.Rows.Count).ClearContents
Application.ScreenUpdating = True
End Sub
Results:
MasterData Sheet:
ActiveJobStatus Sheet:

Matching two data lists in Excel VBA and exporting to New Sheet

I receive an excel file monthly and have to export parts of it to a new file. I have a list of identifier numbers and I am trying to match the list of numbers in the selected list to the full file and then export the rows of relevant data to a new sheet.
Sub Run_All_Macros()
Application.ScreenUpdating = False
Sheets.Add.Name = "Output"
Call Convert_to_Numbers
Call Highlight_Selected_Contractors
End Sub
'Original Spreadsheet is formatted incorrectly
'Convert PSD Codes to Numbers
Sub Convert_to_Numbers()
Dim xCell As Range
Range("A2:A2500").Select
For Each xCell In Selection
xCell.Value = CDec(xCell.Value)
Next xCell
End Sub
'Highlight Selected Contractors
Sub Highlight_Selected_Contractors()
Dim Full, Selection, Code, SelectedCode As Range
Worksheets("Sheet1").Select
'Set all cells in Column A Sheet 1 to Full
Set Full = Worksheets("Sheet1").Range("A1", Range("A1").End(xlDown))
'Set all cells in Column A Sheet 2 to Selection
Worksheets("Sheet2").Select
Set Selection = Worksheets("Sheet2").Range("A1", Range("A1").End(xlDown))
'If the numbers match highlight the cell
For Each Code In Full
For Each SelectedCode In Selection
If Code.Value = SelectedCode.Value Then
*** Code.Select
Selection.Copy
Sheets.Select ("Output")
ActiveSheet.Paste
End If
Next SelectedCode
Next Code
End Sub
After executing this code column A in 'Output' is filled with zeros from A2:A2500. From messing around with breakpoints I've identified the problem to be where I've placed * but I'm not sure what's wrong with what's written there.
Thanks
There few errors in the code above and I also have few suggestions and finally the code.
ERRORS
1) Sheets.Add.Name = "Output" This line will give you an error if there is already a sheet called "Ouput". Delete the sheet first and then create it. You must be wondering that in case the sheet is not there, then how can I delete it? For such scenarios you can use On Error Resume Next which should be avoided in most cases.
2) When working with ranges, always specify which sheet you are referring to else Excel will always assume that you are referring to the "ActiveSheet". As you realized that Sub Convert_to_Numbers() was taking Output Sheet into consideration whereas you want the operation to happen in "Output" Sheet.
3) Dim Full, Selection, Code, SelectedCode As Range As mentioned in my comments earlier avoid using Excel Reserved words as variables. Also unlike VB.Net, if you declare variables as you did in VBA then only the last variable will be declared as Range. The other 3 will be declared as variant. VB defaults the variable to being type Variant. A Variant type variable can hold any kind of data from strings, to integers, to long integers, to dates, to currency etc. By default “Variants” are the “slowest” type of variables. Variants should also be avoided as they are responsible for causing possible “Type Mismatch Errors”. It’s not that we should never use Variants. They should only be used if you are unsure what they might hold on code execution.
4) Avoid the use of words like .ActiveCell, Selection, Select, Activate etc. They are a major cause of errors. Also they slow your code down.
SUGGESTIONS
1) Instead to using Sheets("WhatEver") every time, store it in a variable and then use that variable. Will cut down your code.
2) Indent your code :) it's much easier to read
3) Group tasks together. For example if you have to do with something with a particular sheet then keep it together. It is easier to read and amend if required.
4) Instead of hard coding your values, get actual ranges. Range("A2:A2500") is a classic example. Will you always have data till 2500? What if it is less or more?
5) End(xlDown) will never give you the last row if there is a blank cell in between. To get the last row in a column, say A in "Sheet1", use this
Sheets("Sheet1").Range("A" & Rows.Count).End(xlUp).Row`
6) Instead of looping, you can use the WorksheetFunction CountIf(). Loops should be avoided as much as possible as they slow down your code.
7) Use appropriate Error handling.
8) Comment your code. It's much easier to know what a particular code or section is doing.
CODE
Option Explicit
Sub Run_All_Macros()
Dim ws1I As Worksheet, ws2I As Worksheet, wsO As Worksheet
Dim ws1LRow As Long, ws2LRow As Long, wsOLr As Long
Dim xCell As Range, rFull As Range, rSelection As Range
Dim rCode As Range, rSelectedCode As Range
On Error GoTo Whoa '<~~ Error Handling
Application.ScreenUpdating = False
'~~> Creating the Output Sheet
Application.DisplayAlerts = False
On Error Resume Next
Sheets("Output").Delete
On Error GoTo 0
Sheets.Add.Name = "Output"
Application.DisplayAlerts = True
'~~> Working with 1st Input Sheet
Set ws1I = Sheets("Sheet1")
With ws1I
'~~> Get Last Row of Col A
ws1LRow = .Range("A" & Rows.Count).End(xlUp).Row
'~~> Set the range we want to work with
Set rFull = .Range("A1:A" & ws1LRow)
'~~> The following is not required unless you want to just format the sheet
'~~> This will have no impact on the comparision. If you want you can
'~~> uncomment it
'For Each xCell In .Range("A2:A" & ws1LRow)
'xCell.Value = CDec(xCell.Value)
'Next xCell
End With
'~~> Working with 2nd Input Sheet
Set ws2I = Sheets("Sheet2") '<~~ Input Sheet 2
ws2LRow = ws2I.Range("A" & Rows.Count).End(xlUp).Row
Set rSelection = ws2I.Range("A1:A" & ws2LRow)
'~~> Working with Output Sheet
Set wsO = Sheets("Output")
wsO.Range("A1") = "Common values"
wsOLr = wsO.Range("A" & Rows.Count).End(xlUp).Row + 1
'~~> Comparison : If the numbers match copy them to Output Sheet
For Each rCode In rFull
If Application.WorksheetFunction.CountIf(rSelection, rCode.Value) > 0 Then
rCode.Copy wsO.Range("A" & wsOLr)
wsOLr = wsOLr + 1
End If
Next rCode
MsgBox "Done"
LetsContinue:
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume LetsContinue
End Sub
Let me know if you still get any errors :)
HTH

Resources