Can you give columns in Excel a variable name? - vba

I have a fairly large Excel spreadsheet, there are around 4500 rows with 35 columns.
Our VBA code references columns by their letter (aka A1, A2, A3 ... A4500) when modifying field values ... this is a limitation as we are unable to move columns around within our sheet without having to update all of our code to where that column has moved to.
Is it possible to give a column a variable name?
If column A currently holds First Name, is it possible to name this column First_Name so that regardless of where this column moves, it retains the First_Name reference?
Then in our VBA code we can say First Name instead of A1?
Thanks!

The easiest way is to dimension (35) variables or Enums that correspond to the either columns or column numbers.
Option Explicit
Public Const Name As String = "A"
Public Const Age As String = "C"
Public Const ID As String = "F"
...
then when referring to the columns use the defined names ie. Range(Name & 13).
this way you only ever need to modify the public constants when the order of column changes.
if you reference columns by numbers consider using Enum
Public Enum Col
Name = 1
ID = 2
Age = 3
End Enum
Sub Main()
Cells(1, Col.ID) = "ID HEADER"
End Sub

Here's a version that dynamically finds the column:
Sub test()
Do While True ' Ctrl-Break to stop
MsgBox ColByName(InputBox("Name:"))
Loop
End Sub
Function ColByName(colName As String) As Long
Dim iCol As Long
For iCol = 1 To UsedRange.Columns.Count ' need better upper range for code off of a sheet
If Cells(1, iCol) = colName Then
ColByName = iCol
Exit Function
End If
Next iCol
ColByName = -1
End Function
I prefer the ENum route but sometimes it is just not practical and it can introduce maintenance issues.
An alternate approach is a collection indexed by name - rebuild the collection when the columns are moved etc. Note that the test shows the default compare is case sensitive.

A technique I use frequently is to leverage an Excel table. Using this command will create a special object within your worksheet that you can then reference within your VBA code. (You can, of course, add a new Excel table programatically using VBA if you like.)
For example, if you have a data in your worksheet like:
+--------------+-------------+
| First_Name | Last_Name |
+--------------+-------------+
| John | Smith |
| Sally | Jones |
+--------------+-------------+
Once you convert it into an Excel table, you will be able to reference it using code like:
Option Explicit
Public Sub SampleCode()
'
' selects Last_Name column
'
Range("Table1[Last_Name]").Select
'
' prints "Jones"
'
Debug.Print Cells(3, Range("Table1[Last_Name]").Column).Value
End Sub
Excel will create a sequential table name for your starting with Table1. You can change this name within Excel or give it a unique name if you create it programmatically.

Related

Best way to populate an excel string column for fastest subsequent vba search (can I use metadata, etc?)

In a column with hundreds or even 1-2 thousand strings of approximately 40 characters, with one string per cell and many repeating entries, what is the best way to populate the column to conduct the fastest possible search later? The search should return a row number so that the corresponding row can be deleted.
Is there some way to append metadata or label to a cell/row for faster search? Is there some other mechanism that can identify cells that will make searching easier?
I'm new to VBA, and I want to set out on the best path before I get too far into the project and have to search through thousands of strings.
edit: Someone requested an example cell: The cells will have email addresses in them. I can control the email addresses on the server, so they will roughly be 40 characters long each. They will contain alphanumeric characters only.
Example of a fast way to implement a dictionary lookup
Data is on Sheet1, and starts in column A
The strings are in column B
Option Explicit
Public Sub SearchStrings()
Dim ur As Variant, r As Long, d As Object
Const COL_ID = 2
Set d = CreateObject("Scripting.Dictionary") 'or Reference to Microsof Scripting Runtime
d.CompareMode = TextCompare 'Case insensitive, or "BinaryCompare" otherwise
ur = Sheet1.UsedRange.Columns(COL_ID) 'read strings from column COL_ID into array
For r = LBound(ur) To UBound(ur) 'populate dictionary; Key = string (unique)
If Not IsError(ur(r, 1)) Then d(CStr(ur(r, 1))) = r 'Item = row id
Next
Debug.Print d.Keys()(3) 'prints the string in row 3
Debug.Print d.Items()(3) 'prints the row number of the 3rd string
End Sub
If you want to store string duplicates use this:
If Not IsError(ur(r, 1)) Then d(COL_ID & "-" & r) = CStr(ur(r, 1))
which is Key = Column ID & "-" & row ID (2-5), and Item = String itself

Excel VBA find in Table

Im having a list of event participants in an Excel Sheet (Col A: Lastname; Col B: Firstname) and a Membership Table with the same plus info colums like birthdate and sex.
No I want to loop through the event list and do some actions on the birthday/sex of the participants. I can express that in MySQL
SELECT birthdate, sex FROM members WHERE lastname = LASTNAME AND firstname = FIRSTNAME
Where LASTNAME & FIRSTNAME are pulled from the participants table. I can figure out how to create a Loop through the event table but I got trouble on how to pull the data from the Membership Table.
Im just not used to Excel VBA so any help to start me off would be greatly appreciated
So far I got following Loop:
Dim participantCount As Integer
Dim sh As Worksheet
Dim rw As Range
Set sh = Sheets(INP_tblakt.Value)
For Each rw In sh.Rows
If sh.Cells(rw.Row, 1).Value = "" And sh.Cells(rw.Row, 2).Value = "" Then
Exit For
End If
participantCount = participantCount + 1
Next rw
EDIT: To Clearify
I got the loop above in wich I want to insert a "function" wich looks up in another sheet the row where A? = sh.Cells(rw.Row,1) and B? = sh.Cells(rw.Row,2) So that I then can get the value from D? and E? to use it for further calculation.
The VBA function Find does only support the matching of one Colum. I now found MATCH and IDEX but couldnt succesfully implement them.
(Hopefully this does help to understand the question, Thanks in advance for help)
Assuming the members table is an Excel Table, you can combine MATCH and OFFSET to get what you need. There may be a faster way but this is what I got.
Consider below table (on same sheet for screenshot):
C3 is the cell with formula that finds the DOB for matching FirstName A3 and LastName B3 from the members table.
The formula is:
=IF(MATCH(A3,members[FirstName],0)=MATCH(B3,members[LastName],0),
OFFSET(members[[#Headers],[DOB]],MATCH(A3,members[FirstName],0),0),
"No Match")
Since we need to exact match 2 fields (FirstName and LastName), we need the Matches to be on the same position. Hence the condition is:MATCH(A3,members[FirstName],0)=MATCH(B3,members[LastName],0)
Once an exact match is found, we can use OFFSET to locate the n'th row from the field we want to extract:OFFSET(members[[#Headers],[DOB]],MATCH(A3,members[FirstName],0),0) It means get the value from matched row of table members with header "DOB". Change the text DOB to the desired header name of your lookup table.
Otherwise "No Match" is returned.

Copying Row Info from one sheet to another based on match

I have an excel book that has two sheets: 1) Import 2) Pricing Rules.
Pricing Rules Sheet
The A column is what I need to match on. Example values include STA_PNP4, STA_PST.. and others. There are potentially around 50 different rows in the sheet, and it will continue to grow over time. Then for each row, there are pricing values in columns B to CF.
Import Sheet
This sheet has the same number of columns, but only Column A is filled out. Example values include STA_PNP4_001_00, STA_PNP4_007_00, STA_PST_010_00.. and many more.
What I need to do:
If the text in Import Sheet Column A before the second "_" matches the column identifer in Pricing Rules Sheet Column A, copy the rest of B to CF of Pricing Rules sheet for that row into the Import sheet for the row it matched on.
Any idea on where to begin with this one?
Why don't you do it using formulas only?
Assuming :
1.) Data in Import Sheet is
(col A)
STA_PNP4_007_00
STA_PNP4_001_00
STA_PNP4_001_00
.
.
2.) Data in Pricing Rules Sheet
(Col A) (col B) (ColC) (Col D) .......
STA_PNP4 1 2 3 .....
STA_PST 4 5 6 .....
STA_ASA2 7 8 9 .....
Then write this formula in B1 cell of Import Sheet
=IFERROR(VLOOKUP(LEFT(A1,FIND("",A1,FIND("",A1)+1)-1),PricingRules!$A$1:$CF$100,2,0),"")
Drag it down in column B
and For Column C , D just change index num from 2 to (3 for C) , (4 for D) and like that.
Because it will continue to grow over time you may be best using VBA. However, even with code I would start by applying the ‘groups’ via formula, so as not to have a spreadsheet overburdened with formulae and hence potentially slow and easy to corrupt. Something like part of #xtremeExcel’s solution which I repeat because the underscores have been treated as formatting commands in that answer:
=LEFT(A1,FIND("_",A1,1+FIND("_",A1))-1)
I’d envisage this (copied down) as an additional column in your Import Sheet - to serve as a key field to link to your Pricing Rules Sheet. Say on the extreme left so available for use by VLOOKUP across the entire sheet.
With that as a key field then either:
Write the code to populate Pricing Rules Sheet as frequently as run/desired. Either populating ‘from scratch’ each time (perhaps best for low volumes) or incrementally (likely advisable for high volumes).
Use VLOOKUP (as suggested). However with at least 84 columns and, presumably, many more than 50 rows that is a lot of formulae, though may be viable as a temporary ‘once off’ solution (ie after population Copy/Paste Special/Values).
A compromise. As 2. But preserve a row or a cell with the appropriate formulae/a and copy that to populate the other columns for your additions to your ColumnA and/or ColumnA:B.
Thanks for the input guys.
I got it implemented via a method like this:
{=VLOOKUP(LEFT($A4,7),PricingRules!A3:CF112,{2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84},FALSE)}
That is my ugly function, applied across a whole row, to look up and copy from my pricing rules every column when it finds a match.
Below is the function that I have created for above scenario. Its working as per the requirement that you have mentioned.
Sub CopyData()
Dim wb As Workbook
Dim importws As Worksheet
Dim PricingRulesws As Worksheet
Dim Pricingrowcount As Integer
Dim importRowCount As Integer
Dim FindValue As String
Dim textvalue As String
Dim columncount As Integer
Dim stringarray() As String
'Enter full address of your file ex: "C:\newfolder\datafile.xlsx"
Set wb = Workbooks.Open("C:\newfolder\datafile.xlsx")
'Enter the name of your "import" sheet
Set importws = Sheets("Import")
'Enter the name of your "Pricing" sheet
Set PricingRulesws = Sheets("PricingRules")
For Pricingrowcount = 1 To PricingRulesws.UsedRange.Rows.Count
FindValue = PricingRulesws.Cells(Pricingrowcount, 1)
For importRowCount = 1 To importws.UsedRange.Rows.Count
textvalue = importws.Cells(importRowCount, 1)
stringarray = Split(textvalue, "_")
textvalue = stringarray(0) & "_" & stringarray(1)
If FindValue = textvalue Then
For columncount = 2 To PricingRulesws.UsedRange.Columns.Count
importws.Cells(importRowCount, columncount) = PricingRulesws.Cells(Pricingrowcount, columncount)
Next columncount
End If
Next importRowCount
Next Pricingrowcount
End Sub

compare huge text files using vba

I gotta serious problem here.. any kind of help is much appreciated!!
I have two huge text files (130 MB)each with thousands of records in each. I need to compare the two files using vba or by any means and generate a spreadsheet which includes the header and with two additional columns. The two additional columns will be the file name and in the next column it should display in which particular column is error. Each record will be having multiple discrepancies. One file can have the records which cannot be found in the other file. So this condition should also be recorded in the spreadsheet.
Example:
Media Events: Taking one record from each.
00000018063|112295|000|**0009**|
PROL:
00000018063|112295|000|**0013**|
In the above example, the records are from two files. The highlighted ones are the differences between the records. So the output should be like this..
HH_NUMBER | CLASS_DATE | MV_MIN DURATION File Mismatc Mismatch Reason
00000018063 | 112295 | 000 **0009** Media Events Mismatches in DURATION
00000018063 | 112295 | 000 **0013** PROL Mismatches in DURATION
00000011861 | 112295 | 002 0126 Media Events missing in PROL file
It seems there are three problems here:
1) Find matching records (first column) between two files.
2) Compare records that match on the first column - if there is a difference, record what the difference is
3) If a record exists in one file but not the other, record that.
I am going to assume that the two "huge files" are in fact separate sheets in the same excel workbook, and that the records are sorted on the first key. This will speed up processing significantly. But speed is a secondary concern, I assume. I also assume there is a third sheet where you put the output.
Here is an outline of VBA code - you will have to do a bit of work to get it "just right" for your application, but I hope this gets you going.
Sub compare()
Dim s1 as Worksheet
Dim s2 as Worksheet
Dim col1 as Range
Dim col2 as Range
Dim c as Range
Dim record1 As Range, record2 As Range, output As Range
Dim m
Dim numCols as Integer
numCols = 5 ' however many columns you want to compare over
Set s1 = Sheets("Media")
Set s2 = Sheets("Pro")
Set output = Sheets("output").Range("A2")
Application.ScreenUpdating = False
s1.Select
Set col1 = Range("A2", [A2].End(xlDown));
s2.Select
Set col2 = Range("A2", [A2].End(xlDown));
On Error Resume Next
For Each c in col1.Cells
m = Application.Match(c.Value, col2, 0);
If isError(m) Then
' you found a record in 1 but not 2
' record this in your output sheet
output.Value = "Record " & c.Value & " does not exist in Pro"
Set output = output.Offset(1,0) ' next time you write output it will be in the next line
' you will have to do the same thing in the other direction - test all values
' in 2 against 1 to see if any records exist in 2 that don't exist in 1
Else
' you found matching records
Set record1 = Range(c, c.offset(0, numCols))
Set record2 = Range(col2.Cells(m,1), col2.Cells(m,numCols))
' now you call another function to compare these records and record the result
' using the same trick as above to "go to the next line" - using output.Offset(1,0)
End If
Next c
End Sub
You could do this with formulas:
See
MS KB: Use Excel to compare two lists of data
Me Excel.com - Creating a list of non-matching values
ExcelExperts.com - Extracting non-matching entries from two columns in a third column
To give you an idea, basically, if you have two lists in columns A & B, you could use formulas like below in columns C and D to show the matching or non-matching:
In C1,
=If(isna(match(A1,B:B,0)),A1,"")
and, in D1
=IF(Isna(Match(B1,A:A,0)),B1,"")
both copied down.
FURTHER READING:
Excel Index Function and Match Function - Contextures MVP
Excel VLOOKUP and Index & Match - Excel User MVP
Excel User MVP - Excel’s Best Lookup Method: INDEX-MATCH

Search through column in excel for specific strings where the string is random in each cell

I am working in excel with a datasheet that is 1000 rows and 15 columns. Currently, in one of the columns, I have a lot of data mixed in with people names (see below for an example). I want to see how many times each person's name appears in the datasheet, so I can use it in a pivot table. There is no particular format or order to the way names appear. It is random. Is there a way to code in excel to search through that whole column and give me a count of the amount of times each person's name appears?
Column D
21421Adam14234
2323xxx Bob 66
23 asjdxx Jacob 665
43 Tim 5935539
2394Bob 88
After some trial and error, I can generate a list of names, one per row and place them in a different column for comparison sake, if that makes it easier.
I know you have got your answer but why not use COUNTIF with Wild Cards? You don't need VBA for this :)
See this example
=COUNTIF($A$1:$A$5,"*"&C1&"*")
SNAPSHOT
You don't have VBA tagged, but I don't know if there is a way to do this without it. I've built a custom function below. To implement it, take the following steps.
1) List desired names starting at column E1.
2) Insert this function into VBA Editor
A) Presss Alt + F11
B) Click Insert > Module from menu bar
C) Copy this code into Module
Option Explicit
Function findString(rngString As Range, rngSearch As Range) As Long
Dim cel As Range
Dim i As Integer
i = 0
For Each cel In rngSearch
If InStr(1, cel.Text, rngString.Value) > 0 Then
cel.offset(,-1) = rngString.Value 'places the name in cell to right of search range
i = i + 1
End If
Next
findString = i
End Function
3) In F1 type the following formula
=findstring(E1,$D$1:$D$5)
4) Run the formula down column F to get the count of each desired name.