Excel Macro multiple range concatenation - vba

I am trying to concatenate cells through excel VBA. This involves multiple ranges. Below is my table
Degree1
Course1,Course2,Course3
Course4,course5,course6
Degree2
Course1,Course2
Course3,Course4
Course5
Course6,Course7
Degree3
Course1,Course2,Course3
Course4,course5,course6
Course7
I want to concatenate all the courses listed below a degree into a single cell next to the degree. Each degree has multiple courses & the # of rows differ for each degree.
I am using excel find function to identify the cell contains the degree & select the courses below it. I am also using the concat function from http://www.contextures.com/rickrothsteinexcelvbatext.html so that I can concatenate the selected ranges.
I tried to write the below code but this is not working, I am getting value error in the end. I guess the range is not stored in the variable
Sub concatrange()
Dim D1Crng As Range 'to set courses under degree1 as range
Dim D2Crng As Range
Dim D3Crng As Range
Dim D1cell As Range 'to identify the cell of D1 and set it as range
Dim D2cell As Range
Dim D3cell As Range
Range("A1:B100").Select
Selection.Find(What:="Degree1", _
LookIn:=xlValues, LookAt:=xlPart, _
SearchOrder:=xlByColumns, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Select
ActiveCell.Select
Set D1cell = Selection
Range(D1cell).Activate
ActiveCell.Offset(1, 0).End(xlDown).Select
Set D1Crng = Selection
Range(D1cell).Activate
ActiveCell.Offset(0, 1).Select
Selection.Formula = "=concat("","",D1Crng)"
End sub
I am repeating the above process for concatenating for other degrees.

VBA's .Join command should work well here.
Sub many_degrees()
Dim rw As Long
With ActiveSheet
For rw = 1 To .Cells(Rows.Count, 1).End(xlUp).Row
If LCase(Left(.Cells(rw, 1).Value, 6)) = "degree" Then
If Application.CountA(.Cells(rw, 1).Resize(3, 1)) > 2 Then
.Cells(rw, 2) = Join(Application.Transpose(.Range(.Cells(rw, 1).Offset(1, 0), .Cells(rw, 1).End(xlDown)).Value), Chr(44))
Else
.Cells(rw, 2) = .Cells(rw, 1).Offset(1, 0).Value
End If
End If
Next rw
End With
End Sub
I have accounted for the case where only one (or none) line of degrees exists below the DegreesX title. The code does depend upon each 'title' starting with Degree as the first 6 characters (not case sensitive). I've used .Offset(x, y) where a simple +1 to the row or column probably would have sufficed, but that may help in understanding the purpose of the various code lines.
      

Related

EXCEL VBA - Delete if a column exists

I have a requirement wherein, I need to delete that Particular column if that exists.
I am trying to locate the Particular column through column header.
This is my code,
If sColumnName = (WorksheetFunction.Match("RSD", Sheets("RS_Report").Rows(1), 0)) And sColumnName = True Then
DDC= WorksheetFunction.Match("RSD", Sheets("RS_Report").Rows(1), 0)
DFC= GetColumnLetter(DDC)
Range(DFC& 1 & ":" & DFC& lastrow).Select
Selection.Delete Shift:=xlUp
the GetColumnLetter and lastrow are my user defined functions and they return correct values. I am not sure how to check if a column exists or not. Kindly help me with this. Share your thoughts.
you can simply go like this
Dim col As Variant
With Sheets("RS_Report") '<--| reference relevant worksheet
col = WorksheetFunction.Match("RSD", .Rows(1), 0) '<--| try searching its row 1 for wanted header text
If Not IsError(col) Then .Columns(col).Delete '<--| if found then delete its entire column
End With
There are three ways to do this.
1) A for loop that looks to the extents of the header row for a specific string.
Pro: its easy
Con: the string has to be exact
something like
Dim string as yourString
Dim lColumn As Long
lColumn = ws.UsedRange.Columns.Count
yourString = whatever
for x = 1 to lcolumn
if range(cells(1, 1), Cells(1, x)).Value = yourString
Columns(x).EntireColumn.Delete
End If
next
2) use the Range.Find method which you can learn about here https://msdn.microsoft.com/en-us/library/office/ff839746.aspx
Here is a short rough example you can use as reference:
Sub Header_Format()
Dim rLastCell As Range
Set rLastCell = UpdateSheet.Cells.Find(What:="*", After:=UpdateSheet.Cells(1, 1), LookIn:=xlFormulas, LookAt:= _
xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious, MatchCase:=False)
With UpdateSheet.Range(Cells(4, 1), Cells(4, rLastCell.Column))
.Copy MasterSheet.Cells(1, 1)
.Copy RemovalSheet.Cells(1, 1)
End With
End Sub
3) Finally there is using the match method, which someone spoke on already.
https://msdn.microsoft.com/en-us/library/office/ff835873.aspx

VBA: Transferring data from one worksheet to another with a "moving range"

I'm trying to create a dashboard of my inventory system. The dashboard will display Sales for the day (category 1), Purchases that need to be made (Category 2), Purchase Orders that are to be expected (Cateogry 3), and Work in Process (Category 4). For this question, I'm only going to focus on category 2, Purchases that need to be made.
I'm trying to transfer all the data from Worksheets("Purchasing") to the Dashboard under category 2. I'm trying to use named ranges to do this, because the range of each category will fluctuate as items are added/deleted. You can find a sample of the workbook that I'm working on here - it's on excelforum.com.
The code below is what I have so far. It works to a degree, but the Range("PurchaseStart"), which is Cell $A$8, starts at A:1. I don't know how to only select the named range that I'm looking for. I added "End #" statements at the end of each row to signify a cutoff and hope to trick excel into only selecting the range of the particular category.
Option Explicit
Sub purchPull()
Dim Dashboard As Worksheet
Dim Purchasing As Worksheet
Dim PM As Range, D As Range, Rng As Range
Dim purchName As Range
Set Purchasing = Worksheets("Purchasing")
Set Dashboard = Worksheets("Dashboard")
' Go through each Item in Purchasing and check to see if it's anywhere within the named range "PurchaseStart"
' In this case it should be "A8:A9" - as there is nothing in the dasboard yet
For Each PM In Purchasing.Range(Purchasing.Cells(1, 1), Purchasing.Cells(Purchasing.Rows.Count, 1).End(xlUp))
With Dashboard.Range("PurchaseStart", Dashboard.Cells(Dashboard.Rows.Count, 1))
Set Rng = .Find(What:=PM.Offset(0, 1), _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not Rng Is Nothing Then
' Do nothing, as we don't want duplicates
Else
' From the start of the named range, transfer data over - THIS IS THE PROBLEM AREA
With Dashboard.Range("PurchaseStart", Dashboard.Cells(.Rows.Count, 1)).End(xlUp)
.Offset(1, 1) = PM.Offset(0, 0) ' Order Number
.Offset(1, 2) = PM.Offset(0, 1) ' SKU
.Offset(1, 3) = PM.Offset(0, 3) ' Qty
.Offset(1, 4) = PM.Offset(0, 4) ' Date
End With
End If
End With
Next
End Sub
You could do something along the lines of:
(This assumes that the beginning of each data section has some header in place, ie "Need to be made" and then below that header is where the data for that section goes):
Sub findDataStartRow()
Dim f as Range, dataStartRange as Range
Set f = Columns(1).Find(what:="Need to be made", lookat:xlWhole)
If Not f is Nothing Then
dataStartRange = Cells(f.row + 1, 1) 'Do stuff with this range... maybe insert rows below it to start data
Else: Msgbox("Not found")
Exit Sub
End if
End Sub
Do similar for each section. This way, no matter where the header goes (and therefore the beginning of where the data should be placed), you'll always have a named range of the location right below the header.
Alternatively, if you want to add the data to the end of the section, just find the header for the section below where you want the data to be and set the dataStartRange = Cells(f.row - 1, 1) after modifying the .Find correctly.
I figured it out. I think it's a pretty good way of handling the problem, but if anyone can think of a better way, I'd love to hear it. Thanks for the help everyone.
Option Explicit
Sub purchPull()
Dim Dashboard As Worksheet
Dim Purchasing As Worksheet
Dim PM As Range, D As Range, Rng As Range
Dim purchName As Range
Dim lastRow As Long
Dim firstRow As Long
Set Purchasing = Worksheets("Purchasing")
Set Dashboard = Worksheets("Dashboard")
' first row of named range "PurchaseStart"
firstRow = Dashboard.Range("PurchaseStart").Row + Dashboard.Range("PurchaseStart").Rows.Count
' Go through each Item in Purchasing and check to see if it's anywhere within the named range "PurchaseStart"
With Purchasing
For Each PM In Purchasing.Range(Purchasing.Cells(2, 1), Purchasing.Cells(Purchasing.Rows.Count, 1).End(xlUp))
With Dashboard.Range("PurchaseStart", Dashboard.Cells(Dashboard.Rows.Count, 1))
Set Rng = .Find(What:=PM.Offset(0, 0), _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not Rng Is Nothing Then
' Do nothing, as we don't want duplicates
Else
' Identify the last row within the named range "PurchaseStart"
lastRow = Dashboard.Range("PurchaseStart").Cells(1, 1).End(xlDown).Row
' Transfer the data over
With Dashboard.Cells(lastRow, 1).End(xlUp)
.Offset(1, 0).EntireRow.Insert
.Offset(1, 0) = PM.Offset(0, 0) ' Order Number
.Offset(1, 1) = PM.Offset(0, 1) ' SKU
.Offset(1, 2) = PM.Offset(0, 2) ' Qty
.Offset(1, 3) = PM.Offset(0, 3) ' Date
End With
End If
End With
Next
End With
End Sub

Excel VBA - Searching in a Loop

First, my code (below) works, but I am trying to see if it can be simplified. The macro in which this code is located will have a lot of specific search items and I want to make it as efficient as possible.
It is searching for records with a specific category (in this case "Chemistry") then copying those records into another workbook. I feel like using Activate in the search, and using Select when moving to the next cell are taking too much time and resources, but I don't know how to code it to where it doesn't have to do that.
Here are the specifics:
Search column T for "Chemistry"
Once it finds "Chemistry", set that row as the "top" record. e.g. A65
Move to the next row down, and if that cell contains "Chemistry", move to the next row (the cells that contain "Chemistry" will all be together"
Keep going until it doesn't find "Chemistry", then move up one row
Set that row for the "bottom" record. e.g. AX128
Combine the top and bottom rows to get the range to select. e.g. A65:AX128
Copy that range and paste it into another workbook
Here is the code:
'find "Chemistry"
Range("T1").Select
Cells.Find(What:="Chemistry", After:=ActiveCell, LookIn:=xlFormulas, LookAt _
:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False).Activate
'set top row for selection
toprow = ActiveCell.Row
topcellselect = "A" & toprow
'find all rows for Chemistry
Do While ActiveCell = "Chemistry"
ActiveCell.Offset(1, 0).Select
Loop
ActiveCell.Offset(-1, 0).Select
'set bottom row for selection
bottomrow = ActiveCell.Row
bottomcellselect = "AX" & bottomrow
'define selection range from top and bottom rows
selectionrange = topcellselect & ":" & bottomcellselect
'copy selection range
Range(selectionrange).Copy
'paste into appropriate sheet
wb1.Activate
Sheets("Chemistry").Select
Range("A2").PasteSpecial
Thanks in advance for any help!
You never need to select or activate unless that's really what you want to do (at the end of the code, if you want the user to see a certain range selected). To remove them, just take out the activations and selections, and put the things on the same line. Example:
wb1.Activate
Sheets("Chemistry").Select
Range("A2").PasteSpecial
Becomes
wb1.Sheets("Chemistry").Range("A2").PasteSpecial
For the whole code; I just loop thorugh the column and see where it starts and stops being "chemistry". I put it in a Sub so you only have to call the sub, saying which word you're looking for and where to Paste it.
Sub tester
Call Paster("Chemistry", "A2")
End sub
Sub Paster(searchWord as string, rngPaste as string)
Dim i as integer
Dim startRange as integer , endRange as integer
Dim rng as Range
With wb1.Sheets("Chemistry")
For i = 1 to .Cells(Rows.Count,20).End(XlUp).Row
If .Range("T" & i ) = searchWord then 'Here it notes the row where we first find the search word
startRange = i
Do until .Range("T" & i ) <> searchWord
i = i + 1 'Here it notes the first time it stops being that search word
Loop
endRange = i - 1 'Backtracking by 1 because it does it once too many times
Exit for
End if
Next
'Your range goes from startRange to endRange now
set rng = .Range("T" & startRange & ":T" & endRange)
rng.Copy
.Range(rngPaste).PasteSpecial 'Paste it to the address you gave as a String
End with
End sub
As you can see I put the long worksheet reference in a With to shorten it. If you have any questions or if it doesn't work, write it in comments (I haven't tested)
The most efficient way is to create a Temporary Custom Sort Order and apply it to your table.
Sub MoveSearchWordToTop(KeyWord As String)
Dim DestinationWorkSheet As Workbook
Dim SortKey As Range, rList As Range
Set SortKey = Range("T1")
Set rList = SortKey.CurrentRegion
Application.AddCustomList Array(KeyWord)
rList.Sort Key1:=SortKey, Order1:=xlAscending, Header:=xlGuess, _
OrderCustom:=Application.CustomListCount + 1, MatchCase:=False, _
Orientation:=xlTopToBottom, DataOption1:=xlSortNormal
Application.DeleteCustomList Application.CustomListCount
Set DestinationWorkSheet = Workbooks("Some Other Workbook.xlsx").Worksheets("Sheet1")
rList.Copy DestinationWorkSheet.Range("A1")
End Sub

Infinite loop with .Find method

I am trying to write a VBA script to automate moving things around in an spreadsheet that has a balance sheet imported from an accounting software.
The values on imported balance sheet start at row 5, column A has some text describing what the values of each row mean, and columns B and D have the amounts for each item.
The subtotals for each section and subsection of the balance sheet are on columns C and E. Each subtotal has is in a cell formatted with a solid upper border.
I would like to bring all these subtotals to the same columns as the values (i.e, columns B and D). I've tried to do this using the .Find method to search for cells with the specific format (cells with an upper border) and a Do loop to keep searching until I find all cells that should have a subtotal in it.
Notes:
I didn't use FindNext because it seems that it ignores format settings used in the preceding Find method, as described here.
I tried to used the FindAll function described by Tushar Mehta to go around this problem with FindNext, but it didn't find all cells with the specified format.
Here's the code. Any help is greatly appreciated!
Sub FixBalanceSheet()
Dim LookFor As Range
Dim FoundHere As String 'Address of the cell that should contain a subtotal
Dim beginAt As Range, endAt As Range, rng As Range 'Set the ranges for the sum to get the subtotal
Dim place As String 'String with the address of a cell that will contain a subtotal
Dim WhereToLook As Range 'Range where subtotals are to be found
'Set workbook and worksheet
With Sheets("Sheet1")
Set WhereToLook = Range("A5:F100")
'Every cell containing a subtotal has an upper border. So, look for cells containing border!
With Application.FindFormat.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.ColorIndex = xlAutomatic
.TintAndShade = 0
End With
'Call search using .Find
Set LookFor = WhereToLook.Find(What:="", After:=Cells(5, 2), _
LookIn:=xlFormulas, LookAt:=xlPart, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=True)
If Not LookFor Is Nothing Then 'Test if a cell with a bottom border is found
'What happens when a subtotal cell is found:
FoundHere = LookFor.Address
Debug.Print "Found at: " & Found
'Loop to set a range, sum values and put them in the right cell
Do
'% find out a range to calculate subtotals and put the value in the right cells %'
'Call for next search
With Application.FindFormat.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.ColorIndex = xlAutomatic
.TintAndShade = 0
End With
Set LookFor = WhereToLook.Find(What:="", After:=endAt, SearchFormat:=True)
Debug.Print "LookFor now is: " & LookFor.Address
Rem If LookFor.Address = Found Then ' Do not allow wrapped search
Rem Exit Do
Rem End If
Loop Until LookFor Is Nothing Or LookFor.Address = FoundHere ' Do not allow wrapped search
End If
End With
End Sub
Consider using a range object to loop through your range. You can add a total if you need a grand total, but this is probably an easier way than trying to select all cells that have formatting.
For example:
Sub TestStackOverflowCode()
Dim r As Range
Dim rngToChk As Range
'This is where you'd insert WhereToLook
Set rngToChk = ActiveSheet.Range("B1:B4")
For Each r In rngToChk
'If the top edge does not NOT have a border
If r.Borders(xlEdgeTop).LineStyle <> xlNone Then
'Copy the cell value to two cells to the right
r.Offset(, 2).Value = r.Value
End If
Next r
End Sub
I would recommend going back to the Range.Find/Range.FindNext method. There were some holes in your logic conditions and I believe I've adjusted them.
Set LookFor = WhereToLook.Find(What:="", After:=Cells(5, 2), _
LookIn:=xlFormulas, LookAt:=xlPart, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=True)
If Not LookFor Is Nothing Then 'Test if a cell with a bottom border is found
'What happens when a subtotal cell is found:
FoundHere = LookFor.Address
Debug.Print "Found at: " & FoundHere
'Loop to set a range, sum values and put them in the right cell
Do
'do something with LookFor as a Range Object here
'Call for next search
Set LookFor = WhereToLook.FindNext(After:=LookFor) '<~~ look for next after current cell
Debug.Print "LookFor now is: " & LookFor.Address
Loop Until LookFor.Address = FoundHere ' Do not allow wrapped search (LookFor will never be nothing here)
End If
The findNext may not have worked if
you had [FindFormat.Borders...] after the [Set LookFor = WhereToLook.Find(...]
I do think ThreeTrickPony's answer is more elegant, but in general I'd suggest finding an alternative way to identify cells rather than formatting.

VBA: copying the first empty cell in the same row

I am a new user of VBA and am trying to do the following (I got stuck towards the end):
I need to locate the first empty cell across every row from column C to P (3 to 16), take this value, and paste it in the column B of the same row.
What I try to do was:
Find non-empty cells in column C, copy those values into column B.
Then search for empty cells in column B, and try to copy the first non-empty cell in that row.
The first part worked out fine, but I am not too sure how to copy the first non-empty cell in the same row. I think if this can be done, I might not need the first step. Would appreciate any advice/help on this. There is the code:
Private Sub Test()
For j = 3 To 16
For i = 2 To 186313
If Not IsEmpty(Cells(i, j)) Then
Cells(i, j - 1) = Cells(i, j)
End If
sourceCol = 2
'column b has a value of 2
RowCount = Cells(Rows.Count, sourceCol).End(xlUp).Row
'for every row, find the first blank cell, copy the first not empty value in that row
For currentRow = 1 To RowCount
currentRowValue = Cells(currentRow, sourceCol).Value
If Not IsEmpty(Cells(i, 3)) Or Not IsEmpty(Cells(i, 4)) Or Not IsEmpty(Cells(i, 5)) Or Not IsEmpty(Cells(i, 6)) Then
Paste
~ got stuck here
Next i
Next j
End Sub
Your loop is really inefficient as it is iterating over millions of cells, most of which don't need looked at. (16-3)*(186313-2)=2,422,043.
I also don't recommend using xlUp or xlDown or xlCellTypeLastCell as these don't always return the results you expect as the meta-data for these cells are created when the file is saved, so any changes you make after the file is saved but before it is re-saved can give you the wrong cells. This can make debugging a nightmare. Instead, I recommend using the Find() method to find the last cell. This is fast and reliable.
Here is how I would probably do it. I'm looping over the minimum amount of cells I can here, which will speed things up.
You may also want to disable the screenupdating property of the application to speed things up and make the whole thing appear more seemless.
Lastly, if you're new to VBA it's good to get in the habit of disabling the enableevents property as well so if you currently have, or add in the future, any event listeners you will not trigger the procedures associated with them to run unnecessarily or even undesirably.
Option Explicit
Private Sub Test()
Dim LastUsed As Range
Dim PasteHere As Range
Dim i As Integer
Application.ScreenUpdating=False
Application.EnableEvents=False
With Range("B:B")
Set PasteHere = .Find("*", .Cells(1, 1), xlFormulas, xlPart, xlByRows, xlPrevious, False, False, False)
If PasteHere Is Nothing Then Set PasteHere = .Cells(1, 1) Else: Set PasteHere = PasteHere.Offset(1)
End With
For i = 3 To 16
Set LastUsed = Cells(1, i).EntireColumn.Find("*", Cells(1, i), xlFormulas, xlPart, xlByRows, xlPrevious, False, False, False)
If Not LastUsed Is Nothing Then
LastUsed.Copy Destination:=PasteHere
Set PasteHere = PasteHere.Offset(1)
End If
Set LastUsed = Nothing
Next
Application.ScreenUpdating=True
Application.EnableEvents=True
End Sub
Sub non_empty()
Dim lstrow As Long
Dim i As Long
Dim sht As Worksheet
Set sht = Worksheets("Sheet1")
lstrow = sht.Cells(sht.Rows.Count, "B").End(xlUp).Row
For i = 1 To lstrow
If IsEmpty(Range("B" & i)) Then
Range("B" & i).Value = Range("B" & i).End(xlToRight).Value
End If
Next i
End Sub