I've been trying, unsuccessfully, for weeks to create a macro that loops through rows of text and identifies variable text phrases to sort products by there underlying components. That text phrase will be located in different spots across the page or linked with multiple other variables. For example how many products in a list contain "leather" and identify that in an answer row next to the list with a defined phrase like "Absolutely". The code I've been working with looks like this:
Sub Find()
Dim Sheet As Worksheet
Dim rng As Range
Dim row As Range
Dim cell As Range
Set Sheet = ActiveSheet
Set rng = Sheet.Range("E2:J7")
For Each row In rng.Rows
For Each cell In row.Cells
Select Case cell.Value
Case "Oil"
Case "Leather"
Cells(row.row, 11).Value = "Absolutely"
Case "Absolutely"
Case "Nope"
Case Else
Cells(row.row, 11).Value = "Nope"
End Select
Next cell
Next row
End Sub
And the Table would look look this:
Currently the code runs through the correct lines and sees all of the components but fails to identify the answers in the answer row. I'm using this code to hopefully identify many variable components in many product lists and "Leather" and "Oil" are currently just place holders. Hopefully this will help myself and many other people avoid reading long lists of data and marking individually whether or not each item meets a certain defined criteria.
Does this have to be VBA? You could easily do this with the COUNTIF function. Using your provided sample data, in cell D10 use this formula and copy down:
=IF(C10="","",IF(COUNTIF($2:$7,C10)>0,"Absolutely","Nope"))
Related
I have no experience with VBA and would love some help. As the title indicates, I'm looking for a script that fills a certain number of blank cells within column G with the average of all nonblank cells within that same range (e.g. fill all blank cells in G16:G59 with the average of all nonblank cells within G16:G59).
To make things more complicated, I'd need to vary the range somewhat dynamically as I wouldn't be sure as to how many rows I'd need to apply this script to and a work colleague who would be using this script might not have any experience with VBA either... The easiest solution I can think of is to have another cell contain the name of the last row in the range, or something like this: "Fill all blank cells in G16:Gx with the average of all nonblank cells within G16:Gx, where x = the row name listed in cell G12". Within G12 I'd have a text that states the last row to define the range, e.g. cell G12 contains the text "G80", which makes the range within the script to read G16:G80.
I know I'm asking for a lot, so if you can even just provide guidance on the first bit, I'd be very grateful! Thank you in advance for your time.
I think we can all remember what it was like when we first started out with VBA coding – which is why I’m helping you here. Normally, you’d be lucky to get any assistance with your question without providing at least some code & a description where it wasn’t doing what you wanted. Using the Record Macro button is always a good place to start.
Assumptions made here are that the data is on “Sheet1” in your file, and that there is a value in the last cell in Column G in the range you’re interested in. If that isn’t the case, let me know and I’ll show an alternative method to find the last row.
I’ve added descriptions about what (most) code does in each case to help you understand what’s going on. Let me know how you go with it.
Option Explicit '<~~ get in the habit of putting this at the top of your code
Sub FillInBlanks()
Dim ws As Worksheet '<~~ declare all variables
Dim LastRow As Long '<~~ use Long not Integer
Dim cel As Range '<~~ use intuitive variable names
Dim avg As Double '<~~ Double used here if you want decimal places in the average
Set ws = Sheets("Sheet1") '<~~ be explicit with sheet references
'Best practice to determine last used row in column
LastRow = ws.Cells(Rows.Count, "G").End(xlUp).Row '<~~ finds the last used row in column G
'Calculate the average & assign it to the variable "avg"
avg = Application.Average(ws.Range("G16:G" & LastRow))
'Loop through each cell in the defined range - one at a time
For Each cel In ws.Range("G16:G" & LastRow)
If cel = "" Then '<~~ = If the cell is blank, then...
cel = avg '<~~ ...put the average value (avg) in the cell
cel.Font.Color = RGB(51, 102, 0) '<~~ change color to suit
End If
Next cel '<~~ go to the next cell in the range
End Sub
To the best of my efforts (VBA novice) I have been trying to write using stack and other resources a macro which would allow me to scan a given list of strings in excel against another much larger list for possible partial matches (Vlookup seems not to do a trick and Instr is a lot better for this it seems). I have been able to get some code working which leverages a loop to scan through a specific range and look for a single string of interest. For example let's say I have 20 cells in Column C with various names of fruits and vegetables and I would like to highlight only specifc ones of interest for me. In the code's case below I have included a string "Cher" (for Cherry) to be searched for against the range which if found creates a hit to the right of the column searched.
My major question would be how to adjust the code so that I could not only search for a single string like "Cherry", but how to run it against a very long list e.g. of 200 items when dealing with a couple of thousands of rows of data?
Sub listchecker()
Dim cell As Range
For Each cell In Range("C2:C20")
If InStr(cell.Value, "Cher") > 0 Then
cell.Offset(0, 1).Value = cell.Value
End If
Next cell
End Sub
Thank you in advance for anyone's help!
Thank you NautMeg and SRJ for the suggestions below. I did adjust the code and it seems to be working now against an array as noted below (hooray)
Dim wordsArray() As Variant
wordsArray = Worksheets("Keywords").Range("B2:B439").Value
Dim word As Variant
Dim cell As Range
For Each cell In Worksheets("Normalized").Range("J2:J49010")
For Each word In wordsArray
If InStr(cell.Value, word) > 0 Then
cell.Offset(0, -1).Value = cell.Value
End If
Next word
Next cell
The only thing I realized now though is that it would be better for 'cell.Offset(0, -1).Value = cell.Value' actually to populate with the array match (the keyword) rather than the cell's value. This is because the keyword might be too broad or too generic and result in false positives. Returning the cells value instead of the match from the array makes life more difficult to troubleshoot/identify a bad broad/generic keyword e.g., ("is").
as SJR mentions above you could store the 200 search items in an array and then amend your code to something like the following:
Dim cell As Range
For Each cell In Range("C2:C20")
'// Add Another For Loop here to loop through your words array
For Each wrd in wordsArray
If InStr(cell.Value, wrd) > 0 Then
cell.Offset(0, 1).Value = cell.Value
End If
Next word
Next cell
End Sub
Possible issue with this kind of search is that it will pick up the first match, for example a search for "mang" could turn up "mango" or "mangetout" depending on which item comes up in the list first. This may or may not be desirable but something to look out for.
I would like to find a way how to cycle through merged cells, e.g. using a For...Next loop.
I could only manage to make it work like this:
Cells(1,1).Select
For i=1 to 6
Selection.Value = arrData(i)
Selection.Offset(0,1).Select
Next i
I hate using .Select - but if I use .Offset(0,i) it won't move from merged cell to merged cell, but just the number of columns from the original cell.
For more detail - I am copying values from a csv-like format into a nicer formatted output sheet, that is then supposed to be exported with bunch of merged cells.
There are multiple sections to the sheet but within each section there is a known amount of cells per row.
My only working solution without .Select is to use .Cells
Example:
For row=0 to 12
with rng.Offset(1,0)
.cells(row+1,1)=arrdata(1+(row*6))
.cells(row+1,3)=arrdata(2+(row*6))
.cells(row+1,7)=arrdata(3+(row*6))
.cells(row+1,9)=arrdata(4+(row*6))
.cells(row+1,14)=arrdata(1+(row*6))
.cells(row+1,16)=arrdata(1+(row*6))
End with
Next row
but this is pretty ardous.
EDIT: Here is a screenshot:
target area
The idea is that the amount of rows is completely flexible, depending on the transaction. So sometimes there is only one row, but can be anything really.
My code generates this section using relative references based on named ranges.
And then from the ugly sheet (where all information is stored in a single row) the values are fed into a one-dimensional array, then the array should be fed into the nice looking sheet.
If the sheet had no merged cells, the formula would look quite simple:
Dim i as integer, j as integer
Dim ws as worksheet: set ws = Worksheets("Printable")
'data array has already been filled with info in a linear order beforehand
k=1
For i=1 to item_qt 'number of cost items lines
For j=1 to item_col 'number of detail columns (in this section)
ws.Range("item_title").Offset(1,0).Cells(i,j).Value=data(k)
k=k+1
Next j
Next i
But because of the nature of this sheet - supposed to be printable and nicer on the eyes - I can't do that and have to find a way how to switch between the merged cells.
Hope this Edit cleared some things up.
I am also looking into the suggestions now to see if I can apply those somehow, but if anybody knows of something better, I am open for everything.
If you're stepping through merged columns, you could use something like
For i = startColumn To endColumn
If Cells(row,StartColumn).MergeArea.Columns.Count > 1 Then
'Do Stuff
i = i + Cells(row,StartColumn).MergeArea.Columns.Count - 1
End If
Debug.Print i
Next i
This will test for merged columns and then jump to the next column after the merge.
EDIT:
Seeing your data structure added in your edit, you could incorporate the MergeArea.Columns.Count method into your For j-Next j loop like
k=1
For i=1 to item_qt 'number of cost items lines
For j=1 to item_col 'number of detail columns (in this section) <-this will need to
'be the total number of columns, not just the number of
'detail fields
ws.Range("item_title").Offset(1,0).Cells(i,j).Value=data(k)
j = j + ws.Range("item_title").Offset(1,0).Cells(i,j).MergeArea.Columns.Count - 1
k=k+1
Next j
Next i
By searching for "excel find merged cells vba" Google comes up with:
How To Identify And Select All Merged Cells In Excel?
https://www.extendoffice.com/documents/excel/962-excel-select-merged-cells.html
Sub FindMergedcells()
'updateby Extendoffice 20160106
Dim x As Range
For Each x In ActiveSheet.UsedRange
If x.MergeCells Then
x.Interior.ColorIndex = 8
End If
Next
End Sub
and
2 Practical Methods to Find Merged Cells in Your Excel
https://www.datanumen.com/blogs/2-practical-methods-find-merged-cells-excel/
Sub FindMerge()
Dim cel As Range
For Each cel In ActiveSheet.Range(“A1:G13”)
If cel.MergeCells = True Then
‘change the color to make it different
cel.Interior.Color = vbYellow
End If
Next cel
End Sub
I want to write a formula like =SUM(tab2!A:A) but instead use the column title of A which is say "count". How can I modify this to look more like: =SUM(tab2!"count")?
The reason I want to do this is because I copy and paste a spreadsheet from another source in tab2 and the column referring to "count" may be in a different column. I want the formula to give me the correct calculation as soon as I paste the new spreadsheet by automatically finding the column to sum up by title name.
I must be missing something because this seems like a very basic question and I can't find the answer anywhere...
Thanks for your help!
I like the idea of naming ranges proposed by #Doug, but if the issue is that you are dumping your data in [and you don't know in advance which column is going to be where] and would need to rename your range every time, there are other options - I suggest using OFFSET. OFFSET allows you to dynamically create a range, starting at a particular point and moving down/up / right/left for as many rows and columns as you determine.
In your case, you will need to combine that with a method for searching the columns to see which one says "Count". I am assuming that your column headings are always in row 1. If they aren't [or if they aren't always in row 2, or any row you know in advance]... you can get around that but then I'd recommend you try to make your data more uniform instead of creating unnecessary Excel workarounds.
In total your formula would look like this:
=SUM(OFFSET(A1,,MATCH("Count",1:1,0)-1,ROWS(A:A),1))
What this does is first determine which column the word "Count" is found in, in row 1. Then it subtracts 1 from that number - this now equals the number of columns to the right that it is, from column A. It uses offset to create a reference to that column, for all rows, and sums those rows together.
Check out the name manager for naming ranges :)
You didn't say whether you would consider a macro solution. If so, this may work.
If the sheet you are pasting into is Sheet2 and the sheet you want the result in is Sheet1, then this macro, if placed in the Worksheet_Activate event of Sheet1 will give you the result as soon as you click on the Sheet1 tab afetr pasting your data into Sheet2:
Private Sub Worksheet_Activate()
Dim r As Range
Dim rCol As Range
Dim rFound As Range
Dim ws As Worksheet
Dim lTotal As Long
Set ws = Sheet2
Set r = ws.Cells
Set rFound = r.Find("count")
If Not rFound Is Nothing Then
Set rCol = rFound.EntireColumn
lTotal = Application.WorksheetFunction.Sum(rCol)
End If
Cells(1, 1) = lTotal
End Sub
It does assume there is only one cell with the word "count" in it on Sheet2.
This question stems off another post I had. (see Search through column in excel for specific strings where the string is random in each cell)
Using the above image as reference, I am trying to search through column B (actually over 1000 lines) using column E as the "lookup values." The end goal would be for "just" the names to be displayed in column C. The trick is all the randomly generated characters the encompass the names. Below is what I would want the datasheet to look like. A formula or module should work, but the vlookup and other lookup function I can't get to work.
For a worksheet function approach, you could enter in C3 and fill down this formula:
=LOOKUP(8^5,SEARCH(E$3:E$7,B3),E$3:E$7)
The constant 8^5=32768 is chosen to be larger than the maximum possible string length so that LOOKUP returns the last matching value. The formula returns #N/A if no string is found.
Another possibility, which may be easier to understand then assylias post initially, but also may be a bit more time consumptive (although with 1,000 rows, I don't think it will matter much) is below.
This requires that you name the range in column E as myNames (or whatever name you wish, just update the code - alternatively, you cuold just write Range("E1:E6")). Also, if you move the random values from column B, update that in the code as well.
Sub findString()
Dim celString As Range, rngString As Range, celSearch As Range, rngSearch As Range
Dim wks As Worksheet
Set wks = Sheets("Sheet1") 'change sheet reference to whatever your sheet name is
Set rngString = wks.Range("myNames")
Set rngSearch = Intersect(wks.UsedRange, wks.Range("B1").EntireColumn)
For Each celString In rngString
For Each celSearch In rngSearch
If InStr(1, celSearch.Text, celString.Value) > 0 Then
celSearch.Offset(, 1) = celString.Value
End If
Next
Next
End Sub
Since, I worked on your original question as well, I would suggest getting the counts through Siddharth's answer and then running this, or assylias's code above to get the names next to the columns. You could put a button the sheet, or just use the Macro dialog box to run the macro.