Using a loop to combine all text down a column - vba

Looking for help regarding writing a loop that will combine all the text values in a column while adding "OR" in between each one. To give some context, I have filenames stored in a column and I want to write a macro that will combine all those filenames separated by "OR" so I can copy and paste the string into a windows search bar and find all those files in a folder.
For example (in Column A)
Apples
Oranges
Bananas
Pears
Blueberries
In B1, the result should be Apples OR Oranges OR Bananas OR Pears OR Blueberries.

While learning to use For and Do loops in VBA is an essential skill, you will find that looping over ranges of cells is slow, and often too slow to be useful. There is often an alternative, and in this case it's the Join function:
Function MergeColumn(rng As Range, Delimiter As String) As Variant
MergeColumn = Join(Application.Transpose(rng.Columns(1).Value), Delimiter)
End Function
How this works:
It's a UDF, so it can be called from other VBA code, or from a worksheet cell
You pass it a Range object. It processes only the left most column of that Range
rng.Columns(1).Value returns the left most column, as a 2D Variant Array, size n x 1 where n is the number of rows in rng. That is, its dimension is 1 to n, 1 to 1
Application.Transpose transposes the array. It has the added feature that when passed a n x 1 array it returns a 1D array length n. That is, its dimension is 1 to n
Join concatenates each member of the array, inserting Delimiter between each element.
Use it like this
In VBA
Sub Demo
Dim r as Range
Dim strResult as String
' Get a reference to the range to be processed, eg
Set r = Range("A1:A10")
' Call the function
strResult = MergeColumn(r, " OR ")
' Print Result to Imedieate window
Debug.Print strResult
End Sub
As a cell formula
=MergeText(A1:A10," OR ")

here is a simple example using For Each Loop
Dim cel as Range, rng as Range
Set rng = Range("A1","A5")
For Each cel in rng
With Range("B1")
If .Value = "" Then
.Value = cel
Else
.Value = .Value & "OR" & cel
End If
End With
Next
Using simple For Loop:
Dim rng as Range, i as integer
Set rng = Range("A1", "A5")
For i = 1 to rng.Rows.Count
With Range("B1")
If .Value = "" Then
.Value = rng.Range("A" & i)
Else
.Value = .Value & "OR" & rng.Range("A" & i)
End If
End With
Next
Using Do Loop
Dim rng as Range, i as integer
Set rng = Range("A1", "A5")
i = 1
Do Until i > rng.Rows.Count
With Range("B1")
If .Value = "" Then
.Value = rng.Range("A" & i)
Else
.Value = .Value & "OR" & rng.Range("A" & i)
End If
End With
i = i + 1
Loop
Hope this helps.

With a formula (assuming Apples is in A2 and B1 is blank):
=IF(ISBLANK(B1),A2,B1&" OR "&A2)
copied down to suit (and output in last row).

A simple solution. Feel free to modify the code.
I did not include much flexibility such as count last row.
Just a basic template to show you how to use for loop.
for li_row = 1 to 10
if str_file = '' then
str_file = cells(li_row,1).value
else
str_file = str_file + ' OR ' + cells(li_row,1).value
end if
next
cells(1,2).value = str_file

One more method ..it works upto last used row in column A
Tested
Sub testing()
Dim lrow As Integer
Dim erange As Range
Dim str As String
With ActiveSheet
lrow = .Range("A" & Rows.Count).End(xlUp).Row
str = ""
For Each erange In .Range("A2:A" & lrow)
If str = "" Then
str = erange.Value
Else
str = str & " OR " & erange.Value
End If
Next erange
MsgBox (str)
End With
End Sub

Related

VBA Looping through single row selections and executing concat code

So, I've been scratching my head for a couple of hours now trying to figure this out. No matter where I look and what I do, I can't seem to make it work.
I have an excel document with ~20 columns and a completely variable number of rows. I want to concatenate each adjacent cell within the defined width (columns A:V)into the first cell (A1 for the first row), and then move to the next row and do the same until I get to the bottom. Snippet below:
Example before and after I'm trying to make
I have the code that does the concatenation. To make it work I have to select the cells I want to concatenate (A1:V1), and then execute the code. Even though some cells are blank, I need the code to treat them this way and leave semicolons there. The code works exactly as I need it to, so I've been trying to wrap it in some sort of Range select, offset, loop:
Dim c As Range
Dim txt As String
For Each c In Selection
txt = txt & c.Value & ";"
Next c
Selection.ClearContents
txt = Left(txt, Len(txt) - 2)
Selection(1).Value = txt
What I am struggling with is making the selection A1:V1, running the code, and then looping this down to A2:V1, A3:V3, etc. I think this can be done with a loops and an offset, but I cannot for the life of me work out how.
Any help at all would be much appreciated :)
This uses variant Arrays and will be very quick
Dim rng As Range
With Worksheets("Sheet4") 'change to your sheet
'set the range to the extents of the data
Set rng = .Range(.Cells(1, 1), .Cells(.Rows.Count, 22).End(xlUp))
'Load data into an array
Dim rngArr As Variant
rngArr = rng.Value
'create Out Bound array
Dim OArr() As Variant
ReDim OArr(1 To UBound(rngArr, 1), 1 To 1)
'Loop array
Dim i As Long
For i = LBound(rngArr, 1) To UBound(rngArr, 1)
'Combine Each Line in the array and load result into out bound array
OArr(i, 1) = Join(Application.Index(rngArr, i, 0), ";")
Next i
'clear and load results
rng.Clear
rng.Cells(1, 1).Resize(UBound(OArr, 1)).Value = OArr
End With
Here's a quick little script I made up to do this - the main thing to note is that I don't use selection, I used a defined range instead.
Sub test()
Dim i As Long
Dim target As Range
Dim c As Range
Dim txt As String
For i = 3 To 8
Set target = Range("A" & i & ":C" & i)
For Each c In target
txt = txt & c.Value & ";"
Next c
Cells(i + 8, "A").Value2 = Left$(txt, Len(txt) - 1)
txt = ""
Next i
End Sub
Just change the range on the below to your requirements:
Sub concat_build()
Dim buildline As String
Dim rw As Range, c As Range
With ActiveSheet
For Each rw In .Range("A2:V" & .Cells(.Rows.Count, "B").End(xlUp).Row + 1).Rows
buildline = ""
For Each c In rw.Cells
If buildline <> "" Then buildline = buildline & ";"
buildline = buildline & c.Value2
Next
rw.EntireRow.ClearContents
rw.EntireRow.Cells(1, 1) = buildline
Next
End With
End Sub

Getting dynamic dropdown list in VBA validation

I have the following case:
1.Column D populated with about 100 values,
2. Using these I create a validation in the Column A cells
3. If I have a value in Cell "A1", this particular value should not appear
in Cell "A2" dropdown list, now the values in "A1" and "A2" should not appear in "A3" and so on.
What should be the thought process to write the VBA code for this?
I found this one interesting, so check this out... Should work as you expect it...
Post this code into your Worksheet and adapt it for your needs (if necessary). Hope it helps.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ws As Worksheet
Dim dict As Object
Dim dictAlreadyTaken As Object
Dim valueRange As Range
Dim targetRange As Range
Dim cell As Object
Dim Key As Variant
Dim currentList() As Variant
Dim i As Integer
If Target.Column = 1 Then
Set ws = Worksheets(1)
Set dict = CreateObject("Scripting.Dictionary")
Set dictAlreadyTaken = CreateObject("Scripting.Dictionary")
Set valueRange = ws.Range("D:D")
Set targetRange = ws.Range("A:A")
For Each cell In valueRange
If cell.Value <> "" Then
dict.Add cell.Value, cell.Row
Else
Exit For
End If
Next cell
For Each cell In targetRange
If cell.Row <= dict.Count Then
If cell.Value <> "" Then
'ad the value taken
dictAlreadyTaken.Add cell.Value, cell.Row
End If
Else
Exit For
End If
Next cell
For Each cell In targetRange
If cell.Row <= dict.Count Then
'add this list
Erase currentList
ReDim currentList(0)
i = 0
ws.Cells(cell.Row, 1).Validation.Delete
For Each Key In dict.keys
If Not dictAlreadyTaken.exists(Key) Then
i = i + 1
ReDim Preserve currentList(i) As Variant
currentList(i) = Key
End If
Next Key
If UBound(currentList) > 0 Then
ws.Cells(cell.Row, 1).Validation.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, Formula1:=Join(currentList, ",")
End If
Else
Exit For
End If
Next cell
End If
End Sub
My thought process would be:
First loop to list all the ranges we need to compare:
Cells(1,1) should not appear in Range(Cells(1,4),Cells(1,4))
Cells(2,1) should not appear in Range(Cells(1,4),Cells(2,4))
Cells(3,1) should not appear in Range(Cells(1,4),Cells(3,4)) ...etc...
Easy enough. Now that we know what ranges to compare, loop through the comparisons:
re: Cells(3,1) should not appear in Range(Cells(1,4),Cells(3,4)) :
.
Dim c as range
For Each c in Range(Cells(1,4),Cells(3,4))
If c.Value = Cells(1,4).Value then
'it's a match! Delete it (or whatever)
c.Value = ""
End If
Next c
Finally, put the two loops together...
From what I understand of your description, I came up with this:
Sub compareCells()
Dim c As Range, x As Integer
For x = 1 To 10
Debug.Print "Cells(" & x & ",1) should not appear in Range(Cells(1,4),Cells(" & x & ",4))"
For Each c In Range(Cells(1, 4), Cells(x, 4))
Debug.Print "compare " & Cells(x, 1).Address & " to " & c.Address
If Cells(x, 1).Value = c.Value Then
Cells(x, 1).Cells.Font.Color = vbBlue
End If
Next c
Next x
End Sub
It should be easily adaptable to your needs, or if not, there are plenty of existing solutions & resources, even a Stack Overflow tag: cascadingdropdown
Here is an approach:
Select a column in your sheet that you can use for a named range (this column can be hidden). For the purpose of example below, I've used column J and my named range is called ValidationRange. I have also assumed that the values in your worksheet start from row 2.
Now in a module, add the following sub:
Sub SetDropDownRange()
Dim oNa As Name: Set oNa = ThisWorkbook.Names.Item("ValidationRange")
Dim iLR&, iC&, iLRJ&
Dim aDRange As Variant
Dim aVRRange As Variant
With ThisWorkbook.Worksheets("Sheet12")
iLR = .Range("D" & .Rows.count).End(xlUp).Row
iLRJ = .Range("J" & .Rows.count).End(xlUp).Row
aDRange = Range("D2:D" & iLR)
For iC = LBound(aDRange) To UBound(aDRange)
If Len(Trim(aDRange(iC, 1))) <> 0 Then
If Application.WorksheetFunction.CountIf(Range("A:A"), aDRange(iC, 1)) = 0 Then
If IsArray(aVRRange) Then
ReDim Preserve aVRRange(UBound(aVRRange) + 1)
Else
ReDim aVRRange(0)
End If
aVRRange(UBound(aVRRange)) = aDRange(iC, 1)
End If
End If
Next
End With
Range("J2:J" & iLRJ).Value = ""
Range("J2:J" & UBound(aVRRange) + 2).Value = Application.Transpose(aVRRange)
oNa.RefersTo = oNa.RefersToRange.Resize(UBound(aVRRange) + 1, 1)
End Sub
Now call this function when something changes in your worksheet.. like so:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 Or Target.Column = 4 Then
SetDropDownRange
End If
End Sub
Set Data Validation for the cells in column A using the named range (which is ValidationRange for this example)
Now everytime your select a value in column A, it will remove that value from the named range and hence from your dropdown box

VBA To Return 1 if Range contain any value - not working

Here I got problem return 1010 logical test. It should checking each cell in range, and exit loop if cell contain number. Return 1 if any cell in range contain value, return 0 if all cell is blank. I tried worksheet function CountIf, CountA, Not IsEmpty, IsText but result is different seems like the blank cell contain invisible string. IsNumeric works on single cell but when range included its not working. I also note the first time I got it run, it produce result, second run causing error. Please help, my range need to be in variable term.
Sub Try()
Dim path As String, myfile As String, file As String
Dim wb As Workbook
Dim i As Integer
Dim NCell As Range
Dim IsNumber As Boolean
path = "E:\SouthNorth\"
myfile = path & "1979.xls"
file = Dir(myfile)
Set wb = Workbooks.Open(Filename:=path & file)
wb.Activate 'necessary?
i = 24
'here object defined error
For Each NCell In Worksheets("Sheet1").Range(Cells(i, 2), Cells(i, 4))
If IsNumeric(NCell) Then
IsNumber = True
If IsNumber = True Then Exit For
End If
Next NCell
Select Case IsNumber
Case True
wb.Worksheets("Sheet2").Range("B" & i) = 1
Case False
wb.Worksheets("Sheet2").Range("B" & i) = 0
End Select
End Sub
I think this belongs in a comment but I'm not allowed to...
You mentioned that your problem is when you try running it a second
time, which means that the error could be from vba trying to open
your file when it is already open. Everything else seems to work
Ignore all this, it was my original "answer" but I don't know how to format a strikethough and I don't want to delete it all.
Try this code
Sub Try()
Dim wb As Workbook
Dim path As String
Dim i As Integer, j As Integer
Dim NCell As Range
path = "E:\SouthNorth\1979.xls"
Set wb = Workbooks.Open(Filename:=path)
wb.Activate
i = 24
Sheets("Sheet2").Range("B" & i).Value = 0
For j = 2 To 4
Set NCell = Sheets("Sheet1").Cells(i, j)
If IsNumeric(NCell.Value) Then
Sheets("Sheet2").Range("B" & i).Value = 1
Exit For
End If
Next j
End Sub
You can use 'CountBlank' - if the column range is always 3 cells then you can declare a boolean and subtract the blank count from 3, giving you a 0 (false) if all cells are blank or anything above 0 (true) if at least one cell is occupied:
Dim x As Boolean
x = 3 - Application.WorksheetFunction.CountBlank(Worksheets("Sheet1").Range(Cells(i, 2), Cells(i, 4)))
MsgBox x
If it's specific to numerical values (e.g. ignoring text) then just add to your IsNumeric line:
If IsNumeric(ncell) And Not ncell = "" Then
To start off with, it is a really bad practice to leave the Cells defining a Range object without a parent worksheet. See Is the . in .Range necessary when defined by .Cells? for more information.
Worksheets("Sheet1").Range(Cells(24, 2), Cells(24, 4))
You seem to specifically want a count for numbers. Bringing in the worksheet's COUNT function will do this.
With Worksheets("Sheet1")
With .Range(.Cells(24, 2), .Cells(24, 4))
Worksheets("Sheet2").Range("B" & i) = Abs(CBool(Application.Count(.Cells)))
End With
End With
You can also stay strictly within VBA with SpecialCells using xlCellTypeConstants with xlNumbers.
Dim rng As Range
With Worksheets("Sheet1")
On Error Resume Next
Set rng = .Range(.Cells(24, 2), .Cells(24, 4)).SpecialCells(xlCellTypeConstants, xlNumbers)
On Error GoTo 0
If Not rng Is Nothing Then
Worksheets("Sheet2").Range("B" & i) = 1
Else
Worksheets("Sheet2").Range("B" & i) = 0
End If
End With

VBA passing variable to function

Final Goal: I am trying to edit existing text in a column from UPPER CASE to Proper (Upper Case)- With an exception whereby if the word follows a hyphen It Will-remain Lower Case. I am outputting it 2 columns across.
My first sub needs to pass my active cell to my Function that applies the formatting (function was tested and works).
I don't know how to make that work.
Option Explicit
Dim txt As String
Dim i As Long
Dim strTest As String
Dim strArray() As String
Dim lCaseOn As Boolean
Dim firstRow As Long, startIt As Long
Dim thisCell As Range
Dim lastRow As Long
Sub throughCols()
Dim thisCell As Range
dataRange
startIt = firstRow + 1
For i = startIt To lastRow
' No idea how to pass my cell I am picking up through to my function
thisCell = Sheets("Names").Select: Range("B" & i).Select
arrayManip (thisCell)
'I need to pass this ActiveCell to arrayManip- not sure how
Next i
End Sub
'====================================================================
Function arrayManip(thisCell)
'Hard coded source cell for testing
' Now trying to itterate through column
' clear out all data
Erase strArray
txt = ""
'set default case
lCaseOn = False
' string into an array using a " " separator
strTest = WorksheetFunction.Proper(ActiveCell.Value)
strTest = Replace(strTest, "-", " - ")
strArray = Split(strTest, " ")
' itterate through array looking to make text foll
For i = LBound(strArray) To UBound(strArray)
If strArray(i) = "-" Then
lCaseOn = True
GoTo NextIteration
End If
If lCaseOn Then
strArray(i) = LCase(strArray(i))
lCaseOn = False
NextIteration:
End If
' loop through the array and build up a text string for output to the message box
txt = txt & strArray(i) & " "
' remove the space
txt = Trim(Replace(txt, " - ", "-"))
ActiveCell.Offset(0, 2).Select: ActiveCell.Value = txt
Next i
' MsgBox txt
End Function
'====================================================================
Sub dataRange()
With Sheets("Names").Columns("B")
If WorksheetFunction.CountA(.Cells) = 0 Then '<--| if no data whatever
MsgBox "Sorry: no data"
Else
With .SpecialCells(xlCellTypeConstants) '<--| reference its cells with constant (i.e, not derived from formulas) values)
firstRow = .Areas(1).Row
lastRow = .Areas(.Areas.Count).Cells(.Areas(.Areas.Count).Rows.Count).Row
End With
' MsgBox "the first row is " & firstRow
' MsgBox "last row is " & lastRow
End If
End With
End Sub
Instead of:
thisCell = Sheets("Names").Select: Range("B" & i).Select
Do:
'## Use the SET keyword to assign an object variable
Set thisCell = Sheets("Names").Range("B" & i)
Also, instead of relying on ActiveCell in the function body, change to:
strTest = WorksheetFunction.Proper(thisCell.Value)
Then call your function:
Call arrayManip(thisCell)
If your function doesn't return a value to the caller, it should probably be a Sub instead. Change it to a Sub and the above Call statement should still work.
See also:
How to make Excel VBA variables available to multiple macros?

Loop through table, shading empty rows and deleting spaces in occupied cells

I'm looking for a way to loop through the third column of each row of a table.
If that cell is blank, it should shade the entire row
If that cell is occupied, it should look for any spaces and replace all spaces with a single # (for example 123 abc becomes 123#abc).
It should not run on the header row of the table.
Some random items-
the worksheet name is Quote
table name is QuoteTable
shading should be RGB(255, 248, 220)
The number of rows in the table will always vary, but the macro will always be checking the contents of the third column of the table which is named Model #.
Thanks for the responses, I will try each one, and in the meantime I came up with this--- which does everything except it highlights every single row from A-N not just the correct ones.
Public Sub ValidateSKUs()
Dim sMfrPN As String
Dim tbl1 As Range
Dim myCell As Range
Set tbl1 = Sheets("Quote").Range("QuoteTable")
For Each myCell In tbl1.Columns(3).Cells
If myCell.Value = "" Then
Sheets("Quote").Range("A" & myCell & ":N" & myCell).Interior.Color = RGB(255, 248, 220)
Sheets("Quote").Range("A" & myCell & ":N" & myCell).Interior.Pattern = xlSolid
Else
sMfrPN = CleanUpPN(myCell)
myCell = sMfrPN
End If
Next
End Sub
Public Function CleanUpPN(ByVal sMfrPN As String) As String
Dim sPN As String
'Trim trailing spaces
sPN = Trim(sMfrPN)
'Replace space with #
sPN = Replace(sPN, " ", "#")
'remove multiple # (e.g. ##)
Do Until InStr(1, sPN, "##") = 0
sPN = Replace(sPN, "##", "#")
Loop
CleanUpPN = sPN
End Function
VBA coding for a structured table can be a pain. You can either deal with the table as a ListObject object or Set a Range object variable to the .DataBodyRange property and deal with the cell addresses as you would with any other worksheet reference.
Sub fix_model_no()
Dim mn As Range
With Worksheets("Sheet1") '<~~ set this properly!
With .ListObjects("QuoteTable")
For Each mn In Range("QuoteTable[Model '#]")
If Len(Trim(mn.Value2)) Then
Intersect(.DataBodyRange, Rows(mn.Row)).Interior.ColorIndex = xlColorIndexNone
mn = Replace(Application.Trim(mn.Value2), Chr(32), Chr(35))
Else
mn.ClearContents
Intersect(.DataBodyRange, Rows(mn.Row)).Interior.ColorIndex = 3 'red
End If
Next mn
End With
End With
End Sub
The single tick in QuoteTable[Model '#] is necessary to reference the Model # column. Any reference to a ListObject table likes to know which worksheet the table resides on.
You may wish to consider Conditional Formatting for the row highlighting based upon the blank cells in column C.
      
Change "Z" to whatever column you require
Sub Oh_Ya()
Dim sh As Worksheet
Dim rng As Range, c As Range
Dim rws As Long, r
Set sh = Sheets("Quote")
With sh
rws = .Cells(.Rows.Count, "A").End(xlUp).Row
Set rng = .Range("C2:C" & rws)
For Each c In rng.Cells
c = Replace(c, " ", "#")
If c = "" Then
r = c.Row
Range("A" & r & ":Z" & r).Interior.Color = RGB(255, 248, 220)
End If
Next c
End With
End Sub