Excel VBA - How to insert comma after each word in a column - vba

I have a column that contains multiple word strings. Like this:
+---+-------------------------------+--------------+-------------------------------+--------------+-------------------------------+-------------------------------+
| | A | B | C | D | E | F |
+---+-------------------------------+--------------+-------------------------------+--------------+-------------------------------+-------------------------------+
| 1 | Early Summer Lawn Application | Service Call | Early Summer Lawn Application | Grub Control | Early Summer Lawn Application | Early Summer Lawn Application |
+---+-------------------------------+--------------+-------------------------------+--------------+-------------------------------+-------------------------------+
My question is how can I insert a comma after each word in the column, to end up with:
+---+-------------------------------+--------------+-------------------------------+--------------+-------------------------------+-------------------------------+
| | A | B | C | D | E | F |
+---+-------------------------------+--------------+-------------------------------+--------------+-------------------------------+-------------------------------+
| 1 | Early,Summer,Lawn,Application | Service,Call | Early,Summer,Lawn,Application | Grub,Control | Early,Summer,Lawn,Application | Early Summer Lawn Application |
+---+-------------------------------+--------------+-------------------------------+--------------+-------------------------------+-------------------------------+
Its ok to lose the space between the words and its ok if the results are in a different column, I just don't know how to insert the commas.

Won't a simple replace work here?
=replace(A1," ", ",")
replaces space with comma. The other function is
=substitute(A1, " ", ",")
also works, but substitute has another argument that might come in handy. It specifies which occurrence to substitute. For example, if you only wanted to replace the first blank space with comma but leave other blank spaces as is, then try this:
=substitute(A1, " ", ",", 1)
Bottom line, use replace if you know where to replace (position), and use substitute if you know what to replace (content). Either would work for a narrow class of problems as you discovered.

The following example erplaces programmatically all spaces with commas:
Sub example()
Dim s1 As String, s2 As String
Dim pos As Integer
s1 = ActiveSheet.ActiveCell.Value
s2 = ""
pos = InStr(1, s1, " ")
While (pos <> 0)
s2 = s2 & Mid(s1, 1, pos - 1) & ","
s1 = Mid(s1, pos + 1)
pos = InStr(1, s1, " ")
Wend
s2 = s2 & s1
ActiveSheet.ActiveCell.Value = s2
End Sub

Related

Excel. copy text string and number of repeats to new columns

Given two columns (A and B), one with a text and one with an integer, such as:
A | B
pen | 3
pen | 5
How could I fill the columns C, D, E [...] with the concatenation of the given string on each row with all integers starting from 1 until the specified number?
The desired output for the given example would be:
A | B | C | D | E | F | G
pen | 3 | pen01 | pen02 | pen03 | |
pen | 5 | pen01 | pen02 | pen03 | pen04 | pen05
With a simple vba sub this can be achieved:
Sub CreateValues
With ActiveSheet
Dim LastRow as Long: LastRow = .Range("A" & .Rows.Count).End(xlup).Row
For i = 1 To LastRow
Max_Num = .Cells(i, 2)
For j = 1 to Max_Num
.Cells(i, j + 2) = .Cells(i, 1) & Format(j, "00")
Next j
Next i
End With
End Sub
If you are looking for a formula solution without needing to resort to VBA, you can use this formula in C1 and drag in both dimensions:
=IF(COLUMNS($C1:C1)<=$B1,CONCATENATE($A1,TEXT(COLUMNS($C1:C1),"00")),"")

How to delete found values from Vlookup function but not from excel itself, so the vlookup wont search the cells again

I got 2 columns on 2 sheets of ~100000 cells long.
Those columns look like this:
---------
| 1 | a |
---------
| 2 | b |
---------
| 3 | c |
---------
| 4 | d |
---------
| 5 | e |
---------
and this:
---------
| 1 | a |
---------
| 3 | k |
---------
| 2 | b |
---------
| 4 | d |
---------
Now I am comparing the first columns to each other, if they match it has to check if the second column also matches. So the result will look like this:
---------------------
| 1 | a | correct |
---------------------
| 2 | b | correct |
---------------------
| 3 | c | wrong |
---------------------
| 4 | d | correct |
---------------------
| 5 | e | not found |
---------------------
I am using this function to do this: =IFERROR(IF(VLOOKUP(A3;newsheet!A:B;2;FALSE)=B3;"Correct";"Wrong");"Not Found") But to do this, it takes very long, I am using excel 2016 and all my 4 processors. Now it calculates slower and slower, probably because I got the first column on alphabetical order both, but the deeper it gets, the more rows it is going to check. So is there any way to let the VLOOKUP function not check the cells, it did already found an agreement.
So in my example: if it found the | 1 | a |, the next round it will search only the following remaining items:
---------------------
| 2 | b | correct |
---------------------
| 3 | c | wrong |
---------------------
| 4 | d | correct |
---------------------
| 5 | e | not found |
---------------------
Thanks in advance for helping me out with this problem
Unless you need the values to dynamically update; the VBA is a better alternative to 100K formulas. Using an ArrayList and arrays it took 1.98 seconds to process the data.
Sub ValidateData()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
'You'll need to adjust these const values
Const LOOKUP_SHEET As String = "newsheet"
Const TARGET_SHEET As String = "oldsheet"
Dim x As Long, y As Long
Dim data As Variant, results As Variant
Dim key As String
Dim list As Object
Set list = CreateObject("System.Collections.ArrayList")
With Worksheets(LOOKUP_SHEET)
'Load the values from columns A and B into an array
data = .Range("A1", .Range("A" & .Rows.Count).End(xlUp)).Resize(, 2)
End With
For x = 1 To UBound(data, 1)
'Create a unique identifier
'using a delimiter to ensure value don't mix
key = data(x, 1) & "|" & data(x, 2)
If Not list.Contains(key) Then list.Add key
Next
With Worksheets(TARGET_SHEET)
'Load the values from columns A and B into an array
data = .Range("A1", .Range("A" & .Rows.Count).End(xlUp)).Resize(, 2)
'Resize the results array
ReDim results(1 To UBound(data), 1 To 1)
For x = 1 To UBound(data, 1)
'Create a unique identifier
'using a delimiter to ensure value don't mix
key = data(x, 1) & "|" & data(x, 2)
results(x, 1) = IIf(list.Contains(key), "Correct", "Wrong")
Next
.Range("C1").Resize(UBound(results, 1)) = results
End With
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Can't post comments because of rep issues, but assuming they are sorted suitably this I think does what you ask.
Edit:
Also if you want to check both columns at once instead of going one at a time, you can join the two columns to make a proxy. i.e. Autofilling down =A1 & B1
So you get a third column containing
1a
2b
2c
etc.
Cuts the vlookups required in half :)
Sub ihopethishelps()
Dim last As Long
Dim r As Long
Range("B1").Select
Selection.End(xlDown).Select
last = ActiveCell.Row - 1
Range("C1").Select
For r = 0 To last
ActiveCell.Offset(r, 0).Value = _
"=IFERROR(IF(VLOOKUP(A" & r + 1 & ",Sheet2!A" & r + 1 & ":O" & last & ",2,FALSE)=B" & r + 1 & "," & Chr(34) & "Correct" & Chr(34) & "," & Chr(34) & "Wrong" & Chr(34) & ")," & Chr(34) & "Not Found" & Chr(34) & ")"
Next
End Sub

Is there a way to perform a cross join or Cartesian product in excel?

At the moment, I cannot use a typical database so am using excel temporarily. Any ideas?
The
You have 3 dimensions here: dim1 (ABC), dim2 (123), dim3 (XYZ).
Here is how you make a cartesian product of 2 dimensions using standard Excel and no VBA:
1) Plot dim1 vertically and dim2 horizontally. Concatenate dimension members on the intersections:
2) Unpivoting data. Launch pivot table wizard using ALT-D-P (don't hold ALT, press it once). Pick "Multiple consolidation ranges" --> create a single page.. --> Select all cells (including headers!) and add it to the list, press next.
3) Plot the resulting values vertically and disassemble the concatenated strings
Voila, you've got the cross join. If you need another dimension added, repeat this algorithm again.
Cheers,
Constantine.
Here is a very easy way to generate the Cartesian product of an arbitrary number of lists using Pivot tables:
https://chandoo.org/wp/generate-all-combinations-from-two-lists-excel/
The example is for two lists, but it works for any number of tables and/or columns.
Before creating the Pivot table, you need to convert your value lists to tables.
Using VBA, you can. Here is a small example:
Sub SqlSelectExample()
'list elements in col C not present in col B
Dim con As ADODB.Connection
Dim rs As ADODB.Recordset
Set con = New ADODB.Connection
con.Open "Driver={Microsoft Excel Driver (*.xls)};" & _
"DriverId=790;" & _
"Dbq=" & ThisWorkbook.FullName & ";" & _
"DefaultDir=" & ThisWorkbook.FullName & ";ReadOnly=False;"
Set rs = New ADODB.Recordset
rs.Open "select ccc.test3 from [Sheet1$] ccc left join [Sheet1$] bbb on ccc.test3 = bbb.test2 where bbb.test2 is null ", _
con, adOpenStatic, adLockOptimistic
Range("g10").CopyFromRecordset rs '-> returns values without match
rs.MoveLast
Debug.Print rs.RecordCount 'get the # records
rs.Close
Set rs = Nothing
Set con = Nothing
End Sub
Here's a way using Excel formulas:
| | A | B | C |
| -- | -------------- | -------------- | -------------- |
| 1 | | | |
| -- | -------------- | -------------- | -------------- |
| 2 | Table1_Column1 | Table2_Column1 | Table2_Column2 |
| -- | -------------- | -------------- | -------------- |
| 3 | A | 1 | X |
| -- | -------------- | -------------- | -------------- |
| 4 | B | 2 | Y |
| -- | -------------- | -------------- | -------------- |
| 5 | C | 3 | Z |
| -- | -------------- | -------------- | -------------- |
| 6 | | | |
| -- | -------------- | -------------- | -------------- |
| 7 | Col1 | Col2 | Col3 |
| -- | -------------- | -------------- | -------------- |
| 8 | = Formula1 | = Formula2 | = Formula3 |
| -- | -------------- | -------------- | -------------- |
| 9 | = Formula1 | = Formula2 | = Formula3 |
| -- | -------------- | -------------- | -------------- |
| 10 | = Formula1 | = Formula2 | = Formula3 |
| -- | -------------- | -------------- | -------------- |
| 11 | ... | ... | ... |
| -- | -------------- | -------------- | -------------- |
Formula1: IF(ROW() >= 8 + (3*3*3), "", INDIRECT(ADDRESS(3 + MOD(FLOOR(ROW() - 8)/(3*3), 3), 1)))
Formula2: IF(ROW() >= 8 + (3*3*3), "", INDIRECT(ADDRESS(3 + MOD(FLOOR(ROW() - 8)/(3) , 3), 2)))
Formula3: IF(ROW() >= 8 + (3*3*3), "", INDIRECT(ADDRESS(3 + MOD(FLOOR(ROW() - 8)/(1) , 3), 3)))
One* general formula to rule them all!
The result
The formula
MOD(CEILING.MATH([index]/PRODUCT([size of set 0]:[size of previous set]))-1,[size of current set])+1
This formula gives the index (ordered position) of each element in the set, where set i has a size of n_i. Thus if we have four sets the sizes would be [n_1,n_2,n_3,n_4].
Using that index one can just use the index function to pick whatever attribute from the set (imagine each set being a table with several columns one could use index([table of the set],[this result],[column number of attribute]).
Explanation
The two main components of the formula explained, the cycling component and the partitioning component.
Cycling component
=MOD([partitioning component]-1, [size of current set])+1
Cycles through all the possible values of the set.
The modulo function is required so the result will "go around" the size of the set, and never "out of bounds" of the possible values.
The -1 and +1 help us go from one-based numbering (our set indexes) to zero-based numbering (for the modulo operation).
Partitioning component
CEILING.MATH([index]/PRODUCT([size of set 0]:[size of previous set]):
Partitions the "cartesian index" in chunks giving each chunk an "name".
The "cartesian index" is just a numbering from 1 to the number of elements in the Cartesian Product (given by the product of the sizes of each set).
The "name" is just an increasing-by-chunk enumeration of the "cartesian index".
To have the same "name" for all indexes belonging to each chunk, we divide the "cartesian index" by the number of partitions and "ceil" it (kind of round up) the result.
The amount of partitions is the total size of the last cycle, since, for each previous result one requires to repeat it for each of this set's elements.
It so happens that the size of the previous result is the product of all the previous sets sizes (including the size of a set before the first so we can generalize, which we will call the "set 0" and will have a constant size of 1).
With screenshots
Set sizes
Prepared set sizes including the "Set0" one and the size of the Cartesian Product.
Here, the sizes of sets are:
"Set0": 1 in cell B2
"Set1": 2 in cell C2
"Set2": 5 in cell D2
"Set3": 3 in cell E2
Thus the size of the Cartesian product is 30 (2*5*3) in cell A2.
Results
Table structure _tbl_CartesianProduct with the following columns and their formulas:
Results:
Cartesian Index: =IF(ROW()-ROW(_tbl_CartesianProduct[[#Headers];[Cartesian Index]])<=$A$2;ROW()-ROW(_tbl_CartesianProduct[[#Headers];[Cartesian Index]]);NA())
concatenation: =TEXTJOIN("-";TRUE;_tbl_CartesianProduct[#[Index S1]:[Index S3]])
Index S1: =MOD(CEILING.MATH([#[Cartesian Index]]/PRODUCT($B$2:B$2))-1;C$2)+1
Index S2: =MOD(CEILING.MATH([#[Cartesian Index]]/PRODUCT($B$2:C$2))-1;D$2)+1
Index S3: =MOD(CEILING.MATH([#[Cartesian Index]]/PRODUCT($B$2:D$2))-1;E$2)+1
step "size of previous partition":
Size prev part S1: =PRODUCT($B$2:B$2)
Size prev part S2: =PRODUCT($B$2:C$2)
Size prev part S3: =PRODUCT($B$2:D$2)
step "Chunk name":
Chunk S1: =CEILING.MATH([#[Cartesian Index]]/[#[Size prev part S1]])
Chunk S2: =CEILING.MATH([#[Cartesian Index]]/[#[Size prev part S2]])
Chunk S3: =CEILING.MATH([#[Cartesian Index]]/[#[Size prev part S3]])
final step "Cycle through the set":
Cycle chunk in S1: =MOD([#[Chunk S1]]-1;C$2)+1
Cycle chunk in S2: =MOD([#[Chunk S2]]-1;D$2)+1
Cycle chunk in S3: =MOD([#[Chunk S3]]-1;E$2)+1
*: for the actual job of producing the Cartesian enumerations
A little bit code in PowerQuery could solve the problem:
let
Quelle = Excel.CurrentWorkbook(){[Name="tbl_Data"]}[Content],
AddColDim2 = Table.AddColumn(Quelle, "Dim2", each Quelle[Second_col]),
ExpandDim2 = Table.ExpandListColumn(AddColDim2, "Dim2"),
AddColDim3 = Table.AddColumn(ExpandDim2, "Dim3", each Quelle[Third_col]),
ExpandDim3 = Table.ExpandListColumn(AddColDim3, "Dim3"),
RemoveColumns = Table.SelectColumns(ExpandDim3,{"Dim1", "Dim2", "Dim3"})
in RemoveColumns
Try using a DAX CROSS JOIN. Read more at MSDN
You can use the expression CROSSJOIN(table1, table2) to create a cartesian product.

Get last value of series in Excel?

I have my excel sheet like this:
A | B
-----
0 | 2
0 | 3
0 | 4
0 | 5
0 | 6
0 | 7
1 | 8
1 | 9
1 | 10
1 | 11
1 | 12
2 | 13
2 | 14
...
How do I get the value of B for the last occurrence of each value in A?
The output is -
C | D
0 | 7
1 | 12
2 | 14
Is there an easy way of doing this? Thanks!
Assuming you already have 0, 1, 2 etc. listed in C1 down then use this formula In D1 copied down
=LOOKUP(C1,A:B)
Highlight the entire table.
On Data tab, in the Outline pane click the Subtotals button.
In the At change in... box, select A.
At the Use function box, select Max.
In the Add Subtotal to box make sure B has a check mark
Click OK.
Use the dashes to collapse each section.
That will avoid any programming or formulas and leave the data intact.
To get the last value in column A use this formula:
=INDEX(A:A, COUNTA(A:A), 1)
The following will get the last value in column B:
=INDEX(B:B, COUNTA(B:B), 1)
A sample using a macro:
Sub LastValueInB()
VarA = Range("A1").Value
VarB = Range("B1").Value
Output = ""
For i = 1 To 10
If Range("A" & i).Value = VarA Then
VarB = Range("B" & i).Value
Else
Output = Output + VarA & VarB & vbCrLf
VarA = Range("A" & i).Value
End If
Next i
MsgBox Output
End Sub

Merging variable rows within a selection individually

I have an Excel 2007 table which looks like this:
/| A | B | C | D
-+---------+----------+----------+----------+
1| Item1 | Info a | 1200 | sum(C1:C2)
2| | | 2130 |
3| Item2 | Info b | 2100 | sum(C3:C7)
5| | | 11 |
6| | | 12121 |
7| | | 123 |
8| Item3 | Info c | 213 | sum(C8:C10)
9| | | 233 |
10| | | 111 |
What I hope to do is that whenever I select the entire table (A1:C10 for the above example) and press <Ctrl> + <M>, the macro code will automatically merge the blank cells with the cell above them that contains text e.g. A1 to A2; A3 to A7 and so forth. The same goes for column B. For column D, after merging, it would also sum up all the items in column C. I could do the merging and summation manually, however it would take me quite a while so I've been looking into macros to make life easier.
I would like to emphasize that the number of rows to merge on each item is variable (Item 1 has only 2 rows - A1 and A2, Item 2 has 4, and so on.)
Is this possible to do in Excel VBA? Any help and comments are greatly appreciated.
If you have a large number of rows, avoid looping through the cells themselves, as this is quite slow. Instaed copy the cells values to a Variant array first.
Option Explicit
Sub zx()
Dim rngTable As Range
Dim vSrcData As Variant
Dim vDestData As Variant
Dim i1 As Long, i2 As Long, i3 As Long, i4 As Long
Set rngTable = Range("A1:D10")
vSrcData = rngTable
' vSrcData is now a two dimensional array of Variants
' set vDestData to an array of the right size to contain results
ReDim vDestData(1 To WorksheetFunction.CountA(rngTable.Columns(1)), _
1 To UBound(vSrcData, 2))
' keep track of row in Destination Data to store next result
i3 = LBound(vSrcData, 1)
' loop through the Source data
For i1 = 1 To UBound(vSrcData, 1) - 1
' sum the rows with blanks in clumn A
If vSrcData(i1, 1) <> "" Then
For i2 = i1 + 1 To UBound(vSrcData, 1)
If vSrcData(i2, 1) = "" Then
vSrcData(i1, 3) = vSrcData(i1, 3) + vSrcData(i2, 3)
Else
Exit For
End If
Next
' copy the result to Destination array
For i4 = 1 To UBound(vSrcData, 2)
vDestData(i3, i4) = vSrcData(i1, i4)
Next
i3 = i3 + 1
End If
Next
' delete original data
rngTable.ClearContents
' Adjust range to the size of results array
Set rngTable = rngTable.Cells(1, 1).Resize(UBound(vDestData, 1), _
UBound(vDestData, 2))
' put results in sheet
rngTable = vDestData
End Sub
Set Quick Key from Excel, Tools/Macros menu, Options