Split a cell and insert its content one cell above the other - vba

I am trying to write a VBA command in order to split a cell content and insert everything it contains, one cell above the other.
Sub SplitInsert()
Dim Cell As Variant
Dim Cell1 As Variant
Dim i As Integer
'Input column is on column A that I manually select'
'Then I press plau'
For Each Cell In Selection
'I split the current selected cell into a variant tab'
Cell1 = Split(Cell.Value)
'Then I do a second loop to insert every Cell1 values'
'one after the other in column B'
For i = 0 To UBound(Cell1)
'I don't know how to insert and shift down just a cell,'
'and not a row or a column'
Cells(2, 1).Insert '....' shift:=xlShiftDown
Next
Next Cell
End Sub
Input:
Desmond Hume-Jack Shepard
Kate Austen
John Locke-James Ford-Hugo Reyes
Would become
Output:
Hugo Reyes
James Ford
John Locke
Kate Austen
Jack Shepard
Desmond Hume
Thank you for your help :)

Sub Macro2()
Dim Cell As Range
Dim Cell1 As Variant
Dim i As Integer
'Input column is on column A that I manually select'
'Then I press plau'
For Each Cell In Selection
'I split the current selected cell into a variant tab'
Cell1 = Split(Cell.Value, "-")
'Then I do a second loop to insert every Cell1 values'
'one after the other in column B'
For i = 0 To UBound(Cell1)
'I don't know how to insert and shift down just a cell,'
'and not a row or a column'
Cells(1, 2).Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
Cells(1, 2).Value = Cell1(i)
Next i
Next Cell
End Sub
Try it and adap it to your needs. I made some changes yo your code, but you were on the right way.
Changed cell from Variant to Range
In the line Cell1 = Split(Cell.Value) you forgot the second
argument of that method. I added it like Cell1 = Split(Cell.Value,
"-")
And finally used Cells(1, 2).Value = Cell1(i) to call the array
value.

I didn't get exactly what you meant by "one cell above the other". So maybe Foxfire And Burns And Burns answered it how you wanted it to be.
My code would insert the result in B and insert a line to have a structured view in output. I also changed some things in your code and tried to comment behind the code for a better understanding of what it does.
Sub SplitInsert()
Dim Cell As Variant
Dim Cell1 As Variant
Dim i As Integer, j As Integer
Dim rng As Range
Set rng = Selection ' get selection range
j = Selection.Row ' get first selected row
For Each Cell In rng ' perform for each on every cell in range
Cell1 = Split(Cell.Value, "-") ' added separator (I assume it's what you'd want to split?)
For i = 0 To UBound(Cell1)
If i > 0 Then Rows(j).Insert ' only insert line if it's not the first value
Cells(j, 2).Value = Cell1(i) ' insert value in B
j = j + 1 ' increase row counter
Next i
Next Cell
End Sub

It takes a lot of time to read/write to/from the worksheet. Not a problem for a small list, but can be a problem for a large list.
The following code avoids that
Read the source data into a variant array
split each item and enter, sequentially, into a collection object
create a results array and populate from the collection in reverse order
write the results array back to the worksheet
Option Explicit
Sub SplitNames()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes() As Variant
Dim cNames As Collection
Dim V As Variant
Dim I As Long, J As Long
'Set results and source worksheets and ranges
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet1")
Set rRes = wsRes.Cells(1, 5)
'read source data into array
'you could use vSrc=Selection instead of determining the range as below
'the code below assumes the data is in column A starting at A1
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp))
End With
'split the names and read them into collection
Set cNames = New Collection
For I = 1 To UBound(vSrc, 1)
V = Split(vSrc(I, 1), "-")
For J = 0 To UBound(V)
cNames.Add V(J)
Next J
Next I
'create results array in reverse order
ReDim vRes(1 To cNames.Count, 1 To 1)
For I = 1 To cNames.Count
vRes(cNames.Count + 1 - I, 1) = cNames(I)
Next I
'write the results
Set rRes = rRes.Resize(rowsize:=UBound(vRes, 1))
With rRes
.EntireColumn.Clear
.Value = vRes
.EntireColumn.AutoFit
End With
End Sub

Related

Get Filtered records into Array Variant without looping VBA

I have 10 records in excel of which i have edited 3rd and 7th records and placing a flag/string "modified" in certain column belongs to same rows to filter while processing
Below is the code that i am working with which is fetching only the first record(3rd) and not the 7th record into array using VBA
Dim RecordsArray() As Variant
Set sht = ThisWorkbook.Sheets("RMData")
sht.Range("M1:M100").AutoFilter Field:=1, Criteria1:="Modified"
sht.Range("A2:A100").Rows.SpecialCells (xlCellTypeVisible)
col = [a2].CurrentRegion.Columns.count
lw = [a2].End(xlDown).Row
RecordsArray = Range(Cells(2, 1), Cells(lw,col)).SpecialCells(xlCellTypeVisible)
Idea is I want to get those two records without looping and searching for
"Modified" string for the edited row
When reading a Filtered Range, most likely there will be splits ranges, the rows will not be continuous, so you need to loop through the Areas of the Filtered Range.
Also, you might have a few Rows in each Area, so you should loop through the Area.Rows.
More detailed comments in my code below.
Code
Option Explicit
Sub Populated2DArrayfromFilterRange()
Dim RecordsArray() As Variant
Dim sht As Worksheet
Dim col As Long, lw As Long, i As Long
Dim FiltRng As Range, myArea As Range, myRow As Range
ReDim RecordsArray(0 To 1000) ' redim size of array to high number >> will optimize later
' set the worksheet object
Set sht = ThisWorkbook.Sheets("RMData")
i = 0 ' reset array element index
' use With statement to fully qualify all Range and Cells objects nested inside
With sht
.Range("M1:M100").AutoFilter Field:=1, Criteria1:="Modified"
.Range("A2:A100").Rows.SpecialCells (xlCellTypeVisible)
col = .Range("A2").CurrentRegion.Columns.Count
lw = .Range("A2").End(xlDown).Row
' set the filtered range
Set FiltRng = .Range(.Cells(2, 1), .Cells(lw, col)).SpecialCells(xlCellTypeVisible)
' Debug.Print FiltRng.Address(0, 0)
For Each myArea In FiltRng.Areas ' <-- loop through areas
For Each myRow In myArea.Rows ' <-- loop through rows in area
RecordsArray(i) = Application.Transpose(Application.Transpose(myRow))
i = i + 1 ' raise array index by 1
Next myRow
Next myArea
ReDim Preserve RecordsArray(0 To i - 1) ' optimize array size to actual populated size
End With
End Sub
If you have a hidden row in the middle, then .SpecialCells(xlCellTypeVisible) will return multiple Areas. Assigning a range to an Array only assigns the first Area. (At also always makes the array 2D)
Instead of looping & searching for "Modified", you could just loop For Each cell in the SpecialCells range and assign that to the array instead - if you plan was "no loops at all" then this is not what you want. (But, I would then have to ask you "why not?"!)
Dim RecordsArray() As Variant, rFiltered As Range, rCell As Range, lCount As Long
Set sht = ThisWorkbook.Sheets("RMData")
sht.Range("M1:M100").AutoFilter Field:=1, Criteria1:="Modified"
sht.Range("A2:A100").Rows.SpecialCells (xlCellTypeVisible)
col = [a2].CurrentRegion.Columns.Count 'This will act on ActiveSheet, not sht - is that intended?
lw = [a2].End(xlDown).Row 'In case of gaps, would "lw=sht.Cells(sht.Rows.Count,1).End(xlUp).Row" be better?
'RecordsArray = Range(Cells(2, 1), Cells(lw, col)).SpecialCells(xlCellTypeVisible)
Set rFiltered = Range(Cells(2, 1), Cells(lw, col)).SpecialCells(xlCellTypeVisible)
ReDim RecordsArray(1 To rFiltered.Cells.Count, 1) 'Mimic default assignment
lCount = 1
For Each rCell In rFiltered
RecordsArray(lCount, 1) = rCell.Value
lCount = lCount + 1
Next rTMP
Set rCell = Nothing
Set rFiltered = Nothing
If you want to avoid dealing with the visible areas mentioned already, you can try something like this
Option Explicit
Public Sub CopyVisibleToArray()
Dim recordsArray As Variant, ws As Worksheet, nextAvailable As Range
Set ws = ThisWorkbook.Worksheets("RMData")
Set nextAvailable = ws.Cells(ws.Rows.Count, "A").End(xlUp).Offset(2)
With ws.Range("M1:M100")
Application.ScreenUpdating = False
.AutoFilter Field:=1, Criteria1:="Modified"
If .Rows.SpecialCells(xlCellTypeVisible).Cells.Count > 1 Then
'copy - paste visibles in col A, under all data
ws.UsedRange.Columns("A:M").SpecialCells(xlCellTypeVisible).Copy nextAvailable
Set nextAvailable = nextAvailable.Offset(1)
nextAvailable.Offset(-1).EntireRow.Delete 'Delete the (visible) header
recordsArray = nextAvailable.CurrentRegion 'Get the cells as array
nextAvailable.CurrentRegion.EntireRow.Delete 'Delete the temporary range
End If
.AutoFilter
Application.ScreenUpdating = True
End With
End Sub
To copy just column A to array use this: ws.UsedRange.Columns("A")
To copy columns A to M use this: ws.UsedRange.Columns("A:M")

Assigning values to array vba

I don't have experience using arrays in VBA and I got lost. What I try to do is the following:
In the column A I have ~15 strings (number is not fixed sometimes it is more sometimes less)
I remove duplicates and then for each name in the column A I would like to create separate sheet in the file.
I created an array to which I tried to assign each name from A with this loop:
Sub assigningvalues()
Dim i As Integer
Dim myArray(20) As Variant
Dim finalrow As Long
ActiveSheet.Range("A1", Range("A1").End(xlDown)).RemoveDuplicates Columns:=Array(1)
finalrow = ActiveSheet.Cells(ActiveSheet.Rows.Count, "A").End(xlDown).Row
'For i = 2 To finalrow -> I get overflow error when I use this range
For i = 2 To Cells(20, 1)
myArray(i) = Cells(i, 1).Value
Next i
'I check with the lines below if values were assigned
Cells(2, 4).Value = myArray(4)
Cells(3, 4).Value = myArray(2)
End Sub
Nevertheless values from the cells to do not assign to the array
Moreover when I try to use finalrow as range for the loop I get overflow error (It is not a big problem as there are workarounds, although it would be nice to know what I've done wrong)
Try the code below:
Option Explicit
Sub assigningvalues()
Dim i As Long
Dim myArray(20) As Variant
Dim FinalRow As Long
Dim Sht As Worksheet
Set Sht = ThisWorkbook.Sheets("Sheet1") ' modify "Sheet1" to your sheet's name
With Sht
.Range("A1", .Range("A1").End(xlDown)).RemoveDuplicates Columns:=Array(1)
FinalRow = .Cells(.Rows.Count, "A").End(xlUp).Row ' get last row in column "A"
For i = 2 To FinalRow
myArray(i) = Cells(i, 1).Value
Next i
'I check with the lines below if values were assigned
.Cells(2, 4).Value = myArray(4)
.Cells(3, 4).Value = myArray(2)
End With
End Sub
Note: you can read the contents of the Range to a 1-D Array without a For loop, using Application.Transpose, you need to change the line you define it to:
Dim myArray As Variant
and populate the entire array using:
myArray = Application.Transpose(.Range("A2:A" & FinalRow))
Try the code below:
Sub assigningvalues()
Dim myArray As Variant
ActiveSheet.Range("A1", Range("A1").End(xlDown)).RemoveDuplicates Columns:=Array(1)
myArray = ActiveSheet.Range("A1", Range("A1").End(xlDown))
For Each element In myArray
ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count)).Name = element
Next element
End Sub
NOTES: The problem with your above code was, that
ActiveSheet.Cells(ActiveSheet.Rows.Count, "A").End(xlDown).Row
returned the absolut number of rows in the sheet, not the used ones. Since your array has length 20 and the sheet about 1 Mio. rows, you have an overflow. you can check this by using
Debug.Print ActiveSheet.Cells(ActiveSheet.Rows.Count, "A").End(xlDown).Row
In the above code, after you remove dublicates, you again go down from A1 to the end and save the range to an array. The array myArray now contains all the cell values in your reduced range.
Now you loop over the elements with
For Each element in myArray
and create a new sheet with Workbook.Sheets.Add and assign the name my setting Sheets(index).name = element
The above code should work for you. Few remarks:
Instead of using "ActiveSheet", ThisWorkbook, etc. You should always start a Sub with this:
Dim wb As Workbook
Set wb = ThisWorkbook 'for the workbook containing the code
Set wb = Workbooks(workbookName) 'to reference an other Workbook
'And for all the sheets you are using
Dim ws As Worksheet
Set ws = wb.Sheets(sheetName) 'this way you avoid problems with multiple
workbooks that are open and active or
unactive sheets.

Row increment in paste reference

As below code, the input fields are B3, C18, C20, C22, C24. (Fixed input fields)
These data going to paste starting from B41:F41.
Problem is, how do I make the increment of the output reference B41:F41 as row +1 each time the macro is being used? Consider as, if there are data in B41:F41, then the paste range would be B42:F42 and so on.
Private Sub CommandButton2_Click()
Range("B3").Copy Range("C41")
Range("C18").Copy Range("B41")
Range("C20").Copy Range("D41")
Range("C22").Copy Range("E41")
Range("C24").Copy Range("F41")
If there will be no empty values copied to ColB then:
Private Sub CommandButton2_Click()
Dim sht As WorkSheet
Set sht = ActiveSheet
With sht.Cells(sht.Rows.Count, 2).End(xlUp).Offset(1, 0).EntireRow
sht.Range("B3").Copy .Cells(3)
sht.Range("C18").Copy .Cells(2)
sht.Range("C20").Copy .Cells(4)
sht.Range("C22").Copy .Cells(5)
sht.Range("C24").Copy .Cells(6)
End With
I suggest transfering your data to an array first, then transfering this array to the required section of the worksheet.
Sub Copy_Paste_Macro()
Dim CopyRange As Range, c As Range
Dim HoldArray() As Variant
Dim n As Long, i As Long
With Worksheets("Sheet1")
'Define Non-Contiguous range
Set CopyRange = Range("B3, C18, C20, C22, C24")
'Count of cells in range
n = CopyRange.Cells.Count
'Resize the array to hold the data
ReDim HoldArray(1 To n)
n = 1
'Store the values from that range into array
For Each c In CopyRange.Cells
HoldArray(n) = c.Value
n = n + 1
Next c
End With
'Paste array as contiguous range
If Worksheets("Sheet1").Range("B41") = "" Then
Worksheets("Sheet1").Range("B41").Resize(1, UBound(HoldArray)).Value = HoldArray
Else
Range("B" & Rows.Count).End(xlUp).Offset(1, 0).Resize(1, UBound(HoldArray)).Value = HoldArray
End If
End Sub
you could
gather input values in an array
write them in one shot
like follows:
Option Explicit
Private Sub CommandButton2_Click()
With Worksheets("SheetName") '<--| change "SheetName" to your actual sheet name
.Cells(WorksheetFunction.Max(41, .Cells(.Rows.COUNT, 2).End(xlUp).Offset(1).row), 2).Resize(, 5) = GetValues(.Range("C18,B3,C20,C22,C24")) '<--| list input cells addresses in wanted output order
End With
... other code
End Sub
Function GetValues(rng As Range) As Variant
Dim cell As Range
Dim iCell As Long
ReDim vals(1 To rng.COUNT) As Variant
For Each cell In rng
iCell = iCell + 1
vals(iCell) = cell.Value
Next cell
GetValues = vals
End Function

excel vba convert string to range

I am trying to run a macro on 3 different ranges, one after another. Once the range is selected, the code works just fine (where variables F and L are defined). I would like to set r1-r3 as Ranges I need and then use a string variable to concatenate the range numbers together. This code works, but doesn't provide the starting and ending row number in the range selected. This is vital because it tells the "TableCalc" macro when to start and stop the code. I would then like to move on to the next range. Thanks for your help.
Sub TestRangeBC()
WS.Select
Dim r1 As Range
Dim r2 As Range
Dim r3 As Range
Dim rngx As String
Dim num As Integer
Dim rng As Range
Set r1 = WS.Range("ONE")
Set r2 = WS.Range("TWO")
Set r3 = WS.Range("THREE")
For num = 1 To 3
rngx = "r" & num
Set rng = Range(rngx)
Dim F As Integer
Dim L As Integer
F = rng.Row + 1
L = rng.Row + rng.Rows.Count - 2
Cells(F, 8).Select
Do While Cells(F, 8) <> "" And ActiveCell.Row <= L
'INSERT SITUATIONAL MACRO
Call TableCalc
WS.Select
ActiveCell.Offset(1, 0).Select
Loop
Next num
End Sub
This is not the answer (as part of your code and what you are trying to achieve is unclear yet), but it is a "cleaner" and more efficient way to code what you have in your original post.
Option Explicit
Dim WS As Worksheet
Your original Sub shorten:
Sub TestRangeBC()
' chanhe WS to your Sheet name
Set WS = Sheets("Sheet1")
Call ActiveRange("ONE")
Call ActiveRange("TWO")
Call ActiveRange("THREE")
End Sub
This Sub gets the Name of the Named Range (you set in your workbook) as a String, and sets the Range accordingly.
Sub ActiveRange(RangeName As String)
Dim Rng As Range
Dim F As Integer
Dim L As Integer
Dim lRow As Long
With WS
Set Rng = .Range(RangeName)
' just for debug purpose >> to ensure the right Range was passed and set
Debug.Print Rng.Address
F = Rng.Row + 1
L = Rng.Row + Rng.Rows.Count - 2
lRow = F
' what you are trying to achieve in this loop is beyond me
Do While .Cells(F, 8) <> "" And .Cells(lRow, 8).Row <= L
Debug.Print .Cells(lRow, 8).Address
'INSERT SITUATIONAL MACRO
' Call TableCalc
' not sure you need to select WS sheet again
WS.Select
lRow = lRow + 1
Loop
End With
End Sub
What are you trying to test in the loop below, what are the criteria of staying in the loop ?
Do While Cells(F, 8) <> "" And ActiveCell.Row <= L
it's really hard to tell what you may want to do
but may be what follows can help you clarifying and (hopefully) doing it!
first off, you can't "combine" variable names
So I'd go with an array of named ranges names (i.e. String array) to be filled by means of a specific sub:
Function GetRanges() As String()
Dim ranges(1 To 3) As String
ranges(1) = "ONE"
ranges(2) = "TWO"
ranges(3) = "THREE"
GetRanges = ranges
End Function
so that you can clean up your "main" sub code and keep only more relevant code there:
Sub TestRangeBC()
Dim r As Variant
Dim ws As Worksheet
Set ws = Worksheets("Ranges") '<--| change "Ranges" to your actual worksheet name
For Each r In GetRanges() '<--| loop through all ranges names
DoIt ws, CStr(r) '<--| call the range name processing routine passing worksheet and its named range name
Next r
End Sub
the "main" sub loops through the named ranges array directly collected from GetRanges() and calls DoIt() to actually process the current one:
Sub DoIt(ws As Worksheet, rangeName As String)
Dim cell As Range
Dim iRow As Long
With ws.Range(rangeName) '<--| reference the passed name passed worksheet named range
For iRow = .Rows(2).Row To .Rows(.Rows.Count - 2).Row '<--| loop through its "inner" rows (i.e. off 1st and last rows)
Set cell = ws.Cells(iRow, 8) '<--| get current row corresponding cell in column "F"
If cell.value = "" Then Exit For '<--| exit at first blank column "F" corresponding cell
TableCalc cell '<-- call TableCalc passing the 'valid' cell as its parameter
Next iRow
End With
End Sub

Run-time error 1004 Application-defined or object defined error

I have looked through the other posts about this and have tried adapted the strategies that were recommend by using Set ActiveWorkbook and Set Active Worksheet and I still get the same error. I hope another set of eyes can help out as I am still very new to VBA and I am not all that comfortable with it yet.
Basically the idea is to copy the cells from column f to column j as values as long as the cells of F do not match the cells of J. I get the row count of column E and use that as my count in the for loop.
Code is here:
Private Sub CalculateRewards_Click()
CopyPaste
End Sub
Sub CopyPaste()
Dim n As Integer
Dim i As Integer
n = Sheets("Calculate").Range("E:E").Cells.SpecialCells(xlCellTypeConstants).Count
i = n
For Counter = 1 To n
Set curCell = Sheets("Calculate").Range("F2:F" &i)
If "$F" &i <> "$J" &i Then
Sheets("Calculate").Range("$F:$F" &i).Copy
Sheets("Calculate").Range("$J:$J" &i).PasteSpecial (xlPasteValues)
Application.CutCopyMode = False
End If
i = i + 1
Next Counter
End Sub
Thanks for the help
Also Edit:
Link to Excel Sheet that has a before page, after first transaction sheet ,and a after second transaction sheet: https://www.dropbox.com/s/n2mn0zyrtoscjin/Rewards.xlsm
CHange this:
Set curCell = Sheets("Calculate").Range("F2:F" &i)
If "$F" &i <> "$J" &i Then
Sheets("Calculate").Range("$F:$F" &i).Copy
Sheets("Calculate").Range("$J:$J" &i).PasteSpecial (xlPasteValues)
Application.CutCopyMode = False
End If
To this:
Set curCell = Sheets("Calculate").Range("F2:F" & i)
If curCell <> Sheets("Calculate").Range("$J" & i) Then
Sheets("Calculate").Range("$J:$J" &i).Value = curCell.Value
End If
May need to do some more teaking as I notice you're working with SpecialCells which essentially filters the range, so iterating For i = 1 to n... probably does not work. Maybe something like:
Dim rngCalc as Range
Set rngCalc = Sheets("Calculate").Range("E:E").Cells.SpecialCells(xlCellTypeConstants)
For each curCell in rngCalc.Cells
If curCell <> curCell.Offset(0, 4) Then
curCell.Offset(0, 4).Value = curCell.Value
End If
Next
EDIT: this sub will calculate the points for the last transaction (identified as the furthest-right column containing transactions) and write them down in column C.
Option Explicit
Sub UpdateCurrentPurchase()
Dim CalcSheet As Worksheet
Dim LastTransRange As Range, TargetRange As Range
Dim LastTransCol As Long, LastTransRow As Long
Dim PurchaseArray() As Variant
Dim Points As Long, Index As Long
'set references up-front
Set CalcSheet = ThisWorkbook.Worksheets("Calculate")
With CalcSheet
LastTransCol = .Cells(2, .Columns.Count).End(xlToLeft).Column '<~ find the last column
LastTransRow = .Cells(.Rows.Count, LastTransCol).End(xlUp).Row
Set LastTransRange = .Range(.Cells(2, LastTransCol), .Cells(LastTransRow, LastTransCol))
Set TargetRange = .Range(.Cells(2, 6), .Cells(LastTransRow, 6)) '<~ column F is the Current Purchase Col
LastTransRange.Copy Destination:=TargetRange '<~ copy last transactions to Current Purchase Col
End With
'pull purchases into a variant array
PurchaseArray = TargetRange
'calculate points
For Index = 1 To LastTransRow
Points = Int(PurchaseArray(Index, 1) / 10) '<~ calculate points
CalcSheet.Cells(Index + 1, 3) = Points '<~ write out the points amount in col C
Next Index
End Sub
ORIGINAL RESPONSE: I think the below will get you where you're going. That being said, it seems like simply overwriting column J with column F (as values) might be the fastest way to an acceptable answer, so if that's the case we can re-work this code to be much quicker using Range objects.
Option Explicit
Private Sub CalculateRewards_Click()
CopyPaste
End Sub
Sub CopyPaste()
Dim LastRow As Long, Counter As Long
Dim cSheet As Worksheet '<~ add a worksheet reference to save some typing
'set references up front
Set cSheet = ThisWorkbook.Worksheets("Calculate")
With cSheet
LastRow = .Range("E" & .Rows.Count).End(xlUp).Row '<~ set loop boundary
'loop that compares the value in column 6 (F) to the value in
'column 10 (J) and writes the value from F to J if they are not equal
For Counter = 1 To LastRow
If .Cells(Counter, 6).Value <> .Cells(Counter, 10).Value Then
.Cells(Counter, 10) = .Cells(Counter, 6)
End If
Next Counter
End With
End Sub