Understanding How Many Configurations of Product are Sold - excel

Each configuration is set-up as a different Part number that makes up one complete device. We want to find out how many of each full configuration we have sold to tell the highest selling combinations. The configuration numbers relate to an option of the product for instance 12345-W means wireless.
We need to find out how many of each string of configurations we have to count them. This has a few columns that will help to find the configuration. You can look at the line number and the sales number to ensure that they are in the same grouping on the sales order. For instance one whole config that makes up a finished product will have line number 1 for all parts associated with the finished product, going down the sales order numerically. We can use this in combination with the sales order to come up with the Configuration String. Then we can look at the config column and part column to differentiate the base config from the options. The "C" in the Config column tells us it's the base model, the "X" tells us it's an option.
With this information we need to create the configuration string shown as a manual example in blueish/purple column, photo linked below. Once we have the string it's just counting the "C" config option only to avoid double counting then we can make a Pivot Table to tell how many duplicates of the same option there are and filter for C only. These are multiple different products, and multiple different configurations of different products.
Here is the Set-up:
Data example: the Blue column is an example manually of what is needed
Some thoughts I had were an If, then statement, Concatenation IF, or a Macro. But nothing has worked out so far. Any advice would be greatly appreciated!

To be able to do that efficient you need to ensure that the data is sorted in the following way:
by Sales
by linenum
by Config
by Part
It needs to be sorted by all 4 columns at once and in this order or you might end up with messed up data!
It is also assumed that the option numbers are always prefixed with the corresponding base number plus a dash.
Then we read the columns we need into arrays (for faster processing). We loop through that data line by line. When we find a C in config we rember that row number (for comparison of the X options and for writing the output). We move on with the next row and check if it is an option and if it belongs to the rembered base config. If so we append the option part no to the base config.
In the end we write the array data back to the cells.
Option Explicit
Public Sub GetFullConfigFromOptions()
Dim ws As Worksheet ' define your sheet here
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
' read data columns into array
Dim ArrSalesNo() As Variant 'define Sales column
ArrSalesNo = ws.Range("A1").Resize(RowSize:=LastRow).Value
Dim ArrConfig() As Variant 'define Config column
ArrConfig = ws.Range("B1").Resize(RowSize:=LastRow).Value
Dim ArrPartNo() As Variant 'define Part column
ArrPartNo = ws.Range("C1").Resize(RowSize:=LastRow).Value
Dim ArrLineNo() As Variant 'define Linenum column
ArrLineNo = ws.Range("E1").Resize(RowSize:=LastRow).Value
' create output array
Dim ArrOut() As Variant 'define String column
ArrOut = ws.Range("D1").Resize(RowSize:=LastRow).Value
Dim CurrentConfigRow As Long
Dim iRow As Long
For iRow = 2 To LastRow
If ArrConfig(iRow, 1) = "C" Then
' is config line …
CurrentConfigRow = iRow ' remember line number for output
If iRow = LastRow Then
' base without options (in last row)
ArrOut(CurrentConfigRow, 1) = ArrPartNo(CurrentConfigRow, 1) ' write base part number to output (without dash)
ElseIf Not ArrSalesNo(CurrentConfigRow + 1, 1) = ArrSalesNo(CurrentConfigRow, 1) Or _
Not ArrLineNo(CurrentConfigRow + 1, 1) = ArrLineNo(CurrentConfigRow, 1) Or _
Not ArrConfig(CurrentConfigRow + 1, 1) = "X" Then
' base without options
ArrOut(CurrentConfigRow, 1) = ArrPartNo(CurrentConfigRow, 1) ' write base part number to output (without dash)
Else
' base with options
ArrOut(CurrentConfigRow, 1) = ArrPartNo(CurrentConfigRow, 1) & "-" ' write base part number to output (including dash)
End If
Else
' check if it is an option line of the remebered config line
If ArrSalesNo(iRow, 1) = ArrSalesNo(CurrentConfigRow, 1) And _
ArrLineNo(iRow, 1) = ArrLineNo(CurrentConfigRow, 1) And _
ArrConfig(iRow, 1) = "X" Then
' is option line (so append to output)
ArrOut(CurrentConfigRow, 1) = ArrOut(CurrentConfigRow, 1) & Mid$(ArrPartNo(iRow, 1), Len(ArrPartNo(CurrentConfigRow, 1)) + 2)
End If
End If
Next iRow
' write ouput to cells
ws.Range("D1").Resize(RowSize:=LastRow).Value = ArrOut
End Sub
The output will look like

Related

Sort Worksheets based on multiple cell values

This is a sample of K4 L4
[![enter image description here][2]][2] This is a sample of K63
So again I want to arrange each worksheet in the workbook based on the values in K4, L4 and than K63
Hello I'm looking for a code that will sort the worksheets in the workbook based on multiple cell values. First I will like to sort all worksheets in the workbook based on K4 (text Ascending Order) than by L4 (text Ascending Order) and finally by cell k63 (value greatest to least). I'm struggling with the logic piece on how to make it vba go in sequence. Any insight will be greatly appreciated.
I hid rows and delete sensitive data. But from the screen shot you can basically get the jist of how I would like the worksheets arranged
The following code shows how you could achieve this:
Create an array of objects that hold the information for every sheet, including the sheet name itself
Sort the array according to your needs. I have used a simple bubble sort as it is fast enough for 100 records - but if you want, feel free to look for more efficient sort algorithms, plenty around here on SO and elsewhere. The key of sorting is that you have a custom compare method that returns -1 if object 1 is "smaller" (needs to be sorted to the left) and 1 if it is "larger" - very similar to the strComp-method in VBA.
After sorting, use the sheet names of the sorted array to rearrange the sheets.
Create a class module and name it clsSheetData that holds the information needed for sorting.
Public sheetname As String
Public gmo As String
Public ovp As String
Public percent As Double
Create a regular module with the code (I assume you want to sort ThisWorkbook, else pass the workbook as parameter)
Sub SortSheets()
' Define the array
ReDim arr(1 To ThisWorkbook.Sheets.Count) As clsSheetData
' - - Step 1: Build array with data
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
Set arr(ws.Index) = New clsSheetData
arr(ws.Index).sheetname = ws.Name
arr(ws.Index).gmo = ws.Range("K4")
arr(ws.Index).ovp = ws.Range("L4")
arr(ws.Index).percent = ws.Range("K63")
Next
' - - Step 2: Sort Array (Bubblesort)
Dim i As Long, j As Long
For i = 1 To UBound(arr) - 1
For j = i + 1 To UBound(arr)
If sheetCompare(arr(i), arr(j)) > 0 Then
Dim Temp As clsSheetData
Set Temp = arr(j)
Set arr(j) = arr(i)
Set arr(i) = Temp
End If
Next j
Next i
' - - Step3: Rearrange sheets
For i = 1 To UBound(arr)
With ThisWorkbook
.Sheets(arr(i).sheetname).Move before:=.Sheets(i)
End With
Next
End Sub
Function sheetCompare(o1 As clsSheetData, o2 As clsSheetData) As Integer
' Compare the data of 2 sheets.
If o1.gmo <> o2.gmo Then ' If gmo is different, use that as criteria
sheetCompare = StrComp(o1.gmo, o2.gmo, vbTextCompare)
ElseIf o1.ovp <> o2.ovp Then ' Else If ovp is different, use that as criteria
sheetCompare = StrComp(o1.ovp, o2.ovp, vbTextCompare)
Else ' Else, compare percentage
sheetCompare = IIf(o1.percent > o2.percent, -1, 1)
End If
End Function

extract specific rows of .tbl files in excel

I have an excel file with the following links:
These links are connected to files with the following data:
I want the yellow part of the notepad file to be read into the .xlsx file yellow parts (the notepad is an opened version of the .tbl file). The dotted parts differ for each Version number. (This code is used as a check that the right discount curve is used). However, the discount_curve.tbl format is the only format the next programme used is able to handle. Therefore, it has the same name just in a different folder.
Is there a way excel/vba can read in every third line whilst the file read in depends on the folder link? I strongly prefer to have the whole process automated since there are many many version numbers. Furthermore, I do not want to change the file formatting, since I want the process to be as clean as possible.
Could someone help me out?
Kind regards.
Please, try the next function, if the necessary data to be extracted exists in a single file, at every three rows.. It will return a 2D array able to be dropped at once in the range you need:
Function extractThirdLine(filePath As String) As Variant
Dim arrTxt, i As Long, arrFin, k As Long
'read the file content in an array:
arrTxt = Split(CreateObject("Scripting.FileSystemObject").OpenTextFile(filePath, 1).ReadAll, vbCrLf)
ReDim arrFin(1 To Int(UBound(arrTxt) / 3) + 1, 1 To 1)
For i = 2 To UBound(arrTxt) Step 3 'start from 2, because arrTxt is 1D array
k = k + 1
arrFin(k, 1) = arrTxt(i) 'build the filal array containing the necessary rows
Next i
extractThirdLine = arrFin
End Function
Your picture does not show the rows and columns headers. So, supposing that the range you show us exists in columns "A:C" and you need to place the extracted data in column "D:D", please use the next way:
Sub testExtractThirdLine()
Dim filePath As String, arrVal, el
filePath = "your text file full name" 'please write here the correct file name
arrVal = extractThirdLine(filePath)
Range("D1").Resize(UBound(arrVal), 1).value = arrVal
End Sub
If the range you show is not the one I supposed, you cam easily adapt Range("D1") to the immediately after the columns range and its row to be the first row of the range in discussion.
If something not clear enough, please do not hesitate to ask for clarifications.
Edited:
But if each third line can be found in a file, for each row, and the path to the respective file is obtained by concatenation of the three columns, the next function will do the job:
Function extractLine(filePath As String) As String
extractLine = Split(CreateObject("Scripting.FileSystemObject").OpenTextFile(filePath, 1).ReadAll, vbCrLf)(2)
End Function
It can be called as:
Sub extractStrings()
Dim i As Long, arr, arrFin, lastRow As Long
lastRow = Range("A" & rows.count).End(xlUp).Row 'supposing that 'C:\' exists in A:A column
arr = Range("A2:C" & lastRow).value
ReDim arrFin(1 To UBound(arr), 1 To 1)
For i = 1 To UBound(arr)
arrFin(i, 1) = extractLine(arr(i, 1) & arr(i, 2) & arr(i, 3))
Next i
'drop the processed array content at once:
Range("D2").Resize(UBound(arrFin), 1).value = arrFin
End Sub
Seems like you're looking for common I/O opearations i.e. reading file line by line.
Pretty good example was shown [here][1]
To reach your goal we need to add some if-conditions to extract every third line of your text files.
Modulo division will be a good helper.
For example we have 'i' as row number
then we just need to make an if condition looks smth like that:
If (i mod 3) = 0 Then ...
It means that we're looking for every 'i' which divided by 3 gives us a remainder of 0
This way our code will look something like this
Sub ReadFileLineByLine()
Dim my_file As Integer
Dim text_line As String
Dim file_name As String
Dim i As Integer
file_name = "C:\text_file.txt"
my_file = FreeFile()
Open file_name For Input As my_file
i = 1
While Not EOF(my_file)
Line Input #my_file, text_line
If (i mod 3) = 0 Then
Cells(i, "A").Value = text_line
End If
i = i + 1
Wend
End Sub
[1]: https://excel.officetuts.net/vba/read-a-text-file/#:~:text=Reading%20a%20file%20line%20by%20line,-Let's%20read%20text&text=Open%20VBA%20Edit%20(Alt%20%2B%20F11,and%20insert%20the%20following%20code.&text=First%2C%20a%20new%20file%20is,places%20it%20inside%20a%20worksheet.
You can create a User function that will read the lines from the given file and return the third one.
Here is such a function (Disclaimer: there is no error management in this code it can probably be improved a lot)
Function Get3rdLine(filename As String)
Dim f As Long
f = FreeFile
Open filename For Input As f
Line Input #f, Get3rdLine ' just ignore this line
Line Input #f, Get3rdLine ' and this one too
Line Input #f, Get3rdLine ' and return this one
Close #f
End Function
You can call it with the path of the file you want to read from:
=Get3rdLine(CONCATENATE(A1,B1,C1)) for example if your path is defined by cells A1, B1 and C1.

I need to count instances of rows and delete duplicates based on multiple column values

BASE TABLE
FINISHED PRODUCT
So I'm working on sorting a CSV export into a format that will allow myself and the people in my department to quickly copy and paste information into a workbook that already exists. There are several formulas and codes that the existing workbook runs so I can't just create a new workbook using the formatting that the CSV export automatically does. Basically I need to take multiple rows of information that have multiple columns of identifiers and count/sum those rows and get rid of the duplicates but I need that row to have all of the corresponding information in the columns in it. I've tried the standard excel formulas and I can get sub totals or delete and sums, but it doesn't carry the rest of the information into it.
So final order of info to check if matched duplicates would be SKU, Floor Lvl, Detail, Room, Lable
Thank you for any help you can give!
As #teylyn suggests, Pivot Table is the way to go :
Select your data including headers
Insert > Pivot Table
In the "Row Labels" box, drop all your fields in order "Label" on top then "Style" then "SKU" ... except for "Count"
Drop the "Count" field in the "Values" box and set it to "Sum of Count"
PivotTable Tools > Design > Report Layout > Show in Tabular Form
PivotTable Tools > Design > Report Layout > Repeat All Item Labels
PivotTable Tools > Design > Grand Totals > Off for Rows and Columns
PivotTable Tools > Design > Subtotals > Do Not Show Subtotals
I get the same result as your "Finished Product".
As per existing comments/answers, PivotTable is probably the way to go. But maybe below is okay for you too (assuming it works). You'll need to assign PathToCSV.
Option explicit
Sub GroupCSVbyColumns()
Dim PathToCSV as string
PathToCSV = "C:\New Folder\ff.csv" 'Replace with actual path.'
If len(dir(PathToCSV)) >0 then
Dim ContentsOfCSV as string
Open PathToCSV for binary access read as #1
ContentsOfCSV = space$(lof(1))
Get #1,1, ContentsOfCSV ' Assumes file will fit in memory'
Close #1
Dim RowsInCSV() as string
RowsInCSV = split(ContentsOfCSV, vbNewline, -1, vbbinarycompare) ' Assumes rows are separated by new line character'
Const COMMA_DELIMITER as string = ","
Dim RowIndex as long
Dim OutputList() as string
Dim OutputCounts() as long
Redim OutputList(lbound(RowsInCSV) to ubound(RowsInCSV))
Redim OutputCounts(lbound(RowsInCSV) to ubound(RowsInCSV))
' "So final order of info to check if matched duplicates would be SKU, Floor Lvl, Detail, Room, Lable"
Not sure if it makes a difference in your case, but code below considers every column (apart from ' Count') when determining duplicates -- not just the ones you mentioned.'
Dim MatchResult as variant
Dim MatchesCount as long: MatchesCount = lbound(OutputList) 'this assignment ensures we leave the first element blank and reserved for header row, as we increment MatchCount first.
Dim CurrentRowText as string
Dim CurrentRowCount as long
For RowIndex = (lbound(RowsInCSV)+1) to ubound(RowsInCSV) ' Skip row of headers'
If len(RowsInCSV(RowIndex))>0 then
CurrentRowText = left$(RowsInCSV(RowIndex),instrrev(RowsInCSV(RowIndex),comma_delimiter,-1, vbbinarycompare)-1)
CurrentRowCount = clng(mid$(RowsInCSV(RowIndex),1+instrrev(RowsInCSV(RowIndex),comma_delimiter,-1, vbbinarycompare)))
' Filter function might perform better than Match below. '
MatchResult = application.match(CurrentRowText, OutputList,0)
If isnumeric(MatchResult) then
OutputCounts(clng(MatchResult)) = OutputCounts(clng(MatchResult)) + CurrentRowCount
Else
MatchesCount = MatchesCount + 1
OutputList(MatchesCount) = CurrentRowText
OutputCounts(MatchesCount) = OutputCounts(MatchesCount) + CurrentRowCount
End if
End if
Next RowIndex
Dim TemporaryArray() as string
Dim ColumnIndex as long
TemporaryArray = split(RowsInCSV(lbound(RowsInCSV)),comma_delimiter,-1, vbbinarycompare)
Dim OutputTable(1 to (MatchesCount+1), 1 to (ubound(TemporaryArray)+1))
' Assign all headers from header row; done outside of loop below as all columns are looped through.'
For ColumnIndex = lbound(OutputTable,2) to (ubound(OutputTable,2))
OutputTable(1,ColumnIndex) = TemporaryArray(ColumnIndex-1)
Next ColumnIndex
For RowIndex = (lbound(OutputTable,1)+1) to ubound(OutputTable,1)
TemporaryArray = split(OutputList(rowindex-1),comma_delimiter,-1, vbbinarycompare)
For ColumnIndex = lbound(OutputTable,2) to (ubound(OutputTable,2)-1)
OutputTable(RowIndex,ColumnIndex) = TemporaryArray(ColumnIndex-1)
Next ColumnIndex
OutputTable(RowIndex,ColumnIndex) = OutputCounts(RowIndex-1)
Next RowIndex
Dim OutputSheet as worksheet
Set OutputSheet = Thisworkbook.worksheets.add
OutputSheet.range("A1").resize(ubound(OutputTable,1),ubound(OutputTable,2)).value2 = OutputTable
Else
Msgbox("No file found at " & PathToCSV)
End if
End sub
Untested, written on mobile.

Excel combine Vlookups

I have two files one is a Project Register that holds key information on a project and the other is a Risk log.
There is a 1:m relationship between entries in the Register and the Risk log. What I need to do is combine all of a project risks into one cell inside the project register file.
The matching field in both files is the Project ID field
Is there a way I can do this using a vlookup variant or multiple nested vlookups?
Here's the user-defined function approach I mentioned (adapted from a different VLOOKUP-variant I already had made):
' Acts like VLOOKUP in a 1-to-many scenario by concatenating all values in matching rows
' instead of just returning the first match
Public Function VLOOKUP_MANY(lookup_value As String, lookup_range As Range, column_number As Integer, Optional delimiter As Variant) As Variant
Dim vArr As Variant
Dim i As Long
Dim found As Boolean: found = False
' Set default delimiter
If IsMissing(delimiter) Then delimiter = ", "
' Get values
vArr = lookup_range.Value2
' If column_number is outside of the specified range, return #REF
If column_number < LBound(vArr, 2) Or column_number > UBound(vArr, 2) Then
VLOOKUP_MANY = CVErr(xlErrRef)
Exit Function
End If
' Search for matches and build a concatenated list
VLOOKUP_MANY = ""
For i = 1 To UBound(vArr, 1)
If UCase(vArr(i, 1)) = UCase(lookup_value) Then
VLOOKUP_MANY = VLOOKUP_MANY & delimiter & vArr(i, column_number)
found = True ' Mark at least 1 result
End If
Next
If found Then
VLOOKUP_MANY = Right(VLOOKUP_MANY, Len(VLOOKUP_MANY) - Len(delimiter)) ' Remove first delimiter
Else
VLOOKUP_MANY = CVErr(xlErrNA) ' If no matches found, return #N/A
End If
End Function
This will search the first column in the specified range for the specified value (same as VLOOKUP), but returns the values in the specified column number concatenated. It will return #N/A when no matches are found, and #REF if an invalid value is specified for the column number (e.g. you choose column 5 but only had a 4-column table).
In case you don't know about user-defined functions - you can just copy this VBA code into the VBE for a module in your workbook. Hit Alt+F11, go to Insert > Module at the top of the screen, then paste this code into the blank file that opens up. When you go to save, you'll have to save your workbook as Macro-Enabled (.xlsm) to keep the code working - Excel will remind you about this in the save screen.
Be forewarned: it's going to be slower than VLOOKUP as a result of having to look through the entire lookup range instead of being able to stop at the first match it finds.
If you're open to using an array formula instead, there are ways to speed up this sort of functionality for very large datasets...
Different version that leverages some of the benefits of array formulas to store lookup values and speedup subsequent calls:
' Acts like VLOOKUP in a 1-to-many scenario by concatenating all values in matching rows
' instead of just returning the first match
' Utilizes a dictionary to speedup multiple matches (great for array formulas)
Public Function VLOOKUP_MANY_ARRAY(lookup_values As Range, lookup_range As Range, column_number As Integer, Optional delimiter As Variant) As Variant
Dim vHaystack As Variant, vNeedles As Variant
Dim i As Long
Dim found As Boolean: found = False
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
' Set default delimiter
If IsMissing(delimiter) Then delimiter = ", "
' Get values
vHaystack = lookup_range
vNeedles = lookup_values
' If column_number is outside of the specified range, return #REF
If column_number < LBound(vHaystack, 2) Or column_number > UBound(vHaystack, 2) Then
VLOOKUP_MANY_ARRAY = CVErr(xlErrRef)
Exit Function
End If
' Add values to a lookup dictionary
For i = 1 To UBound(vHaystack, 1)
If dict.Exists(UCase(vHaystack(i, 1))) Then
dict.Item(UCase(vHaystack(i, 1))) = dict.Item(UCase(vHaystack(i, 1))) & delimiter & vHaystack(i, column_number)
Else
dict.Add UCase(vHaystack(i, 1)), vHaystack(i, column_number)
End If
Next
Dim outArr As Variant
If IsArray(vNeedles) Then ' Check number of lookup cells
' Build output array
ReDim outArr(1 To UBound(vNeedles, 1), 1 To 1) As Variant
For i = 1 To UBound(vNeedles, 1)
If dict.Exists(UCase(vNeedles(i, 1))) Then
outArr(i, 1) = dict.Item(UCase(vNeedles(i, 1)))
Else
outArr(i, 1) = CVErr(xlErrNA)
End If
Next
Else
' Single output value
If dict.Exists(UCase(vNeedles)) Then
outArr = dict.Item(UCase(vNeedles))
Else
outArr = CVErr(xlErrNA)
End If
End If
VLOOKUP_MANY_ARRAY = outArr
End Function
This creates a Dictionary, which is a special structure that's really good for looking up values. There's a little extra overhead involved in building it, but once you have the structure, you can do lookups into it very quickly. This is especially nice with array formulas, which is basically when the exact same formula gets put into a whole collection of cells, then the function executes once and returns values for every cell (instead of just executing once, separately, for a bunch of cells). Enter it like an array formula with CTRL+SHIFT+ENTER, and make the first argument refer to all your lookup values instead of just one.
It will work without being used as an array formula, but it will be somewhat slower than the first function in that situation. However, if you use it in an array formula, you'll see huge speedups.
RE-EDIT:
You might need to write a user defined function or write a macro (code on same link)

VBA - 2010 - How to invert a filter

I am wondering about a simple solution for reversing the filtered values. Although it seems to me to be an easy task, I have not had a success while researching on the internet.
The situation and problem: I have a table with multiple columns, and lot of rows (exact amount does not matter obviously) and I want to see what was not filtered in exactly one column. The problem is that I normally need to do a lot of clicking
For example - in the database of projects I have filtered the ones worth over 500 000 €, which are mine and are coming from a specific country. By one click I would like to see which ones are below 500 000 €) but are still mine and coming from a specific country.
Possible solutions which came to my mind:
Create a unique list of what is filtered, unfiltered, and create an unique list of full column, AdvanceFilter by the difference. (That's my white horse - it might work in my opinion)
Go through each filtering options and check/uncheck one by one.
Screenshot filters, transfer to text, create a unique values at the column, invert the filter in advanced filtering (very crazy idea, came out of desperation)
Somewhere easily take a list of what's filtered and inverted it by easy function (that was my initial thought but not working!)
Does anybody has an idea how to approach this situation?
I am able to try the VBA on my own so I would be happy if you can point me in the right direction. Of course I would welcome your thoughts in code too.
Here's an idea to toggle a numeric filter. It won't work with all numeric filters, but most of them. For instance, it won't work with Between, because that uses Criteria1 and Criteria2. But you could expand the code to account for that.
Also, it only really works on numeric filters. It will work on some text filters, but only if one criteria is applied.
Sub InvertNumericFilter()
Dim lFilter As Long
Dim lo As ListObject
Dim fltr As Filter
Dim aOper As Variant, aOpp As Variant
Dim i As Long
'aOpp is the opposite of the corresponding
'operator in aOper
aOper = Split("<> <= >= = < >")
aOpp = Split("= > < <> >= <=")
'Find which column you're in
Set lo = ActiveCell.ListObject
lFilter = ActiveCell.Column - lo.DataBodyRange.Column + 1
Set fltr = lo.AutoFilter.Filters(lFilter)
'if the first characters of the criteria are in aOper
'then swap them for aOpp
For i = LBound(aOper) To UBound(aOper)
If Left(fltr.Criteria1, Len(aOper(i))) = aOper(i) Then
lo.DataBodyRange.AutoFilter lFilter, Replace$(fltr.Criteria1, aOper(i), aOpp(i))
Exit For
End If
Next i
End Sub
Your example happened to be inverting a number, but if you want it to be universal (apply to nonnumerics), it would get a lot more complicated.
Update
This will invert value lists, but it makes some assumptions. For one, if you only have two values, it's not a value list, it's an xlOr operator. If you're using xlOr on some other type of field, it might cause problems.
Sub InvertFilter()
Dim lFilter As Long
Dim lo As ListObject
Dim fltr As Filter
Dim aOper As Variant, aOpp As Variant
Dim i As Long, j As Long
Dim dc As Scripting.Dictionary
Dim vaValues As Variant
'Find which column you're in
Set lo = ActiveCell.ListObject
lFilter = ActiveCell.Column - lo.DataBodyRange.Column + 1
Set fltr = lo.AutoFilter.Filters(lFilter)
'lists of values or just two values
If fltr.Operator = xlFilterValues Or fltr.Operator = xlOr Then
'get all the possible values and put in a dictionary
vaValues = lo.ListColumns(lFilter).DataBodyRange.Value
Set dc = New Scripting.Dictionary
For i = LBound(vaValues, 1) To UBound(vaValues, 1)
If Not dc.Exists("=" & vaValues(i, 1)) Then
dc.Add "=" & vaValues(i, 1), "=" & vaValues(i, 1)
End If
Next i
'If it's more than two values
If IsArray(fltr.Criteria1) Then
'remove from dictionary
For i = LBound(fltr.Criteria1) To UBound(fltr.Criteria1)
If dc.Exists(fltr.Criteria1(i)) Then
dc.Remove fltr.Criteria1(i)
End If
Next i
Else
dc.Remove fltr.Criteria1
dc.Remove fltr.Criteria2
End If
'reapply filter
lo.DataBodyRange.AutoFilter lFilter, dc.Keys, xlFilterValues
ElseIf fltr.Operator = 0 Then
'aOpp is the opposite of the corresponding
'operator in aOper
aOper = Split("<> <= >= = < >")
aOpp = Split("= > < <> >= <=")
'if the first characters of the criteria are in aOper
'then swap them for aOpp
For i = LBound(aOper) To UBound(aOper)
If Left(fltr.Criteria1, Len(aOper(i))) = aOper(i) Then
lo.DataBodyRange.AutoFilter lFilter, Replace$(fltr.Criteria1, aOper(i), aOpp(i))
Exit For
End If
Next i
End If
End Sub

Resources