I have a sheet of columns.
I want to compare data in multiple columns, and return a flag in another column to indicate rows that are duplicates. I found a little code online which was meant for checking one column of data, and have so far been unsuccessful in being able to tweek it for multiple columns. The final code will need to look at specific columns which I will define later however for the moment say the sheet is as follows:
StaffNumber CallType
1 A
2 B
1 A
4 D
5 E
6 F
7 G
8 H
1 A
2 C
1 Z
6 P
The Col A is labelled Staff Number. Col B is labelled CallType. In Col C I want the flag to be entered against the row.
My Code is as follows:
Sub DuplicateIssue()
Dim last_StaffNumber As Long
Dim last_CallType As Long
Dim match_StaffNumber As Long
Dim match_CallType As Long
Dim StaffNumber As Long
Dim CallType As Long
last_StaffNumber = Range("A65000").End(xlUp).Row
last_CallType = Range("B65000").End(xlUp).Row
For StaffNumber = 1 To last_StaffNumber
For CallType = 1 To last_CallType
'checking if the Staff Number cell is having any item, skipping if it is blank.
If Cells(StaffNumber, 1) <> " " Then
'getting match index number for the value of the cell
match_StaffNumber = WorksheetFunction.Match(Cells(StaffNumber, 1), Range("A1:A" & last_StaffNumber), 0)
If Cells(CallType, 2) <> " " Then
match_CallType = WorksheetFunction.Match(Cells(CallType, 2), Range("B1:B" & last_CallType), 0)
'if the match index is not equals to current row number, then it is a duplicate value
If StaffNumber <> match_StaffNumber And CallType <> match_CallType Then
'Printing the label in the column C
Cells(StaffNumber, 3) = "Duplicate"
End If
End If
End If
Next
Next
End Sub
My problem is that only when Col 1 is a duplicate will the macro enter "Duplicate" into Col C, and it is not checking if the value of Col B is also the same.
Any Help would be much appreciated.
Try this code:
.
Option Explicit
Public Sub showDuplicateRows()
Const SHEET_NAME As String = "Sheet1"
Const LAST_COL As Long = 3 ' <<<<<<<<<<<<<<<<<< Update last column
Const FIRST_ROW As Long = 2
Const FIRST_COL As Long = 1
Const DUPE As String = "Duplicate"
Const CASE_SENSITIVE As Byte = 1 'Matches UPPER & lower
Dim includedColumns As Object
Set includedColumns = CreateObject("Scripting.Dictionary")
With includedColumns
.Add 1, "" ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< col 1 as dupe criteria
.Add 3, "" ' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< col 3 as dupe criteria
End With
Dim searchRng As Range
Dim memArr As Variant
Dim i As Long
Dim j As Long
Dim unique As String
Dim totalRows As Long
Dim totalCols As Long
Dim totalURCols As Long
Dim valDict As Object
Set valDict = CreateObject("Scripting.Dictionary")
If CASE_SENSITIVE = 1 Then
valDict.CompareMode = vbBinaryCompare
Else
valDict.CompareMode = vbTextCompare
End If
With ThisWorkbook.Sheets(SHEET_NAME)
totalRows = .UsedRange.Rows.Count 'get last used row on sheet
totalURCols = .UsedRange.Columns.Count 'get last used col on sheet
Set searchRng = .Range( _
.Cells(FIRST_ROW, FIRST_COL), _
.Cells(totalRows, LAST_COL) _
)
If LAST_COL < totalURCols Then
.Range( _
.Cells(FIRST_ROW, LAST_COL + 1), _
.Cells(FIRST_ROW, totalURCols) _
).EntireColumn.Delete 'delete any extra columns
End If
End With
memArr = searchRng.Resize(totalRows, LAST_COL + 1) 'entire range with data to mem
For i = 1 To totalRows 'each row, without the header
For j = 1 To LAST_COL 'each col
If includedColumns.exists(j) Then
unique = unique & searchRng(i, j) 'concatenate values on same row
End If
Next
If valDict.exists(unique) Then 'check if entire row exists
memArr(i, LAST_COL + 1) = DUPE 'if it does, flag it in last col
Else
valDict.Add Key:=unique, Item:=i 'else add it to the dictionary
End If
unique = vbNullString
Next
searchRng.Resize(totalRows, LAST_COL + 1) = memArr 'entire memory back to the sheet
End Sub
.
Result:
Related
I am not that much familiar in VBA code. I am looking to implement two scenarios using VBA code in excel.
Scenario 1: If the value in the "C" column contains specific text, then replace the corresponding values in the "A" column as below
If the value in C contains "abc" then A= "abc".
If the value in C contains "gec" then A= "GEC".
It should loop from the second row to last non-empty row
A
B
C
Two
abc-def
Thr
gec-vdg
Thr
abc-ghi
Expected Result:
A
B
C
abc
Two
abc-def
gec
Thr
gec-vdg
abc
Thr
abc-ghi
Scenario 2: If the value in the "B" column is "A", then replace all the "A" value in the B column as "Active". If the value in the "B" column is I", then replace all the I value in the B column as inactive.
It should loop from the second row to last non-empty row
A
B
C
abc
A
abc-def
gec
I
gec-vdg
abc
A
abc-ghi
Expected Result:
A
B
C
abc
Active
abc-def
gec
Inactive
gec-vdg
abc
Active
abc-ghi
I know that it is possible by using excel formulas. Wondering, how it can be implemented using vba code in excel.
Usually people on here won't just write code for you, this is more for helping you with your code when your stuck. However I've written one for you based on the information you have provided. I've assumed your cells in column C would always have the hyphen and you always want what's left of the hyphen. If there is no hyphen or the relevant cell in column C is empty then nothing will be put into the relevant cell in column A.
I've put in to turn off ScreenUpdating for the code as I don't know how many rows you have. If it's a lot and you have a lot going on, then we can also turn off Calculation and Events to speed it up more, or run it as an array if it's really slow but I suspect that it won't be an issue.
Paste this into your relevant sheet module and change the sheet name as well as the column that's finding the last row if C isn't the right one:
Sub UpdateCells()
Application.ScreenUpdating = False
Dim i As Long, lRow As Long, ws As Worksheet
Set ws = Sheets("Sheet1") 'Change Sheet1 to your sheet name
lRow = ws.Range("C" & Rows.Count).End(xlUp).Row 'Finds your last row using Column C
With ws
For i = 2 To lRow 'Loop from row 2 to last row
If .Range("B" & i) = "A" Then
.Range("B" & i) = "Active"
ElseIf .Range("B" & i) = "I" Then
.Range("B" & i) = "Inactive"
End If
If .Range("C" & i) <> "" Then
If InStr(.Range("C" & i), "-") > 0 Then 'If current row Column C contains hyphen
.Range("A" & i) = Left(.Range("C" & i), InStr(.Range("C" & i), "-") - 1)
End If
End If
Next i
End With
Application.ScreenUpdating = True
End Sub
Replace Values
Option Explicit
Sub replaceCustom()
' Define constants.
Const wsName As String = "Sheet1"
Const ColumnsAddress As String = "A:C"
Const FirstRow As Long = 2
Dim Contains As Variant: Contains = VBA.Array(3, 1) ' 0-read, 1-write
Const findContainsList As String = "abc,gec" ' read
Const replContainsList As String = "abc,gec" ' write
Dim Equals As Variant: Equals = VBA.Array(2, 2) ' 0-read, 1-write
Const findEqualsList As String = "A,I" ' read
Const replEqualsList As String = "Active,Inactive" ' write
Dim CompareMethod As VbCompareMethod: CompareMethod = vbTextCompare
' Define workbook.
Dim wb As Workbook: Set wb = ThisWorkbook ' Workbook containing this code.
' Define range.
Dim rng As Range
With wb.Worksheets(wsName).Columns(ColumnsAddress)
Set rng = .Resize(.Worksheet.Rows.Count - FirstRow + 1) _
.Offset(FirstRow - 1).Find( _
What:="*", _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious)
If rng Is Nothing Then
Exit Sub
End If
Set rng = .Resize(rng.Row - FirstRow + 1).Offset(FirstRow - 1)
End With
' Write values from range to array.
Dim Data As Variant: Data = rng.Value
' Write lists to arrays.
Dim findCo() As String: findCo = Split(findContainsList, ",")
Dim replCo() As String: replCo = Split(replContainsList, ",")
Dim findEq() As String: findEq = Split(findEqualsList, ",")
Dim replEq() As String: replEq = Split(replEqualsList, ",")
' Modify values in array.
Dim i As Long
Dim n As Long
For i = 1 To UBound(Data, 1)
For n = 0 To UBound(Contains)
If InStr(1, Data(i, Contains(0)), findCo(n), CompareMethod) > 0 Then
Data(i, Contains(1)) = replCo(n)
Exit For
End If
Next n
For n = 0 To UBound(Equals)
If StrComp(Data(i, Equals(0)), findEq(n), CompareMethod) = 0 Then
Data(i, Equals(1)) = replEq(n)
Exit For
End If
Next n
Next i
' Write values from array to range.
rng.Value = Data
End Sub
I need to display two columns A and B listed in a combobox with unique values. So if two rows have the same A but not the same B, it is not a duplicate, both column need to be duplicate. I found a code that list one column (A) with unique values but I don't know how to add the column B.
There's a picture of my data and how I want to display it in my ComboBox.
Here's the code:
Private Sub UserForm_Initialize()
Dim Cell As Range
Dim col As Variant
Dim Descending As Boolean
Dim Entries As Collection
Dim Items As Variant
Dim index As Long
Dim j As Long
Dim RngBeg As Range
Dim RngEnd As Range
Dim row As Long
Dim Sorted As Boolean
Dim temp As Variant
Dim test As Variant
Dim Wks As Worksheet
Set Wks = ThisWorkbook.Worksheets("Sheet1")
Set RngBeg = Wks.Range("A3")
col = RngBeg.Column
Set RngEnd = Wks.Cells(Rows.Count, col).End(xlUp)
Set Entries = New Collection
ReDim Items(0)
For row = RngBeg.row To RngEnd.row
Set Cell = Wks.Cells(row, col)
On Error Resume Next
test = Entries(Cell.Text)
If Err = 5 Then
Entries.Add index, Cell.Text
Items(index) = Cell.Text
index = index + 1
ReDim Preserve Items(index)
End If
On Error GoTo 0
Next row
index = index - 1
Descending = False
ReDim Preserve Items(index)
Do
Sorted = True
For j = 0 To index - 1
If Descending Xor StrComp(Items(j), Items(j + 1), vbTextCompare) = 1 Then
temp = Items(j + 1)
Items(j + 1) = Items(j)
Items(j) = temp
Sorted = False
End If
Next j
index = index - 1
Loop Until Sorted Or index < 1
ComboBox1.List = Items
End Sub
Any clue? Thanks!
Try this code, please. It assumes that unique definition means pairs of values from the two columns, on the same row, to be unique:
Sub UnicTwoValInTwoColumns()
Dim sh As Worksheet, arr As Variant, arrFin As Variant, countD As Long
Dim lastRow As Long, i As Long, j As Long, k As Long, boolDupl As Boolean
Set sh = ActiveSheet 'use here your sheet
'supposing that last row in column A:A is the same in column B:B
'If not, the last row for B:B will be calculated and then the higher will be chosen:
lastRow = sh.Range("A" & Rows.Count).End(xlUp).Row
ReDim arrFin(1 To 2, 1 To lastRow) 'redim the final array for maximum possible number of elements
arr = sh.Range("A3:B" & lastRow).value 'pun in array the range to be analized
k = 1 'initialize the first array element number
For i = 1 To UBound(arr, 1) 'iterate between the array elements
boolDupl = False 'initialize the variable proving that the pair of data already in arrFin
For j = 1 To k 'iterate between the arrFin elements in order to check for duplicates
If arr(i, 1) & arr(i, 2) = arrFin(1, j) & arrFin(2, j) Then
boolDupl = True: Exit For 'if a duplicate is found the loop is exited
End If
Next j
If Not boolDupl Then 'load the arrFin only if a duplicate has not been found
arrFin(1, k) = arr(i, 1): arrFin(2, k) = arr(i, 2)
k = k + 1 'increment the (real) array number of elements
End If
Next
ReDim Preserve arrFin(1 To 2, 1 To k - 1) 'redim array at the real dimension (preserving values)
With Me.ComboBox1
.ColumnCount = 2 'be sure that combo has 2 columns to receive values
.List = WorksheetFunction.Transpose(arrFin) 'fill the combo with the array elements
End With
End Sub
You can paste the code in the form Initialize event, or let the Sub like it is, copy it in the form module and only call it from the event in discussion. I would suggest you to proceed in this las way. If you have (or will have) something else in the event, it would be simpler to identify a problem if it occurs, I think,
Currently, I want to automate some annoying work in excel and need some help.
I have a huge report which has 200k+ rows and about 500 columns and my task is to find out which cells of a column are never used.
This was fairly easy and I managed it to create a script that works for that so far.
But now I want to distinguish between row types and return for each row type whether there are columns that are never used.
My problem is that I do not know how to iterate through the contents of a cell, so that if the row type changes my script will count the empty columns for the next row type.
I hope you get the idea and can help me. You do not have to give me the full code but maybe an idea of how I can get to the solution :)
This is the vba code I currently have and that gives me the correct solution but without distinguishing between the rowtypes
Public row As Long
Public rowMax As Long
Public startRow As Integer
Public materialType As String
Public filter As String
Public col As Integer
Public colMax As Integer
Public isUsed As Boolean
Sub bestimmeObFelderGenutzt()
With Sheets("Sheet1")
filter = "I"
startRow = 2
rowMax = Sheets("Sheet1").Cells(.Rows.Count, "F").End(xlUp).row
colMax = Sheets("Sheet1").Cells(1, .Columns.Count).End(xlToLeft).Column
materialType = Sheets("Sheet1").Range(filter & startRow).Value
For col = 1 To colMax
Sheets("Sheet2").Cells(1, col + 2).Value = Sheets("Sheet1").Cells(1, col).Value
Next col
For row = 2 To rowMax
Sheets("Sheet2").Range("A" & row).Value = Sheets("Sheet1").Range("A" & row).Value
Sheets("Sheet2").Range("B" & row).Value = Sheets("Sheet1").Range("I" & row).Value
For col = 1 To colMax
If IsEmpty(Sheets("Sheet1").Cells(row, col)) = False Then
isUsed = True
Sheets("Sheet2").Cells(row, col + 2).Value = 1
Else:
Sheets("Sheet2").Cells(row, col + 2).Value = 0
End If
Next col
Next row
End With
End Sub
Sub findeUngenutzteSpalten()
With Sheets("Sheet2")
rowMax = Sheets("Sheet2").Cells(.Rows.Count, "F").End(xlUp).row
colMax = Sheets("Sheet2").Cells(1, .Columns.Count).End(xlToLeft).Column
Sheets("Sheet3").Cells(1, 1).Value = "Spaltenüberschrift"
Sheets("Sheet3").Cells(1, 2).Value = "Jemals benutzt?"
For col = 3 To colMax
isUsed = False
For row = 2 To rowMax
If Sheets("Sheet2").Cells(row, col).Value = 1 Then
Sheets("Sheet3").Range("A" & col - 1).Value = Sheets("Sheet2").Cells(1, col).Value
Sheets("Sheet3").Range("B" & col - 1).Value = "Ja"
GoTo WeiterCol
Else:
If row = rowMax Then
Sheets("Sheet3").Range("A" & col - 1).Value = Sheets("Sheet2").Cells(1, col).Value
Sheets("Sheet3").Range("B" & col - 1).Value = "Nein"
Else:
GoTo WeiterRow
End If
End If
WeiterRow:
Next row
WeiterCol:
Next col
End With
End Sub
If I understood your task correctly this should work, copy to your module and read comments:
Sub FindUnusedColumnsPerRow()
Dim cellRow As range, cellColumn As range
Dim rowRange As range, columnRange As range
Dim rowsCount As Long, columnsCount As Long
Dim insertRow As Long
Dim listOfEmptyColumns()
Dim i As Long, j As Long
Dim arrayCheck As Integer
With Sheets("Sheet1") ' I assume that this is your sheet with materials where you want to find unused columns
rowsCount = .Cells(Rows.Count, 6).End(xlUp).row ' get last row
columnsCount = .Cells(1, Columns.Count).End(xlToLeft).Column ' get last column
For Each cellRow In range(.Cells(2, 1), .Cells(rowsCount, 1)) ' going through all rows - here I suppose that material type is in the 1-st column, 1-st row is a header and data starts from 2-d row
For Each cellColumn In range(.Cells(cellRow.row, 2), .Cells(cellRow.row, columnsCount)) ' for each row looking through all columns - I suppose that data starts from 2-d column
If cellColumn = "" Then ' if the cell is empty.
ReDim Preserve listOfEmptyColumns(i) ' expanding array when needed
listOfEmptyColumns(i) = cellColumn.Column ' adding column number to an array, you may change it to = .cells(1,cellColumn.Column) to put a header name instead of column number
i = i + 1 ' increment the counter
End If
Next
On Error Resume Next ' a small trick to check whether the array with column numbers is empty
arrayCheck = UBound(listOfEmptyColumns) ' if the array is empty - an #9 "Subscript out of range" exception will be thrown
If Err.Number = 0 Then ' error number is 0 - means that there was no error
With Sheets("Sheet2") ' I suppose this is the sheet to store results
insertRow = .Cells(Rows.Count, 1).End(xlUp).row + 1 ' find the row to insert
.Cells(insertRow, 1) = cellRow.Value ' put the type to 1-st column
j = 2 ' start filling the row of the type with numbers of empty columns
For i = 0 To UBound(listOfEmptyColumns) ' populating data from array
.Cells(insertRow, j) = listOfEmptyColumns(i)
j = j + 1
Next
End With
End If
Err.Clear ' clearing the error, because it still holding an error information (if it was thrown)
On Error GoTo 0 ' don't forget to switch on normal error handling
Erase listOfEmptyColumns ' reset array before next row as the data is stored on sheet2
i = 0 ' reset the counter for further use
Next
End With
End Sub
I have a spreadsheet with approx. 45,000 rows. Currently I am looping through a column and targeting any cells with a value of 0. Those row numbers get stored in an array. I am then looping through that array, and changing another cell based on the array value. I have 5000 rows with values that need to be reassigned, and it is taking over an hour to run that segment of the code (saving the row numbers to the array only takes a few seconds). Any ideas on how to get the code to run faster? Here is the code:
'Initialize array
Dim myArray() As Variant
Dim x As Long
'Looks for the last row on the "Dates" sheet
Dim lastRow As Long
With ThisWorkbook.Sheets("Dates")
lastRow = .Cells(.Rows.Count, "C").End(xlUp).Row + 1
End With
Dim i As Integer
i = 2
Dim uCounter As Integer
'Loop through all the dates on the "Dates" sheet
While i <= lastRow
'Each date has a counter next to it
uCounter = Worksheets("Dates").Range("B" & i).Value
Dim uDate As String
'Store the date as a string
uDate = Worksheets("Dates").Range("C" & i).Value
Dim startRow As Long, endRow As Long
'Finds the first and last instance of the date on the CERF Data page (45,000 rows)
With Sheets("CERF Data")
startRow = .Range("AN:AN").Find(what:=uDate, after:=.Range("AN1"), LookIn:=xlValues).Row
endRow = .Range("AN:AN").Find(what:=uDate, after:=.Range("AN1"), LookIn:=xlValues, searchdirection:=xlPrevious).Row
End With
Dim j As Long
For j = startRow To endRow
'If the cell in column BB is 0, and the counter is above 0 save row number to array, j being the variable representing row number
If Sheets("CERF Data").Range("BB" & j).Value = 0 And uCounter > 0 Then
'save row number to array
ReDim Preserve myArray(x)
myArray(x) = j
x = x + 1
'decrement counter by 1
uCounter = uCounter - 1
End If
If uCounter = 0 Then Exit For
Next j
i = i + 1
Wend
Dim y As Long
'Loop through the array and assign a value of 2 to all the rows in the array for column AS
For y = LBound(myArray) To UBound(myArray)
Sheets("CERF Data").Range("AS" & myArray(y)).Value = 2
Next y
Thanks!
Without more info this is what I can get you:
Just 1 loop through all the rows, once, checking both if the value on column BB = 0 and the date is within your range of dates:
Option Explicit
Sub Test()
Dim arr, i As Long, DictDates As Scripting.Dictionary
arr = ThisWorkbook.Sheets("CERF Data").UsedRange.Value
Set DictDates = New Scripting.Dictionary 'You need the Microsoft Scripting Runtime Reference for this to work
'Create a dictionary with all the dates you must check
With ThisWorkbook.Sheets("Dates")
LastRow = .Cells(.Rows.Count, 3).End(xlUp).Row
For i = 2 To LastRow
If Not DictDates.Exists(CDate(.Cells(i, 3))) Then DictDates.Add CDate(.Cells(i, 3)), 1
Next i
End With
'Only one loop through the whole array
For i = 1 To UBound(arr)
If arr(i, 54) = 0 And DictDates.Exists(CDate(arr(i, 40))) Then 'check your 2 criterias, date and value = 0
arr(i, 45) = 2 'input the value 2 on the column "AS"
End If
Next i
ThisWorkbook.Sheets("CERF Data").UsedRange.Value = arr
End Sub
I have values in column B separated by commas. I need to split them into new rows and keep the other data the same.
I have a variable number of rows.
I don't know how many values will be in the cells in Column B, so I need to loop over the array dynamically.
Example:
ColA ColB ColC ColD
Monday A,B,C Red Email
Output:
ColA ColB ColC ColD
Monday A Red Email
Monday B Red Email
Monday C Red Email
Have tried something like:
colArray = Split(ws.Cells(i, 2).Value, ", ")
For i = LBound(colArray) To UBound(colArray)
Rows.Insert(i)
Next i
Try this, you can easily adjust it to your actual sheet name and column to split.
Sub splitByColB()
Dim r As Range, i As Long, ar
Set r = Worksheets("Sheet1").Range("B999999").End(xlUp)
Do While r.row > 1
ar = Split(r.value, ",")
If UBound(ar) >= 0 Then r.value = ar(0)
For i = UBound(ar) To 1 Step -1
r.EntireRow.Copy
r.Offset(1).EntireRow.Insert
r.Offset(1).value = ar(i)
Next
Set r = r.Offset(-1)
Loop
End Sub
You can also just do it in place by using a Do loop instead of a For loop. The only real trick is to just manually update your row counter every time you insert a new row. The "static" columns that get copied are just a simple matter of caching the values and then writing them to the inserted rows:
Dim workingRow As Long
workingRow = 2
With ActiveSheet
Do While Not IsEmpty(.Cells(workingRow, 2).Value)
Dim values() As String
values = Split(.Cells(workingRow, 2).Value, ",")
If UBound(values) > 0 Then
Dim colA As Variant, colC As Variant, colD As Variant
colA = .Cells(workingRow, 1).Value
colC = .Cells(workingRow, 3).Value
colD = .Cells(workingRow, 4).Value
For i = LBound(values) To UBound(values)
If i > 0 Then
.Rows(workingRow).Insert xlDown
End If
.Cells(workingRow, 1).Value = colA
.Cells(workingRow, 2).Value = values(i)
.Cells(workingRow, 3).Value = colC
.Cells(workingRow, 4).Value = colD
workingRow = workingRow + 1
Next
Else
workingRow = workingRow + 1
End If
Loop
End With
This will do what you want.
Option Explicit
Const ANALYSIS_ROW As String = "B"
Const DATA_START_ROW As Long = 1
Sub ReplicateData()
Dim iRow As Long
Dim lastrow As Long
Dim ws As Worksheet
Dim iSplit() As String
Dim iIndex As Long
Dim iSize As Long
'Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
With ThisWorkbook
.Worksheets("Sheet4").Copy After:=.Worksheets("Sheet4")
Set ws = ActiveSheet
End With
With ws
lastrow = .Cells(.Rows.Count, ANALYSIS_ROW).End(xlUp).Row
End With
For iRow = lastrow To DATA_START_ROW Step -1
iSplit = Split(ws.Cells(iRow, ANALYSIS_ROW).Value2, ",")
iSize = UBound(iSplit) - LBound(iSplit) + 1
If iSize = 1 Then GoTo Continue
ws.Rows(iRow).Copy
ws.Rows(iRow).Resize(iSize - 1).Insert
For iIndex = LBound(iSplit) To UBound(iSplit)
ws.Cells(iRow, ANALYSIS_ROW).Offset(iIndex).Value2 = iSplit(iIndex)
Next iIndex
Continue:
Next iRow
Application.CutCopyMode = False
Application.Calculation = xlCalculationAutomatic
'Application.ScreenUpdating = True
End Sub
A formula solution is close to your requirement.
Cell G1 is the delimiter. In this case a comma.
Helper E1:=SUM(E1,LEN(B1)-LEN(SUBSTITUTE(B1,$H$1,"")))+1
You must fill the above formula one row more.
A8:=a1
Fill this formula to the right.
A9:=LOOKUP(ROW(1:1),$E:$E,A:A)&""
Fill this formula to the right and then down.
B9:=MID($H$1&LOOKUP(ROW(A1),E:E,B:B)&$H$1,FIND("艹",SUBSTITUTE($H$1&LOOKUP(ROW(A1),E:E,B:B)&$H$1,$H$1,"艹",ROW(A2)-LOOKUP(ROW(A1),E:E)))+1,FIND("艹",SUBSTITUTE($H$1&LOOKUP(ROW(A1),E:E,B:B)&$H$1,$H$1,"艹",ROW(A2)-LOOKUP(ROW(A1),E:E)+1))-FIND("艹",SUBSTITUTE($H$1&LOOKUP(ROW(A1),E:E,B:B)&$H$1,$H$1,"艹",ROW(A2)-LOOKUP(ROW(A1),E:E)))-1)&""
Fill down.
Bug:
Numbers will be converted to Text. Of course you can remove the &"" at the end of the formula, but blank cells will be filled with 0.
Given #A.S.H.'s excellent and brief answer, the VBA function below might be a bit of an overkill, but it will hopefully be of some help to someone looking for a more "generic" solution. This method makes sure not to modify the cells to the left, to the right, or above the table of data, in case the table does not start in A1 or in case there is other data on the sheet besides the table. It also avoids copying and inserting entire rows, and it allows you to specify a separator other than a comma.
This function happens to have similarities to #ryguy72's procedure, but it does not rely on the clipboard.
Function SplitRows(ByRef dataRng As Range, ByVal splitCol As Long, ByVal splitSep As String, _
Optional ByVal idCol As Long = 0) As Boolean
SplitRows = True
Dim oldUpd As Variant: oldUpd = Application.ScreenUpdating
Dim oldCal As Variant: oldCal = Application.Calculation
On Error GoTo err_sub
'Modify application settings for the sake of speed
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
'Get the current number of data rows
Dim rowCount As Long: rowCount = dataRng.Rows.Count
'If an ID column is specified, use it to determine where the table ends by finding the first row
' with no data in that column
If idCol > 0 Then
With dataRng
rowCount = .Offset(, idCol - 1).Resize(, 1).End(xlDown).Row - .Row + 1
End With
End If
Dim splitArr() As String
Dim splitLb As Long, splitUb As Long, splitI As Long
Dim editedRowRng As Range
'Loop through the data rows to split them as needed
Dim r As Long: r = 0
Do While r < rowCount
r = r + 1
'Split the string in the specified column
splitArr = Split(dataRng.Cells(r, splitCol).Value & "", splitSep)
splitLb = LBound(splitArr)
splitUb = UBound(splitArr)
'If the string was not split into more than 1 item, skip this row
If splitUb <= splitLb Then GoTo splitRows_Continue
'Replace the unsplit string with the first item from the split
Set editedRowRng = dataRng.Resize(1).Offset(r - 1)
editedRowRng.Cells(1, splitCol).Value = splitArr(splitLb)
'Create the new rows
For splitI = splitLb + 1 To splitUb
editedRowRng.Offset(1).Insert 'Add a new blank row
Set editedRowRng = editedRowRng.Offset(1) 'Move down to the next row
editedRowRng.Offset(-1).Copy Destination:=editedRowRng 'Copy the preceding row to the new row
editedRowRng.Cells(1, splitCol).Value = splitArr(splitI) 'Place the next item from the split string
'Account for the new row in the counters
r = r + 1
rowCount = rowCount + 1
Next
splitRows_Continue:
Loop
exit_sub:
On Error Resume Next
'Resize the original data range to reflect the new, full data range
If rowCount <> dataRng.Rows.Count Then Set dataRng = dataRng.Resize(rowCount)
'Restore the application settings
If Application.ScreenUpdating <> oldUpd Then Application.ScreenUpdating = oldUpd
If Application.Calculation <> oldCal Then Application.Calculation = oldCal
Exit Function
err_sub:
SplitRows = False
Resume exit_sub
End Function
Function input and output
To use the above function, you would specify
the range containing the rows of data (excluding the header)
the (relative) number of the column within the range with the string to split
the separator in the string to split
the optional (relative) number of the "ID" column within the range (if a number >=1 is provided, the first row with no data in this column will be taken as the last row of data)
The range object passed in the first argument will be modified by the function to reflect the range of all the new data rows (including all inserted rows). The function returns True if no errors were encountered, and False otherwise.
Examples
For the range illustrated in the original question, the call would look like this:
SplitRows Range("A2:C2"), 2, ","
If the same table started in F5 instead of A1, and if the data in column G (i.e. the data that would fall in column B if the table started in A1) was separated by Alt-Enters instead of commas, the call would look like this:
SplitRows Range("F6:H6"), 2, vbLf
If the table contained the row header plus 10 rows of data (instead of 1), and if it started in F5 again, the call would look like this:
SplitRows Range("F6:H15"), 2, vbLf
If there was no certainty about the number of rows, but we knew that all the valid rows are contiguous and always have a value in column H (i.e. the 3rd column in the range), the call could look something like this:
SplitRows Range("F6:H1048576"), 2, vbLf, 3
In Excel 95 or lower, you would have to change "1048576" to "16384", and in Excel 97-2003, to "65536".