AdvancedFitler Out Values from ListRange using Formula - excel

I'm trying to setup AdvancedFilter to filter out a ListRange of items. After some testing, I realized that it only accepts using a "formula" of <>A when I use a criteria range of 2 cells. If I add a third <>B it just does nothing.
My original thought was simple to prepend to my column <> to each cells value, but now it seems that won't work. I need to figure out a way to have both a formula and a range somehow applied.
IE:
Data:
Let Num
A 1
B 2
C 3
This Works for Filter Range:
Let
<>B
This Doesn't:
Let
<>B
<>C
But my CriteriaRng looks like this:
Let
B
C
How I can reference a way to say for all items in Let column, Filter <>Cell.Value in CriteriaRange:=
Here's the basic code I tried/debugged this issue with:
FilterRng.AdvancedFilter Action:=xlFilterInPlace, CriteriaRange:=Range("D1:D3"), Unique:=False
Stop
FilterRng.AdvancedFilter Action:=xlFilterInPlace, CriteriaRange:=Range("D1:D2"), Unique:=False
Stop
Updates:
I found this example --> https://www.mrexcel.com/board/threads/with-adavnced-filter-how-do-i-exclude-a-value.733153/page-2
=ISNA(MATCH($A9,Exclude!$A$1:$A$2,0))
But I'd need to built that formula via VBA and make it much more generic. I'm better w/ VBA then formula's.
I also read in this post that he basically uses highlighting via regular filter, then another filter based on highlighting, but I know there is a better way utilizing a formula in a cell.
https://stackoverflow.com/a/34012365/5079799
I think I also somewhere you can do "or" operations when staggering rows w/ advanced filter, so I could make my column a staggered column, but that also sounds hacky and I couldn't get it to work on my brief attempt.

If you have multiple lines in your Criteria you're doing an OR operation. If you want to do an AND operation you need a single line in your criteria but the same Caption listed multiple times, see below.
If you name your ranges: Database, Criteria, and Extract respectively then record a macro and run the advanced filter it will write the code for you. You can then modify the code to accept variable input.

I basically copied my answer from this one, but built the FormulaStr and automated it more, as thats the point of VBA!
https://stackoverflow.com/a/28506854/5079799
Sub Test()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim CriteriaRng As Range, DataRng As Range
Set CriteriaRng = ws.Range("D1:D3")
Set DataRng = ws.Range("A1:B4")
Dim FormulaRng As Range, FormulaStr As String, DataRngCellTwoStr As Range
Set DataRngCellTwoStr = Cells(DataRng.Row + 1, DataRng.Column)
Set FormulaRng = ws.Range(Cells(2, CriteriaRng.Column + 1), Cells(2, CriteriaRng.Column + 1))
FormulaStr = "=ISNA(MATCH(" & DataRngCellTwoStr.Address(False, False) & "," & CriteriaRng.Address & ",0))"
FormulaRng.Value = FormulaStr
ws.Range(Cells(1, CriteriaRng.Column + 1), Cells(1, CriteriaRng.Column + 1)).Clear
Set FormulaRng = ws.Range(Cells(1, CriteriaRng.Column + 1), Cells(2, CriteriaRng.Column + 1))
DataRng.AdvancedFilter Action:=xlFilterInPlace, CriteriaRange:=FormulaRng, Unique:=False
End Sub
Notes:
You must enter Formula on 2nd row and make FilterRng exactly 2 rows!
The Header Should be BLANK
Formula should looks like this =ISNA(MATCH(A2,$D$1:$D$3,0)) with A2 being first row below headers of criteria column in filter range and D1:D3 being the criteria column.

Related

Countif with reference to different sheet

Hello I will need help with problem I am facing right now and even Google couldn't help me.
I would like to add to field AS2 a COUNTIF formula with source information from different sheet.
This COUNTIF should jump to sheet ee_lpk and then take a range from column A2 down to the end of last used row. and compare that with criteria from field D.
so for AS2 will be comparing with D2 for AS3 with D3.
When I recorded that it showed:
ActiveCell.FormulaR1C1 = COUNTIF(ee_lkp!R[-143]C[-44]:R[217]C[-44],R[-143]C[-41])"
this is working but just in case that there is on ee_lpk page same number or rows what is changing from day to day.
Any help will be much appreciated.
Martin
You need to break this problem down using variables. Try something like this:
sub Answer()
Dim srcRng as Range
Dim srcLength as Long
'First find how many rows on sheet ee_lpk and store it as a variable
srcLength = Sheets("ee_lkp").UsedRange.Rows.Count
'Then use that variable to get your range
Set srcRng = Range(Cells(2,1), Cells(srcLength, 1))
'Or another viable option would be:
'Set srcRng = Range("A2:A" & srcLength)
'Then put that in your Countif formula
ActiveCell.FormulaR1C1 = _
"=COUNTIF(ee_lkp!" & srcRng.Address(True, True, xlR1C1) & ", R[-143]C[-41])
End Sub

vba column address from column number

I have a column number , say columnNumber = 4 . I need the used range of this column. I know how to find the last used row, and I could convert the column number to a column number like so
ColumnLetter = Split(Cells(1, ColumnNumber).Address, "$")(1)
LastRow = sht.Cells(sht.Rows.Count, ColumnLetter).End(xlUp).Row
and then build an address like so
rngaddy = ColumnLetter & "1:" & ColumnLetter & LastRow
and finally do
Range(rngaddy)
But is there an easier way to find the complete used range of a column given it's number ?
Dim rngaddy As Range
With Sheet1
Set rngaddy = .Range(.Cells(1, 4), .Cells(.Rows.Count, 4).End(xlUp))
End With
and if, for some reason, you want to see the address in A1 notation, merely:
debug.print rngaddy.address
Note that in doing it this way, rngaddy is, itself, the range object and not a string. So no need to do Range(rngaddy)
You could return the last populated cell is in columns # col with this:
MsgBox Cells(sht.Rows.Count,col).End(xlUp).Address
If you want to return the first populated cell as well, you could use:
MsgBox IIf(IsEmpty(Cells(1,col)),Cells(1,col).End(xlDown),Cells(1,col)).Address
Therefore this would return only the "used" range of Column #4 (D):
Sub Example_GetUsedRangeOfColumn()
Const col = 4
Dim sht As Worksheet
Set sht = Sheets("Sheet1")
MsgBox Range(IIf(IsEmpty(Cells(1, col)), Cells(1, col).End(xlDown), _
Cells(1, col)), Cells(sht.Rows.Count, col).End(xlUp)).Address
End Sub
So with this example:
...the above procedure would return: .
My preferred method is to use ListObjects aka Excel Tables to hold any input data whenever I possibly can. ListObjects are named ranges that Excel automatically maintains on your behalf, and because they grow automatically when new data is added, they give you a very robust way of referencing ranges in Excel from VBA, that is more immune to users doing things that might otherwise break code reliant on the .End(xlUp) approach.
? Range("MyTable").ListObject.ListColumns("Column 1").DataBodyRange.Address
$A$3:$A$7
Often I'll give the column concerned a named range of its own, in case the user (or a developer) later wants to change the Table column name, and use that name in my code instead.
? Range("FirstColumn").Address
$A$3:$A$7
If somebody (perhaps me) adds rows/columns above/left of the range of interest or shuffles the order of Table columns around, or changes the name of a column, the code still references the intended range and doesn't need to be changed.
? Range("FirstColumn").Address
$C$4:$C$8
? Range(Range("FirstColumn").Address & ":" & Range("FirstColumn").EntireColumn.cells(1).address).Address
$C$1:$C$8
Granted, that method of getting the range from the top cell (which may be above the ListObject) to the bottom of the column concerned is kinda long, but once you start using ListObjects more in your code you normally don't care what is above or below them...you just want the goods held inside.
I haven't used .End(xlUp) in years, other than to find where my data ends should I be in the process of turning it into a ListObject. But I'm a ListObject evangelist...your mileage may vary :-)
to get the real UsedRange of a columns you could use:
With Columns(columnNumber).SpecialCells(xlCellTypeConstants)
Set rngaddy = .Parent.Range(.Areas(1), .Areas(.Areas.Count))
End With
where rngaddy is a Range object
of course what above would fail if the column has no "constant" cells, then you may want to add some error trapping or entry check (e.g. If WorksheetFunction.CountA(Columns(columnNumber)) = 0 Then Exit Sub
Or
Option Explicit
Public Sub test()
Const columnNumber As Long = 4
Dim rngaddy As Range
Set rngaddy = Intersect(Columns(2), ActiveSheet.UsedRange): Debug.Print rngaddy.Address
End Sub

Inserting a formula all the way to the last row in the last empty column

I am fairly new to writing macros in VBA, but I am doing my best to learn as much as I can as quickly as possible. Anyway, the task I am trying to perform is pretty simple, but I'm having trouble coming up with a way to do it.
What I want to do is paste a formula into all of the rows in the last empty column of a spreadsheet.
So in this case, into the highlighted cells shown in the screenshot:
Example:
However, I don't want to rely on typing ("K1:K" & lastrow), what I want to do is create reference to the last column and the last row.
So far I've only been able to paste the formula into the entire column by using this:
lastcol = .Cells(1, .Columns.Count).End(xlToLeft).Offset(0, 1).Column
fvlookup = "=vlookup(#1,#2,#3,False)"
fvlookup = Replace (fvlookup, "#1", rng.Address)
fvlookup = Replace (fvlookup, "#2", [LookupFile.csv]LookupFile!$B:$1")
fvlookup = Replace (fvlookup, "#3", "5")
.Columns(lastcol).Formula = fvlookup
But later on in the process I'm working on, I want to remove all of "#N/A" and place them into a different tab named "JEs" because some of the items in the table actually don't have a value in the table I'm looking up to, and need JEs created for them. So, I would have a lot of unnecessary rows in the new tab if I went down this route.
Anyway, I've tried this:
lastcol = .Cells(1, .Columns.Count).End(xlToLeft).Offset(0,1).Column
fvlookup = "=VLOOKUP(#1,#2,#3,False)"
fvlookup = Replace(fvlookup, "#1", rng.Address)
fvlookup = Replace(fvlookup, "#2", "[LookupFile.csv]LookupFile!$B:$I")
fvlookup = Replace(fvlookup, "#3", "5")
With .Columns(lastcol)
lrow = .range("A" & .Rows.Count).End(xlUp).Row
.Rows(lrow).Formula = fvlookup
End With
But it only places the formula into "K1" (referencing the attached image)
I've also tried selecting this value after the formula is pasted and auto filling (I know it is advised to avoid selecting ranges with vba, but I still wanted to try as a last resort), but it says it isn't allowed.
I've also tried using:
.range(lastcol & .range("A" & .Rows.Count).End(xlUp).Rows).Formula = fvlookup
But that gives me run-time error '1004': Application-defined or object-defined error. I tried creating a variable named 'lrange' and setting it similar to how I set lastcol, but no luck (it returns the same error message as above). I've also tried editing lastcol to lastcol.Column or .Columns(lastcol) in the above string, but still nothing.
I've tried researching similar questions and all of the recommendations advise defining two variables (a lastrow one and a lastcolumn one) and inserting the formula into that range, but that doesn't seem to work with what I'm using.
I feel like the process VBA is trying to execute is restricted to only being able to insert data into the entire column, or part of it, with the way I set the macro up, like it's not able to find the last column and insert all the way to the last row with the way I set it up. So I apologize if this is the case, or if I should have written it differently.
Any advise or direction anyone can provide on this topic would be much appreciated.
Thank you for your time!
Instead of looping at the end I would just use .FillDown
Cells(2, lastcol).FormulaR1C1 = fvlookup
Range(Cells(2, lastcol), Cells(lrow, lastcol)).FillDown
How about replacing your code with something like this:
Sub foo()
lastcol = .Cells(1, .Columns.Count).End(xlToLeft).Offset(0, 1).Column
fvlookup = "=VLOOKUP(#1,#2,#3,False)"
fvlookup = Replace(fvlookup, "#1", Rng.Address)
fvlookup = Replace(fvlookup, "#2", "[LookupFile.csv]LookupFile!$B:$I")
fvlookup = Replace(fvlookup, "#3", "5")
lrow = .Range("A" & .Rows.Count).End(xlUp).Row
For i = 2 To lrow
.Cells(i, lastcol).Formula = fvlookup
Next i
End Sub
This will loop from row 2 to lastrow and add the formula to lastcol.
As long as the table doesn't have an entirely blank row or column (as it shouldn't) Range("A1").CurrentRegion identifies the extent of the table. Then .Rows.Count and .Columns.Count gives you the information you need to be able to populate the adjacent column with a formula.
You can also fill the column's cells with formulas in one go, using FormulaR1C1 - provided you are careful with cell referencing. Here is an example:
Dim tableRange As Range
Dim x As Integer, y As Integer
Set tableRange = Range("A1").CurrentRegion
x = tableRange.Rows.Count
y = tableRange.Columns.Count
'fill the adjacent column with a SUM function summing across each row
Range(Cells(2, y + 1), Cells(x, y + 1)).FormulaR1C1 = "=SUM(RC[-" & y & "]:RC[-1])"
(You can also use y and 1 to give this new column a heading.)
If you need to replicate VLOOKUP with a specific (absolute) address such as $B:$I then I would first name that range and insert the name in the formula.
An example of such is:
.FormulaR1C1 = "=VLOOKUP(RC[-1],mylookup,2,FALSE)"
where mylookup is the range name and RC[-1] is referencing the cell immediately to the left of each formula.

Extending Formula

I have a list of values all in a row (500+), like:
AAC80013, /ACY03537, /ADC64131, /AED59827, /AKC13125, /APS84849, etc...
and would like to know to merge them all into one cell so they are as follows:
AAC80013, ACY03537, ADC64131, AED59827, AKC13125, APS84849,
I have tried to do a merge and center and this didn't work. I have however found a way to do this by using an =A4&B4&C4 etc... but I was hoping someone could either advise me of a quicker way to merge them rather than go though and do each one individually.
Based on your question's tags it sounds like you are looking for a simple, non-macro solution.
The process of stitching together multiple text values into one text value is called concatenation. You've discovered one method to do this with formulas using ampersands & between the items. Another is the CONCATENATE() function that can be used in a formula. But frankly, both of these are terrible if you want to do a lot of concatenations. The CONCATENATE() function is particularly irksome because while it should accept a range of values to stitch together, it does not!
So if the concatenation work is complex, the most common way to simplify the workload is to write a VBA macro. But that's programming and requires you to know a few things.
Here is a simple alternative. It is a manual workflow that can make this task very quick and painless.
Let's assume your values are in the range A4:Z4 and also assume that row 5 is empty. Just follow these steps:
1.) In cell B5 enter this formula:
=SUBSTITUTE(A5&A4&B4,"/",", ")
2.) Now copy B5 and select the range C5:Z5 and paste.
3.) By now Z5 will look fearsome. No worries. Copy Z5.
4.) Right-click on the cell you wish to have your final list in and select Paste Special - Values.
5.) Select row 5 and delete all of that.
That's it. It takes about two seconds once you get the hang of it.
Sub combine()
Dim lastCol As Integer, xRow As Integer
Dim cel As Range, rng As Range
Dim delimiter As String, firstCellInfo As String
firstCellInfo = Cells(1, 1).Value
xRow = 1 'change this to the row with your data.
delimiter = "/"
lastCol = ActiveSheet.UsedRange.Columns.Count
Set rng = Range(Cells(xRow, 1), Cells(xRow, lastCol))
For Each cel In rng
If Left(cel.Value, Len(delimiter)) = delimiter Then
Debug.Print Right(cel.Value, Len(cel.Value) - Len(delimiter))
cel.Value = Right(cel.Value, Len(cel) - Len(delimiter))
End If
If cel.Column > 1 Then firstCellInfo = firstCellInfo + ", " + cel.Value
Next cel
Cells(2, 1).Value = firstCellInfo
End Sub
Note: Change the xRow to whatever row has your data. Also, there's a delimiter / in each cell except the first one - so the loop will remove that, if it exists, and add the result to a string firstCellInfo. At the end of the loop, I placed this combined data into B1 - just so you can run this and make sure this works. If you want to put the info back in A1, just change Cells(2,1).Value to Cells(1,1).Value.
Also, if you want to delete the extra data (columns B onward), just add this after the Cells(2,1).Value = firstCellInfo:
Set rng = Range(Cells(xRow, 2), Cells(xRow, lastCol))
rng.Clear

Select top-n rows in table after filter with VBA

It seems that applying filter to a table has destroyed my understanding of how to handle this.
I have a table with multiple columns. I'm going to filter on one of the columns and sort on another. After that, I want to select/copy the first 10 rows of specific columns of what's filtered into another table.
I could easily do this before filters. I need the first 10 rows AFTER the filter is applied. I'm not seeing how to choose the 10th row AFTER a filter.
Can anyone point me to a VBA reference that explains how to do this? Do I need to use SQL to do this? Am I over thinking this and making it too complicated?
The following works to select the first 10 visible cells of column F, after filtering is applied. You'll need start at F2 if you want to exclude the header-cell.
Sub TenVisible()
Dim rng As Range
Dim rngF As Range
Dim rng10 As Range
Set rngF = Range("F:F").SpecialCells(xlCellTypeVisible)
For Each rng In Range("F:F")
If Not Intersect(rng, rngF) Is Nothing Then
If rng10 Is Nothing Then
Set rng10 = rng
Else
Set rng10 = Union(rng10, rng)
End If
If rng10.Cells.Count = 10 Then Exit For
End If
Next rng
Debug.Print rng10.Address
'.. $F$1:$F$2,$F$4:$F$5,$F$9:$F$10,$F$12,$F$20:$F$21,$F$23
rng10.Select
End Sub
The following is one of a number of ways to select from F2 downwards (assuming the UsedRange starts from row 1):
Set rngF = Range("F2", Cells(ActiveSheet.UsedRange.Rows.Count, _
Range("F2").Column)).SpecialCells(xlCellTypeVisible)
For what it's worth, seeing as this is one of the first search results for this kind of issue -
after filtering a table on a named column like this :
Worksheets("YourDataSheet").ListObjects("Table_Name").Range.AutoFilter _
field:=Worksheets("YourDataSheet").ListObjects("Table_Name").ListColumns("ColumnName").Index, _
Criteria1:="FilterFor..."
... I was then able to copy the resulting single visible row to another sheet using :
Worksheets("YourDataSheet").Range("Table_Name").SpecialCells(xlCellTypeVisible).Range("A1").EntireRow.Copy _
Destination:=Range("AnotherSheet!$A$2").EntireRow
So that's one way to refer to visible rows after the filtering. HTH.

Resources