Creating Excel file with info from database - excel

I have a query that returns something like this:
1 2 3 4 5 6 7
A. B. C. D. E. F. G.
Etc...
Etc...
N rows
I store the query on a dataset. Then I create the Excel file using something like this:
Sql=“select * from table”
Dim cmd As New SqlDataAdapter(Sql, con)
Dim ds As New DataSet
cmd.Fill(ds)
For i=0 To Tables(0).Rows.Count - 1
For x=0 To ds.Tables(0).Columns.Count - 1
ExcelFile.Cells(i+1;x+1)=ds.Tables(0).Rows(i).Item(j)
Next
Next
The code works fine except that I need to write also columns headers name (1,2,3,4,etc.) My first que question is how can I add the headers?
And the main problem... the query sometimes is going to return more than 80k results, so following the for loop logic my code is going to run 80k times for every column (in this case 7 times) which is going to give me a slow result.
There is another fast way to fill and Excel file? Or this is the best way to do it?

You have access to the ColumnName property of each Column in your DataTable. For example, to just add the headers with as little modification of your code as possible, you could just do this:
'Write ColumnName to the corresponding cell in row 1
For x=0 To ds.Tables(0).Columns.Count - 1
ExcelFile.Cells(1, x+1) = ds.Tables(0).Columns(x).ColumnName
Next
'Modded to start at the second row and fix index variable
For i=1 To Tables(0).Rows.Count - 1
For x=0 To ds.Tables(0).Columns.Count - 1
ExcelFile.Cells(i+1, x+1) = ds.Tables(0).Rows(i).Item(x)
Next
Next
You are right to be concerned about the performance of this, though. The number one rule of Excel automation is to actually interact with Excel as little as possible, because each interaction is very expensive.
Assuming you are using regular Office Interop, you should build a 2-dimensional array representing the values from your query. You then find an equivalent size range in your worksheet, and set the value of that range to the array. This way you've cut many thousands of interactions to just one.
Dim rowCount = ds.Tables(0).Rows.Count
Dim colCount = ds.Tables(0).Columns.Count
Dim ws = ExcelFile
Dim valueSet(,) As Object
ReDim valueSet(rowCount - 1, colCount - 1)
For row = 0 To rowCount - 1
For col = 0 To colCount - 1
valueSet(row, col) = ds.Tables(0).Rows(row).Item(col)
Next
Next
'Set the entire set of values in a single operation
ws.Range(ws.Cells(1, 0), ws.Cells(rowCount, colCount).Value = valueSet
Also, if you actually are using Excel Interop or a wrapper around it like NetOffice, you should look into EPPlus and see if it does what you need. It's a helper library that works with OfficeOpenXML and doesn't even require Excel to be installed.

I use the following:
Dim sSql As String
Dim tbl As ListObject
'Declare a Connection object
Dim cnDB As New ADODB.Connection
'Declare a Recordset Object
Dim rs As ADODB.Recordset
' Housekeeping, set the connection strings
Set cnn = New ADODB.Connection
cnn.Provider = "MSDASQL"
cnn.CommandTimeout = 100
Set tbl = ActiveSheet.ListObjects("Lookup")
With tbl.DataBodyRange
If .Rows.Count > 1 Then
.Offset(1, 0).Resize(.Rows.Count - 1, .Columns.Count).Rows.Delete
End If
End With
' Connect to the database and paste new data
cnn.ConnectionString = "driver={};server={};uid={};pwd={};database={}"
sSql = "SELECT BLAH BLAH "
cnn.Open
Set rs = cnn.Execute(sSql)
ThisWorkbook.Worksheets("Lookup").Range("A2").CopyFromRecordset rs
cnn.Close

Related

How can I loop through Access Field Names by index?

I am trying to build an Access db of inspection data for mass manufactured plastic parts from a bunch of Excel sheets, which come from different sources.
I am doing this with two Access files: one as the front end/UI (Forms and Macros), and one as the back end (Tables containing the desired data).
My process:
Use front end Access Form to open the msoFileDialogFilePicker and select an Excel sheet of data to get read into my back end Access Table. The Table where the data is stored in the back end file depends on radio buttons selections in the front end file Form.
Macro reads data from Excel sheet, transposes data, and stores it in a new dumpSheet at the end of the original data file. Each row in this dumpSheet is effectively its own record in the corresponding back end Access table. Data gets read into back end Access Tables using DAO.Database and DAO.Recordset.
Repeat with new inspection data as it comes in every two weeks.
The inspection data comes from different sources, but it can be for the same plastic part. This combination of company + part dictates which fields are of interest to be read into the recordset. For example, all of these combinations would have different fields (each combination has its own table), but there is new inspection data for each combination every two weeks (hence the need for a db):
Company X + Part A
Company X + Part B
Company Y + Part A
Company Y + Part B
Here is a general look at how I'm setting this up in the Access front end Form Macro (where the fields listed in the Do While Loop are for one specific Company + Part combination:
Set excelTemp = New Excel.Application
excelTemp.Visible = True
excelTemp.DisplayAlerts = True
Set trExcelWB = excelTemp.Workbooks.Open("myExcelData.xlsx") 'Picked from msoDialogFilePicker
Dim ws As Worksheet
Set ws = trExcelWB.Sheets(Sheets.Count)
Dim dbDump As DAO.Database
Set dbDump = DBEngine.Workspaces(0).OpenDatabase("backend-database.accdb")
Dim dumpTable as String
dumpTable = "myTableForDataInBackEndDB"
Dim tdf As DAO.TableDef
Set tdf = dbDump.TableDefs(dumpTable)
Dim rst As DAO.Recordset
Set rst = dbDump.OpenRecordset(Name:=dumpTable)
With rst
.AddNew
Dim n as Integer
n = 2
Do While Len(ws.Cells(n,5).Value) > 0 '5th column of dumpSheet stores unique part IDs and governs end of my dataset on the dumpSheet
.AddNew
![Date of Inspection] = ws.Cells(n, 2).Value
![Date of Production] = ws.Cells(n, 3).Value
![LOT] = ws.Cells(n, 4).Value
![Serial] = ws.Cells(n, 5).Value
![Part] = ws.Cells(n, 6).Value
![Depth] = ws.Cells(n, 7).Value
![Wall thickness] = ws.Cells(n, 8).Value
.Update
n = n + 1
Loop
Which works in the case where I have:
Date of Inspection
Date of Production
LOT
Serial
Part
Depth
Wall Thickness
As my field names. These are my headers in only one case of many.
I would like something like this in my Do While Loop:
Do While Len(ws.Cells(n,5).Value) > 0
Dim col as Integer
For col = 2 To ws.UsedRange.Columns.Count
.AddNew
![dbDump.TableDefs(dumpTable).Fields(col - 1).Name] = ws.Cells(n, col).Value
Next col
.Update
n = n + 1
Loop
I get an Error
"Item not found in Collection".
I'm confused because dbDump.TableDefs(dumpTable).Fields(col - 1).Name on its own gives me the name of the field I want as a string, but I cannot figure out how to call it in the dbDump from this code.
I've looked and can't figure out how to loop through field names via index and not name.

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.

How to copy specific columns in specific order from one sheet to another?

I need to copy date and time, code and names from a big data sheet, which contains multiple columns. Row counts may differ.
The sequence of actions should be:
Copy the consecutive Range from A3 which is the first active cell through to the data at column AZ - This is a manual selection.
Using the VBA linked Command button start the process of copying data in next sheet:
for example
sheet1.column B = sheet2.column A
sheet2.column B= ""
'empty and data copy is not needed, please just generate the empty row
sheet1.column Y = sheet2.column C
After the copying process is over, clear all data from sheet1
My core problem is the data count for above rows differs every time. I can't find a correct sequence of commands to get these columns in the order I need from sheet1. To add to that, the formatting breaks and the time values are 'stringified', so it can't be reused.
The generated data needs to be exported to another workbook and the copying process is critically important as I do it repeatedly. Locating and copying each column manually every time.
The solution to your problem is of the form f(x) = y where x is the column no. of the source sheet and y is the column number of that very same column on the destination sheet.
f(x) is a simple mapping between a Source column and transformed into a destination column no.
As you still need to define the problem better by including sample data, I'll simply brief you on The 3 steps to resolve your problem.
I hope you know your VBA well enough to encode the steps into the specific VBA code you need to solve this permanently.
Create a sheet as a "ControlPanel" that maps the columns you need.
Assuming your sheets are named appropriately as per the code below.
Kindly do apply your VBA skills and discretion to customize the code below as per your needs.
Public Sub Add_Msng_And_Check_Prev_EmpData()
'' Objective : Check missing employees from the incoming data placed in the Destination_Sheet sheet which is the client's format.
'' The _Source_Sheet sheet is our destination where processed data sits,
'' columns from Destination_Sheet are mapped to specific columns of the _Source_Sheet sheet.
'' Copy the missing emp codes to these mapped columns.
'' Support : myfullnamewithoutspaces#gmail.com
'' Status : Production 14-Dec-2016 10.32 PM
'' Version : 1.0
'' To Do : 10 is the column number on Source_Sheet where the emp code resides
'' Convert this magic number to a generic solution
Dim Src_Sheet, Destination_Sheet As Worksheet
Dim Dest_Sheet_Column_Mapping, Src_Sheet_Column_Location As Range
Set Src_Sheet = Sheets("Source_Sheet")
Set Destination_Sheet = Sheets("Destination_Sheet")
Set Dest_Sheet_Column_Mapping = Sheets("ControlPanel").Range("A2:A60")
Set Src_Sheet_Column_Location = Sheets("ControlPanel").Range("D2:D60")
Dim myMap() As Integer
Dim myRow As Variant
ReDim myMap(Dest_Sheet_Column_Mapping.Count + 1)
'' Map the source_columns to the destination_columns
For Each myRow In Src_Sheet_Column_Location
'' Index corresponds to Source_Sheet column
'' Value at Index to Destination_Sheet
'' for eg: Destination_Sheet.column = myMap(Src_Sheet.column)
myMap(myRow) = Dest_Sheet_Column_Mapping.Cells(myRow, 1).Value
Next myRow
Dim Primary_Key_Not_Null As Collection
Set Primary_Key_Not_Null = New Collection
Dim Master, Src_Sheet_Range, Src_Range As Range
Dim MissingEmployeeCode, LookupValue, tempVar, LookupResult As Variant
Dim LastRow, i, Src_Range_Rows_Count, Src_Sheet_Range_Rows_Count As Integer
'' This is the source of all new entries we need to search for.
Set Src_Sheet_Range = Destination_Sheet.Range(Destination_Sheet.Cells(1, myMap(10)), Destination_Sheet.Cells(Destination_Sheet.Cells(1048576, myMap(10)).End(xlUp).Row, myMap(10)))
Src_Sheet_Range_Rows_Count = Src_Sheet_Range.Rows.Count
'' This is the database of all previous existing entries we need to search against.
Set Src_Range = Src_Sheet.Range(Src_Sheet.Cells(1, 10), Src_Sheet.Cells(Src_Sheet.Cells(1048576, 10).End(xlUp).Row, 10))
Src_Range_Rows_Count = Src_Range.Rows.Count
For i = 3 To Src_Sheet_Range_Rows_Count
'' Skip the blank rows and header at rows 0 to 2
On Error Resume Next
LookupValue = Destination_Sheet.Cells(i, myMap(10)).Value
LookupResult = Application.Match(LookupValue, Src_Range, 0)
If (IsError(LookupResult)) Then
'' To Do : Check for Duplicates within the previously added values
'' LookupValue becomes your missing empcode and i is the row number it's located at
'' The row number i becomes critical when you want to copy the same record that you have just found missing.
Primary_Key_Not_Null.Add i '' LookupValue
'' LookupValue is the actual missing empcode, however we need the row number for the copy operation later
End If
Next i
LastRow = Src_Sheet.Cells(1048576, 10).End(xlUp).Offset(1, 0).Row
Dim FirstRow, LastColumn, j, Src_Range_Columns_Count As Integer
FirstRow = LastRow
''--Phase 3--------------------------------------------------------------------------------------------------
'' Objective : Get and paste data for each missing empcode
With Src_Range
LastColumn = .Cells(1, 1).End(xlToRight).Column
LastRow = Primary_Key_Not_Null.Count + FirstRow
Set Src_Range = .Range(.Cells(1, 1), .Cells(LastRow, LastColumn))
Src_Range_Columns_Count = Src_Range.Columns.Count
For i = FirstRow To LastRow ''FirstRow + 3 '' Commented for Debugging
For j = 1 To Src_Range_Columns_Count '' 59
'' The simple logic is Row Numbers and Column numbers obtained from all the above operations
'' define the cells in the Src_Sheet sheet that we need this data pasted ito.
'' For details please see the code below.
Src_Sheet.Cells(i, j).Value = Destination_Sheet.Cells(Primary_Key_Not_Null(i - FirstRow + 1), myMap(j)).Value
Next j
Next i
End With
''--Phase 4--------------------------------------------------------------------------------------------------
'' Objective : For the previous range in Source_Sheet, check each cell in each column against the mapped columns in the Destination_Sheet.
'' When you find a discrepancy: style it Bad, for the matches: style it Good,
'' for the not found : Style it neutral.
LastRow = FirstRow
FirstRow = 2
Set Src_Range = Src_Sheet.Range(Src_Sheet.Cells(2, 1), Src_Sheet.Cells(LastRow, LastColumn))
Src_Range.Style = "Normal"
Dim FoundRow, FoundColumn As Integer
FoundRow = 0
FoundColumn = 10
Dim LookupRange, LookupDatabase As Range
Set LookupRange = Src_Sheet.Range(Src_Sheet.Cells(1, 10), Src_Sheet.Cells(LastRow, 10))
Set LookupDatabase = Destination_Sheet.Range(Destination_Sheet.Cells(1, myMap(10)), Destination_Sheet.Cells(Src_Sheet_Range_Rows_Count, myMap(10)))
Dim FoundRows As Collection
Set FoundRows = New Collection
'' Locate the row of each employee code on Emp Master, push it into a collection and let the emp code be it's key
Dim LookupRange_Row_Count As Integer
LookupRange_Row_Count = LookupRange.Rows.Count
For i = 2 To LookupRange_Row_Count
On Error Resume Next
FoundRow = Application.Match(LookupRange.Cells(i, 1).Value, LookupDatabase, 0)
If (Not IsError(FoundRow)) Then
'' myRow contains EmpCode which is the key, FoundRow = Where I Found it, becomes the value.
FoundRows.Add FoundRow, CStr(LookupRange.Cells(i, 1).Value)
End If
Next i
Dim Src_Sheet_Value, EmpMstrValue, myEmpCodeString As String
For i = FirstRow To LastRow '' 2 to 1029
For j = 1 To Src_Range_Columns_Count '' 59
'' Handle 4 cases.
'' 1. Src_Sheet Cell Value Found and matches = Good
'' 2. Src_Sheet Cell Value Found and does not match = Bad
'' 3. Src_Sheet Cell Value Not Found or not in Scope and hence does not match = Neutral
'' 4. Src_Sheet Cell Value is a duplicate of a value which is already checked earlier. = ??
Src_Sheet_Value = Src_Sheet.Cells(i, j).Value
myEmpCodeString = CStr(LookupRange.Cells(i, 1).Value)
myRow = CInt(FoundRows(myEmpCodeString))
EmpMstrValue = Destination_Sheet.Cells(myRow, myMap(j)).Value
'' Implements 1. Src_Sheet Cell Value Found and matches = Good
If Src_Sheet_Value = EmpMstrValue Then
Src_Sheet.Cells(i, j).Style = "Good"
Else
Src_Sheet.Cells(i, j).Style = "Bad"
End If
Next j
Next i
End Sub
I found myself in the same situation as yourself sometime back. Although the code is conceptually simple, it requires you to thoroughly define your problem in the Source, Destination, Transformation pattern.
Do Feel free to mail me at myfullnamewithoutspaces#gmail.com. I'll assist any way I can.

VBA: Loop to map figures from 2 different workbooks

I have 2 different excel workbooks, one is called DATA and one is called ReportTemplate. So now i need to insert data from DATA to corresponding row/column in ReportTemplate.
How I am doing now is, for example:
'Others ( this is for other currencies)
DATA_FILE.activate
Sheets("SA_FX_2").select
SRC_SA_FX_2_D25 = Range("D25").Value
SRC_SA_FX_2_E25 = Range("E25").Value
SRC_SA_FX_2_F25 = Range("F25").Value
SRC_SA_FX_2_G25 = Range("G25").Value
SRC_SA_FX_2_H25 = Range("H25").Value
REPORTTEMPLATE_FILE.activate
Sheets("SA_FX_2").select
Range("D36").Value = SRC_SA_FX_2_D25
Range("E36").Value = SRC_SA_FX_2_E25
Range("F36").Value = SRC_SA_FX_2_F25
Range("G36").Value = SRC_SA_FX_2_G25
Range("H36").Value = SRC_SA_FX_2_H25
The SRC_SA_FX_2_X25 is to store the values for that particular cells in DATA, then insert it into Range("X36").Value in REPORTTEMPLATE. So currently I am doing the "mapping" in a hard-coded way.
This is how my DATA looks like:
DATA TEMPLATE
And this is how my REPORTTEMPLATE looks like:REPORTTEMPLATE
For the "Others" and above currencies, they are all fixed, so I can just hard code the cells.
However, if there is any new currency, my macro should detect that, then choose the currency in the drop down list, and do the mapping.
How can I actually parameterize my codes above in order to the mapping for the rest of the currencies?
Thank you for your advice.
A simple way would be to MATCH them like:
'we look for rows 3 to 5 of DATA_FILE in REPORTTEMPLATE_FILE
Dim i As Long, OutRow As Variant, wsIn As Worksheet, wsOut As Worksheet
Set wsIn = DATA_FILE.Sheets("SA_FX_2")
Set wsOut = REPORTTEMPLATE_FILE.Sheets("SA_FX_2")
For i = 3 To 5
'get the row of the active item in REPORTTEMPLATE_FILE
OutRow = Application.Match(wsIn.Cells(i, 3).Value2, wsOut.Columns(3), 0)
If Not IsNumeric(OutRow) Then OutRow = Application.Match("-- Please Select Currency --", wsOut.Columns(3), 0) 'not there -> new line
wsOut.Range("C" & OutRow & ":H" & OutRow).Value2 = wsIn.Range("C" & i & ":H" & i).Value2
Next
Just change the For i = 3 To 5 to fit your needs and check, if the "-- Please Select Currency --" does match.
If you have any questions or problems, just ask ;)

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