How to do multiple transpose in excel vba - excel

I have a list of items which are scattered, I need them all in one column, the items scattered can be brought into one column within the blank cells.
This is my requirement. The values in the first column must not change position.
I have a code which does the transpose, but its changing the position of values in the first column, its putting everything together, so the position of pink which is 9th, becomes 8th as its igonoring the blank.
Sub test3()
Dim outarr()
Nc = Cells.Find(What:="*", LookIn:=xlFormulas, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column + 1
lr = Cells.Find(What:="*", LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
inarr = Range(Cells(1, 1), Cells(lr, Nc))
ReDim outarr(1 To lr * Nc, 1 To 1)
indi = 1
For i = 1 To UBound(inarr, 1)
For j = 1 To UBound(inarr, 2)
If inarr(i, j) <> "" Then
outarr(indi, 1) = inarr(i, j)
indi = indi + 1
End If
Next j
Next i
Range(Cells(1, Nc + 1), Cells(indi - 1, Nc + 1)) = outarr
End Sub
my requirement is to move the values from other columns without disturbing the 1st column.

Re-ractoring original code into a single loop, and adding the condition that the input index will not increment if the output hasn't 'caught up' with the input:
Option Explicit
Sub test3()
Dim outarr(), inarr()
' Change to Long as required
Dim i As Integer, j As Integer, k As Integer, lr As Integer, Nc As Integer, indi As Integer
Nc = Cells.Find(What:="*", LookIn:=xlFormulas, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column + 1
lr = Cells.Find(What:="*", LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
inarr = Range(Cells(1, 1), Cells(lr, Nc))
ReDim outarr(1 To lr * Nc, 1 To 1)
indi = 1
k = 0
' Loop over array row-wise
Do While k < lr * Nc
i = k \ Nc + 1
j = k Mod Nc + 1
' If output row not same as input row and first column is occupied, don't increment k
If inarr(i, j) <> "" Then
If indi < i And j = 1 Then
indi = indi + 1
Else
outarr(indi, 1) = inarr(i, j)
indi = indi + 1
k = k + 1
End If
Else
k = k + 1
End If
Loop
Range(Cells(1, Nc + 1), Cells(indi - 1, Nc + 1)) = outarr
End Sub

you can use Dictionary object
Sub test2()
With New Scripting.Dictionary
Dim cel As Range
For Each cel In Range("A1", Cells(Rows.Count, 1).End(xlUp)).SpecialCells(xlCellTypeConstants)
.Add cel.Row, Range(cel, Cells(cel.Row, Columns.Count).End(xlToLeft))
Next
Dim lastCol As Long
lastCol = Cells.Find(What:="*", LookIn:=xlFormulas, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
Dim ik As Long
For ik = 0 To .Count - 1
Cells(.keys(ik), lastCol + 2).Resize(, .Items(ik).Columns.Count).Value = .Items(ik).Value
Next
End With
End Sub
just add reference to "Microsoft Scripting Runtime" library

Try this.
Sub ManyRowsToOneColumn()
Dim N As Long, i As Long, K As Long, j As Long
Dim sh1 As Worksheet, sh2 As Worksheet
K = 1
Set sh1 = Sheets("Sheet1")
Set sh2 = Sheets("Sheet2")
N = sh1.Cells(Rows.Count, "A").End(xlUp).Row
For i = 1 To N
For j = 1 To Columns.Count
If sh1.Cells(i, j) <> "" Then
sh2.Cells(K, 1).Value = sh1.Cells(i, j).Value
K = K + 1
Else
Exit For
End If
Next j
Next i
End Sub
Before.
After.

Related

How to split cell contents from multiple columns into rows by delimeter?

The code I have takes cells containing the delimiter (; ) from a column, and creates new rows (everything except the column is duplicated) to separate those values.
What I have
I need this for multiple columns in my data, but I don't want the data to overlap (ex: for 3 columns, I want there to be only one value per row in those 3 columns). It would be ideal if I could select multiple columns instead of only one as my code does now.
What I want
Sub splitByCol()
Dim r As Range, i As Long, ar
Set r = Worksheets("Sheet").Range("J2000").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
Try this code
Sub Test()
Dim a, x, e, i As Long, ii As Long, iii As Long, k As Long
a = Range("A1").CurrentRegion.Value
ReDim b(1 To 1000, 1 To UBound(a, 2))
For i = LBound(a) To UBound(a)
For ii = 2 To 3
x = Split(a(i, ii), "; ")
For Each e In x
k = k + 1
b(k, 1) = k
b(k, 2) = IIf(ii = 2, e, Empty)
b(k, 3) = IIf(ii = 3, e, Empty)
b(k, 4) = a(i, 4)
Next e
Next ii
Next i
Range("A5").Resize(UBound(b, 1), UBound(b, 2)).Value = b
End Sub
I'd go this way
Sub SplitByCol()
With Worksheets("Sheet")
With .Range("B2", .Cells(.Rows.Count, "B").End(xlUp))
Dim firstColValues As Variant
firstColValues = .Value
Dim secondColValues As Variant
secondColValues = .Offset(, 1).Value
Dim thirdColValues As Variant
thirdColValues = .Offset(, 2).Value
.Offset(, -1).Resize(, 4).ClearContents
End With
Dim iRow As Long
For iRow = LBound(firstColValues) To UBound(firstColValues)
Dim currFirstColValues As Variant
currFirstColValues = Split(firstColValues(iRow, 1), "; ")
Dim currSecondColValues As Variant
currSecondColValues = Split(secondColValues(iRow, 1), "; ")
With .Cells(.Rows.Count, "C").End(xlUp).Offset(1, -1)
With .Resize(UBound(currFirstColValues) + 1)
.Value = currFirstColValues
.Offset(, 2).Value = thirdColValues(iRow, 1)
End With
End With
With .Cells(.Rows.Count, "B").End(xlUp).Offset(1, 1)
With .Resize(UBound(currSecondColValues) + 1)
.Value = currSecondColValues
.Offset(, 1).Value = thirdColValues(iRow, 1)
End With
End With
Next
End With
End Sub
Follow the code step by step by pressing F8 while the cursor is in any code line in the VBA IDE and watch what happens in the Excel user interface
EDIT
adding edited code for a more "parametric" handling by means of a helper function
Sub SplitByCol()
With Worksheets("Sheet")
With .Range("B2", .Cells(.Rows.Count, "B").End(xlUp))
Dim firstColValues As Variant
firstColValues = .Value
Dim secondColValues As Variant
secondColValues = .Offset(, 1).Value
Dim thirdColValues As Variant
thirdColValues = .Offset(, 2).Value
.Offset(, -1).Resize(, 4).ClearContents
End With
Dim iRow As Long
For iRow = LBound(firstColValues) To UBound(firstColValues)
Dim currFirstColValues As Variant
currFirstColValues = Split(firstColValues(iRow, 1), "; ")
Dim currSecondColValues As Variant
currSecondColValues = Split(secondColValues(iRow, 1), "; ")
WriteOne .Cells(.Rows.Count, "C").End(xlUp).Offset(1), _
currFirstColValues, thirdColValues(iRow, 1), _
-1, 2
WriteOne .Cells(.Rows.Count, "B").End(xlUp).Offset(1), _
currSecondColValues, thirdColValues(iRow, 1), _
1, 1
Next
End With
End Sub
Sub WriteOne(refCel As Range, _
currMainColValues As Variant, thirdColValue As Variant, _
mainValuesOffsetFromRefCel As Long, thirdColValuesOffsetFromRefCel As Long)
With refCel.Offset(, mainValuesOffsetFromRefCel)
With .Resize(UBound(currMainColValues) + 1)
.Value = currMainColValues
.Offset(, thirdColValuesOffsetFromRefCel).Value = thirdColValue
End With
End With
End Sub
Please, use the next code. It uses arrays and should be very fast for big ranges to be processed, working mostly in memory:
Sub testSplitInsert()
Dim sh As Worksheet, lastR As Long, arr, arrSp, arrFin, i As Long, j As Long, k As Long
Set sh = ActiveSheet
lastR = sh.Range("B" & sh.rows.count).End(xlUp).row
arr = sh.Range("B1:D" & lastR).Value
ReDim arrFin(1 To UBound(arr) * 10, 1 To 3) 'maximum to keep max 10 rows per each case
k = 1 'initialize the variable to load the final array
For i = 1 To UBound(arr)
arrSp = Split(Replace(arr(i, 1)," ",""), ";") 'trim for the case when somebody used Red;Blue, instead of Red; Blue
For j = 0 To UBound(arrSp)
arrFin(k, 1) = arrSp(j): arrFin(k, 3) = arr(i, 3): k = k + 1
Next j
arrSp = Split(Replace(arr(i, 1)," ",""), ";")
For j = 0 To UBound(arrSp)
arrFin(k, 2) = arrSp(j): arrFin(k, 3) = arr(i, 3): k = k + 1
Next j
Next
sh.Range("G1").Resize(k - 1, 3).Value = arrFin
End Sub
It processes the range in columns "B:D" and returns the result in columns "G:I". It can be easily adapted to process any columns range and return even overwriting the existing range, but this should be done only after checking that it return what you need...

Looping with variable data set

Based on the picture I would like for each animal to be copied to each Set/# (and for the outcome to be on Sheet 2).
Example of Goal
The issue is that it won't always be a set of 14 it can vary based on the data but the Animals would stay the same (no more then 4).
Below is what I have, granted it is not based on the picture. That is an example.
Sub DowithIf()
rw = 5
cl = 2
rw = 1000
Do While rw < erw
If Cells(rw, cl) <> Cells(rw - 1, cl) Then
Cells(rw, cl + 1) = Cells(rw, cl)
Range("A5:B5").Select
Selection.Copy
Sheets("Sheet2").Select
Range("A2").Select
ActiveSheet.Paste
Range("A2:B4").Select
Application.CutCopyMode = False
Selection.FillDown
Sheets("Data").Select
Range("E3:J5").Select
Selection.Copy
Sheets("Sheet2").Select
Range("C2").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
ElseIf Cells(rw, cl) = "" Then
Exit Do
End If
rw = rw + 1
Loop
End Sub
I think you'd find this easier if you looked at VBA as more of a programming language than a macro recorder. In your example, the task is really just to create an array whose row count is:
number of set names * number of set items
All you'd need to do is populate that array following a certain pattern. In your example it would be:
set number n with all set items, set number n + 1 with all set items, etc.
Skeleton code would look something like this:
Const SET_NAMES_ROW_START As Long = 6
Const SET_ITEMS_ROW_START As Long = 6
Const SET_NAMES_COL As String = "A"
Const SET_ITEMS_COL As String = "E"
Const OUTPUT_ROW_START As Long = 6
Const OUTPUT_COL As String = "G"
Dim names() As Variant, items() As Variant, output() As Variant
Dim namesCount As Long, itemsCount As Long
Dim idx As Long, nameIdx As Long, itemIdx As Long
'Read the set values.
With Sheet1
names = .Range( _
.Cells(SET_NAMES_ROW_START, SET_NAMES_COL), _
.Cells(.Rows.Count, SET_NAMES_COL).End(xlUp)) _
.Resize(, 2).Value2
items = .Range( _
.Cells(SET_ITEMS_ROW_START, SET_ITEMS_COL), _
.Cells(.Rows.Count, SET_ITEMS_COL).End(xlUp)) _
.Value2
End With
'Dimension the output array.
namesCount = UBound(names, 1)
itemsCount = UBound(items, 1)
ReDim output(1 To namesCount * itemsCount, 1 To 3)
'Populate the output array.
nameIdx = 1
itemIdx = 1
For idx = 1 To namesCount * itemsCount
output(idx, 1) = names(nameIdx, 1)
output(idx, 2) = names(nameIdx, 2)
output(idx, 3) = items(itemIdx, 1)
itemIdx = itemIdx + 1
If itemIdx > itemsCount Then
'Increment the name index by 1.
nameIdx = nameIdx + 1
'Reset the item index to 1.
itemIdx = 1
End If
Next
'Write array to the output sheet.
Sheet1.Cells(OUTPUT_ROW_START, OUTPUT_COL).Resize(UBound(output, 1), UBound(output, 2)).Value = output
So I think this will let you dynamically pick the size of your data set. I'm assuming that the column headers will always be at Row 5 as pictured. It loops through each input column and provides a unique output in H, I, and J. Disclaimer: I didn't get to test this as I'm not on my work PC.
Sub MixTheStuff()
'sets size of data in A (Set). -5 for the header row as noted
x = ThisWorkbook.Sheets("Data").Cells(Rows.Count, 1).End(xlUp).Row - 5
'sets size of data in B (#)
y = ThisWorkbook.Sheets("Data").Cells(Rows.Count, 2).End(xlUp).Row - 5
'sets size of data in E (Animal)
z = ThisWorkbook.Sheets("Data").Cells(Rows.Count, 5).End(xlUp).Row - 5
i=6 'First row after the headers
For sThing = 1 to x 'set thing
For nThing = 1 to y 'number thing
For aThing = 1 to z 'animal thing
'Pastes the value of the stuff (Set, #, and Animal respectively)
ThisWorkbook.Sheets("Data").cell(i,10) = ThisWorkbook.Sheets("Data").cell(x,1).value
ThisWorkbook.Sheets("Data").cell(i,11) = ThisWorkbook.Sheets("Data").cell(y,2).value
ThisWorkbook.Sheets("Data").cell(i,12) = ThisWorkbook.Sheets("Data").cell(z,5).value
i = i + 1 'Go to the next output row
Next sThing
Next nThing
Next aThing
End Sub
Sort of Unpivot
This will allow you to handle maximally 1023 animals.
The Code
Option Explicit
Sub SortOfUnpivot()
Const FirstRow As Long = 6
Const LastRowCol As String = "E"
Const dstFirstCell As String = "H6"
Dim srcCols As Variant
srcCols = VBA.Array("A", "B", "E")
Dim LB As Long
LB = LBound(srcCols)
Dim UB As Long
UB = UBound(srcCols)
Dim srcCount As Long
srcCount = UB - LB + 1
Dim LastRow As Long
LastRow = Cells(Rows.Count, LastRowCol).End(xlUp).Row
Dim rng As Range
Set rng = Cells(FirstRow, LastRowCol).Resize(LastRow - FirstRow + 1)
Dim Source As Variant
ReDim Source(LB To UB)
Dim j As Long
For j = LB To UB
Source(j) = rng.Offset(, Columns(srcCols(j)).Column - rng.Column).Value
Next j
Dim UBS As Long
UBS = UBound(Source(UB))
Dim Dest As Variant
ReDim Dest(1 To UBS ^ 2, 1 To srcCount)
Dim i As Long
Dim k As Long
For j = 1 To UBS
k = k + 1
For i = 1 + (j - 1) * UBS To UBS + (j - 1) * UBS
Dest(i, 1) = Source(0)(k, 1)
Dest(i, 2) = Source(1)(k, 1)
Dest(i, 3) = Source(2)(i - (j - 1) * UBS, 1)
Next i
Next j
Range(dstFirstCell).Resize(UBound(Dest), srcCount).Value = Dest
End Sub

Working with jagged arrays, printing sub-array to sheet, vba

Background:
Was trying to come up with efficient ways to handle a large amount of tables on a single sheet and came across Jagged Arrays (herein "Jars").
To simply understand some basics of Jars, I was trying to build a simple scenario of staggered information to be able to create the Jar.
My Jar is labeled big_arr and each array inside is called lil_arr.
Here is the data for the scenario:
ColA 'adding row number in front of each word
1 cat
2 dog
3
4 mouse
5 elephant
6
7 zebra
8 snake
9
10 cheese
11 pickle
12
13 anteater
14 mirkat
15
16 skunk
17 smurf
In the above scenario, big_arr(2) = lil_arr where `lil_arr = array("mouse","elephant").
I would then have big_arr(i) print to a sheet; the sheet is labeled as i, when looping. So sheet 2 would have cells(1,1).value = "mouse" and cells(1,2).value = "elephant".
Issue:
I am having issues getting the data to print as expected.
The exact printing that is happening (based on i as the sheet name):
1 has cells(1,1).value = 0
2 has cells(1,1).value = "skunk"
3 has cells(1,1).value = 0
4 has cells(1,1).value = 0
5 has cells(1,1).value = 0
6 has cells(1,1).value = 0
I don't seem to be able to print using Application.Transpose(big_arr(i)). I have attempted to loop, but don't seem to have appropriate syntax.
Question:
Any help to resolve the issue with Application.Transpose(), which does not trigger an error message, would be appreciated.
Otherwise, help to get the loop to work with appropriate syntax would be phenomenal.
Code in question:
Code with Application.Transpose() for printing
Sub create_jagged_array_of_tables()
Dim big_arr As Variant, lil_arr As Variant, lr As Long, i As Long, j As Long, k As Long, ws As Worksheet
lr = Cells(Rows.Count, 1).End(xlUp).Row
ReDim big_arr(1 To lr)
For i = 1 To lr
j = 1
Do Until IsEmpty(Cells(i + j, 1))
j = j + 1
Loop
If j > 1 Then
lil_arr = Cells(i, 1).Resize(j).Value
big_arr(j) = lil_arr
i = i + j
k = k + 1
Else
MsgBox "row " & i & " is not part of an array"
End If
Next i
For i = 1 To k
Set ws = Sheets.Add
ws.Name = i
Cells(1, 1).Value = Application.Transpose(big_arr(i))
Next i
End Sub
Code for the loop I attempted, giving type-mismatch, focusing only on the for i = 1 to k loop:
For i = 1 To k
Set ws = Sheets.Add
ws.Name = i
'Cells(1, 1).Value = Application.Transpose(big_arr(i))
For j = 1 To UBound(big_arr(i), 1)
Cells(j, 1).Value = big_arr(i)(j)
Next j
Next i
In this case j will always = 2 at the line:
big_arr(j) = lil_arr
so you keep overwriting that.
I assume you want to use k instead of j for the counter of big_arr:
big_arr(k) = lil_arr
But that will require you to have a k=1 before the i loop.
Also you need to resize the output to the size of the lil_array:
Sub create_jagged_array_of_tables()
Dim big_arr As Variant, lil_arr As Variant, lr As Long, i As Long, j As Long, k As Long, ws As Worksheet
lr = Cells(Rows.Count, 1).End(xlUp).Row
ReDim big_arr(1 To lr)
k = 0
For i = 1 To lr
j = 1
Do Until IsEmpty(Cells(i + j, 1))
j = j + 1
Loop
If j > 1 Then
lil_arr = Cells(i, 1).Resize(j).Value
k = k + 1
big_arr(k) = lil_arr
i = i + j
Else
MsgBox "row " & i & " is not part of an array"
End If
Next i
For i = 1 To k
Set ws = Sheets.Add
ws.Name = i
Cells(1, 1).Resize(1, UBound(big_arr(i), 1)).Value = Application.Transpose(big_arr(i))
Next i
End Sub
Did just a little tweaking and it's working for me:
Sub create_jagged_array_of_tables()
Dim big_arr As Variant, lil_arr As Variant, lr As Long, i As Long, j As Long, k As Long, ws As Worksheet
lr = Cells(Rows.Count, 1).End(xlUp).Row
Dim big_arr_size As Long
' Assumes you have groups of 2 per small array
big_arr_size = WorksheetFunction.CountA(Range("A1:A" & lr)) / 2
ReDim big_arr(1 To big_arr_size)
k = 1
For i = 1 To lr
j = 1
Do Until IsEmpty(Cells(i + j, 1))
j = j + 1
Loop
If j > 1 Then
lil_arr = Cells(i, 1).Resize(j).Value
big_arr(k) = lil_arr ' changed `j` to `k`
i = i + j
k = k + 1
Else
MsgBox "row " & i & " is not part of an array"
End If
Next i
For i = 1 To big_arr_size
Set ws = Sheets.Add
ws.Name = i
ws.Cells(1, 1).Value = big_arr(i)(1, 1)
ws.Cells(1, 2).Value = big_arr(i)(2, 1)
Next i
End Sub
Edit: Here's a perhaps different way you can do this. It avoids using a "small array" to set as part of a larger array.
Sub t()
Dim big_arr As Variant
Dim lr As Long
lr = Sheets("Sheet1").Cells(Rows.Count, 1).End(xlUp).Row
Dim big_arr_size As Long
' Assumes you have groups of 2 per small array
big_arr_size = WorksheetFunction.CountA(Range("A1:A" & lr)) / 2
ReDim big_arr(1 To lr)
big_arr = Range("A1:A" & lr).Value
Dim i As Long, wsName As Long
Dim ws As Worksheet
wsName = LBound(big_arr)
For i = LBound(big_arr) To UBound(big_arr) - 1
If Not IsEmpty(big_arr(i, 1)) And Not IsEmpty(big_arr(i + 1, 1)) Then
Set ws = Sheets.Add
ws.Name = wsName
ws.Cells(1, 1).Value = big_arr(i, 1)
ws.Cells(1, 2).Value = big_arr(i + 1, 1)
wsName = wsName + 1
End If
Next i
End Sub
The Post already had two brilliant answers (one accepted) and both have there unique characteristics. But just want to share some of my idea since I find the the post highly interesting. I just tried to simplify the creation of jagged array using single loop using a flag and avoided transpose. May please not taken as contravention.
Sub create_jagged_array_of_tables()
Dim big_arr() As Variant, lil_arr As Variant, lr As Long, i As Long, j As Long, k As Long, ws As Worksheet
Dim Nw As Boolean, Xval As Variant
lr = Cells(Rows.Count, 1).End(xlUp).Row
k = 0
j = 0
For i = 1 To lr
Xval = Cells(i, 1).Value
If IsEmpty(Xval) = False Then
If Nw = False Then
Nw = True
k = k + 1
j = 1
ReDim lil_arr(1 To 1, 1 To j)
lil_arr(1, j) = Xval
ReDim Preserve big_arr(1 To k)
big_arr(k) = lil_arr
Else
j = j + 1
ReDim Preserve lil_arr(1 To 1, 1 To j)
lil_arr(1, j) = Xval
big_arr(k) = lil_arr
End If
Else
Nw = False
End If
Next i
For i = 1 To k
Set ws = Sheets.Add
ws.Name = i
Cells(1, 1).Resize(1, UBound(big_arr(i), 2)).Value = big_arr(i)
Next i
End Sub
And if creation of jagged Array is not required and sole objective is to copy the content in the desired fashion, the it could be further simplified to
Sub test1()
Dim lr As Long, Rng As Range, Area As Range, Cnt As Long, Arr As Variant
lr = Cells(Rows.Count, 1).End(xlUp).Row
Set Rng = Range("A1:A" & lr)
Rng.AutoFilter Field:=1, Criteria1:="<>"
Set Rng = Rng.SpecialCells(xlCellTypeVisible)
Cnt = 0
For Each Area In Rng.Areas
Cnt = Cnt + 1
Set ws = Sheets.Add
ws.Name = Cnt
Arr = Area.Value
If IsArray(Arr) Then
ws.Cells(1, 1).Resize(UBound(Arr, 2), UBound(Arr, 1)).Value = Application.Transpose(Arr)
Else
ws.Cells(1, 1).Value = Arr
End If
Next
Rng.AutoFilter Field:=1
End Sub

VBA formatting table with merged cells

I've got a function which merges cells in table if whole range has the same value (eg. if A1:G1 is equal to A2:B2 it will merge cells like A1&A2, B1&B2 etc. More here: How to check if two ranges value is equal)
Now I would like, to change color on table created by that funcion, like first row (doesn't matter if merged or no) filled with color, second blank etc. but I have no idea whether I should color it with merging function or create another which will detect new table with merged rows as one etc. Below is my code:
Sub test()
Dim i As Long, j As Long, k As Long, row As Long
row = Cells(Rows.Count, 2).End(xlUp).row
k = 1
For i = 1 To row Step 1
If Cells(i, 1).Value = "" Then Exit For
If Join(Application.Transpose(Application.Transpose(Range(Cells(i, 1), Cells(i, 3)))), Chr(0)) <> Join(Application.Transpose(Application.Transpose(Range(Cells(i + 1, 1), Cells(i + 1, 3)))), Chr(0)) Then
If i <> k Then
For j = 1 To 3 Step 1
Application.DisplayAlerts = False
Range(Cells(i, j), Cells(k, j)).Merge
Application.DisplayAlerts = True
Next j
End If
k = i + 1
End If
Next i
End Sub
Try:
Option Explicit
Sub test1()
Dim LastColumn As Long, LastRow As Long, i As Long
With ThisWorkbook.Worksheets("Sheet1")
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
LastColumn = .Cells(1, .Columns.Count).End(xlToLeft).Column
For i = 2 To LastRow Step 2
.Range(Cells(i, 1), .Cells(i, LastColumn)).Interior.Color = vbGreen '<- You could change the color
Next i
End With
End Sub
Before:
After:
Edited Solution:
Option Explicit
Sub test1()
Dim rng As Range
With ThisWorkbook.Worksheets("Sheet1")
Set rng = .UsedRange
.ListObjects.Add(xlSrcRange, rng, , xlYes).Name = "Table1"
.ListObjects("Table1").TableStyle = "TableStyleLight3"
End With
End Sub
Result:
So, after some time I've figured it out by myself. Below is the code:
Dim i As Long, j As Long, k As Long, l As Long, c As Integer
row = Cells(Rows.Count, 2).End(xlUp).row
k = 7
c = 1
For i = 7 To row Step 1
If Join(Application.Transpose(Application.Transpose(Range(Cells(i, 1), Cells(i, 3)))), Chr(0)) <> Join(Application.Transpose(Application.Transpose(Range(Cells(i + 1, 1), Cells(i + 1, 3)))), Chr(0)) Then
If i <> k Then
For j = 1 To 3 Step 1
Application.DisplayAlerts = False
Range(Cells(i, j), Cells(k, j)).Merge
Application.DisplayAlerts = True
Next j
End If
Select Case c
Case 0
Range(Cells(k, 1), Cells(k, 3)).Interior.Color = xlNone
c = 1
Case 1
For l = 0 To i - k Step 1
Range(Cells(k + l, 1), Cells(k + l, 3)).Interior.Color = RGB(217, 225, 242)
Next l
c = 0
End Select
k = i + 1
End If
Next i

Read Column A, insert rows based on pattern

I have data in Column A that looks like this:
A
B
A
B
B
B
A
B
A
B
Some points:
All A's must have at least one B. All A's have a B, all B's have an A. (it's an accounting system - it requires this).
Any A can have as many B's as needed.
After each A.B[n] combo, we need a C.
The C must be an inserted row. Sorting and Filtering is not allowed (A, B, and C are variables that aren't replaced with alphabetical characters like seen here).
The code should not insert a C above the first A.
Expected output:
A
B
C
A
B
B
B
C
A
B
C
A
B
C
I've already looked at this: Excel: Insert new line every x rows with content according to a pattern
but the pattern is based on a known 27-line insertion. This has no guaranteed pattern in my problem.
Being a forward thinker, I used multiple Do loops.
Sub InsertCs()
Application.ScreenUpdating = False
Const A As String = "A", B As String = "B", C As String = "C"
Dim r As Long, r2 As Long
With Worksheets("Sheet1")
Do
r = r + 1
If .Cells(r, "A").Value = A And .Cells(r, "A").Offset(1).Value = B Then
r2 = r + 1
Do
r2 = r2 + 1
Loop Until Cells(r2, "A").Value = "" Or Cells(r2, "A").Value = A Or Cells(r2, "A").Value = C
If Not Cells(r2).Value = C Then
.Rows(r2).Insert xlDown
.Cells(r2, "A").Value = C
End If
r = r2
End If
Loop Until Cells(r, "A").Value = ""
End With
End Sub
Try
Sub test()
Dim vDB, vR()
Dim A, B, C
Dim i As Long, r As Long, n As Long
A = "A"
B = "B"
C = "C"
vDB = Range("a1").CurrentRegion
r = UBound(vDB, 1)
n = 1
ReDim Preserve vR(1 To n)
vR(1) = vDB(1, 1)
For i = 2 To r
If vDB(i - 1, 1) = B And vDB(i, 1) = A Then
n = n + 1
ReDim Preserve vR(1 To n)
vR(n) = C
End If
n = n + 1
ReDim Preserve vR(1 To n)
vR(n) = vDB(i, 1)
Next i
If vR(n) = B Then
n = n + 1
ReDim Preserve vR(1 To n)
vR(n) = C
End If
Range("c1").CurrentRegion.Clear
Range("c1").Resize(n, 1) = WorksheetFunction.Transpose(vR)
End Sub
If you want multi column then
Sub test2()
Dim vDB, vR(), vS()
Dim A, B, C
Dim i As Long, r As Long, n As Long
Dim col As Integer
Dim Ws As Worksheet
A = "A"
B = "B"
C = "C"
vDB = Range("a1").CurrentRegion
r = UBound(vDB, 1)
col = UBound(vDB, 2)
n = 1
ReDim Preserve vR(1 To col, 1 To n)
For j = 1 To col
vR(j, n) = vDB(1, j)
Next j
For i = 2 To r
If vDB(i - 1, 1) = B And vDB(i, 1) = A Then
n = n + 1
ReDim Preserve vR(1 To col, 1 To n)
vR(1, n) = C
End If
n = n + 1
ReDim Preserve vR(1 To col, 1 To n)
For j = 1 To col
vR(j, n) = vDB(i, j)
Next j
Next i
If vR(1, n) = B Then
n = n + 1
ReDim Preserve vR(1 To col, 1 To n)
vR(1, n) = C
End If
Set Ws = Sheets.Add 'Sheets("Result")
With Ws
.Range("a1").CurrentRegion.Clear
.Range("a1").Resize(n, col) = WorksheetFunction.Transpose(vR)
End With
End Sub
1. All A's must have at least one B.
Since all A's must have at least one B, your logic seems to boil down to: If current cell is not B and cell directly above is B then insert row and paste C.
Option Explicit
Sub Macro1()
Dim i As Long
Dim a As Variant, b As Variant, c As Variant
a = "A"
b = "B"
c = "C"
With Worksheets("sheet3")
For i = .Cells(.Rows.Count, "A").End(xlUp).Row + 1 To 3 Step -1
Select Case .Cells(i - 1, "A").Value2
Case b
If .Cells(i, "A").Value2 <> b Then
.Rows(i).Insert
.Cells(i, "A") = c
End If
End Select
Next i
End With
End Sub
Sub MultipleSearch()
Dim rng As Range
Dim cll As Range
Dim lrow As Long
lrow = Cells(Rows.Count, 1).End(xlUp).Row
Set rng = Range("A1:A" & lrow)
Cells(lrow + 1, 1) = "C"
For i = rng.Cells.Count To 2 Step -1
If rng.Item(i) = "A" Then
Rows(i).Insert
Cells(i, 1) = "C"
End If
Next
End Sub
Try this simple code. It will loop from the last cell in column A and if there is a variable varA then varB, it will insert a row and add varC. Assign your variables as needed.
Dim varA As Variant, varB As Variant, varC As Variant
Dim Rng As Range, i As Long, lRow As Long
varA = "A"
varB = "B"
varC = "C"
lRow = Cells(Rows.Count, 1).End(xlUp).Row
For i = lRow To 2 Step -1
If Cells(i, 1).Value = varB And Cells(i, 1).Offset(-1).Value = varA Then
Cells(i, 1).Offset(1).EntireRow.Insert
Cells(i, 1).Offset(1).Value = varC
End If
Next i
Using Find and some do loops is a way to do it...
Sub InsertC()
Application.ScreenUpdating = False
Dim Data As Range: Set Data = Worksheets("Sheet1").Range("A:A")
Dim FirstCell As Range: Set FirstCell = Data.Find("A", LookIn:=xlValues, LookAt:=xlWhole, SearchDirection:=xlNext, SearchOrder:=xlByRows)
Dim NextCell As Range, ACell As Range: Set ACell = FirstCell
If Not ACell Is Nothing Then
Do
Set NextCell = ACell
Do While NextCell.Offset(1, 0) = "B"
Set NextCell = NextCell.Offset(1, 0)
Loop
If Not ACell = NextCell Then
NextCell.Offset(1, 0).Insert Shift:=xlDown
NextCell.Offset(1, 0) = "C"
End If
Set ACell = Data.Find("A", After:=NextCell, LookIn:=xlValues, LookAt:=xlWhole, SearchDirection:=xlNext, SearchOrder:=xlByRows)
Loop While ACell.Address <> FirstCell.Address
End If
Application.ScreenUpdating = True
End Sub

Resources