VBA solution for 'stacking' columns - excel

Below is a snippet of the table I'm working with.
From left to write I need to know how I can the entire second column appended to the first one. So, starting at V5789 of the second column, that and all of the contents below it need to be placed after the V854 in the first column. The third column needs to be 'stacked'onto the bottom of the second one. So 2 appends to 1, 3 appends to 2, 4 appends to 3. etc.
Any clues?

Hows this for an alternative? Only has 2 calls to the worksheet and one loop.
Sub append()
Dim g, newArray
Dim strJoin As String
Dim x As Integer
g = Sheet1.Cells(1, 1).CurrentRegion.Value
For x = 1 To UBound(g, 2)
strJoin = strJoin & Replace(Join(Application.Transpose(Application.Index(g, 0, x)), "~/"), "/~", "")
If Right(strJoin, 2) <> "~/" And x <> UBound(g, 2) Then strJoin = strJoin & "~/"
Next x
newArray = Split(strJoin, "~/")
Columns(1).Cells(1).Resize(UBound(newArray) + 1).Value = Application.Transpose(newArray)
End Sub

Try this:
Sub DoooooooooIT()
Dim col As Range, _
found As Range
Dim currRow As Integer
currRow = ActiveSheet.Range("A:A").Find("", after:=ActiveSheet.Range("A1"), lookat:=xlWhole, searchdirection:=xlNext).Row
For Each col In ActiveSheet.UsedRange.Columns
If col.Column <> 1 Then
Set found = col.EntireColumn.Find("", after:=col.Cells(1, 1), lookat:=xlWhole, searchdirection:=xlNext)
Set found = ActiveSheet.Range(col.Cells(1, 1), found)
found.Copy
ActiveSheet.Cells(currRow, 1).PasteSpecial
currRow = currRow + found.Cells.Count - 1
End If
Next col
End Sub

I would go about this by using 2 for loops: outer loop will start your counting variable at 2, then iterate up with a step of 1. This is iterating through your columns.
in that loop, have a nested for loop that iterates through each row of the unique columns. It will iterate from row one and down, checking each loop if the cell at the position given by the outer loop's column and inner loop's row has anything in it (so check if cell.value = ""). Once it finds the first empty cell in that column, have it copy everything from row 3 until the row counting variable in the outer loop's column and paste it into the previous column at row 3.
I have provided pseudo-code instead of the actual code because I don't believe this would be too difficult to write yourself and I don't have time to at this moment, so you may be able to do it sooner than I will have a chance too. However, if you want further assistance let me know and I can work on some code for you when I have a chance.
edit: forgot to add, make sure to include something to deal with the unique position of where the second column data needs to go into the first column. likely best if you just include something like "if PasteToColumn = 1 then paste into cell A9" or something similar
EDIT again: here's my new and improved version! let me know what you think/if it works
Sub MoveStuff()
Dim rowcounter As Integer
Dim columncounter As Integer
rowcounter = 1
columncounter = 2
Do While Cells(rowcounter, columncounter).Value <> ""
Do While Cells(rowcounter, columncounter).Value <> ""
rowcounter = rowcounter + 1
Loop
Range(Cells(3, columncounter), Cells(rowcounter - 1, columncounter)).Cut (Columns(columncounter - 1).End(xlDown).Offset(rowoffset:=1))
columncounter = columncounter + 1
rowcounter = 1
Loop
End Sub

Related

Split Comma Separated Values Into Array And Find Matches to Pre-set Criteria

I am new to StackOverflow, apologies in advance if I am not going about this in the right way.
I have some raw data that looks like the following:
All the values are separated by commas, in this string I am looking to find if the full range of numbers (1,2,3,4,5) is found, if it does, then it should return a 100% match.
In case only 4 numbers out of this range are found then it should return 80%, for 3 numbers 60%, for 2 numbers 40%, for 1 number 10% and in case none are found it should return "none". (see desired output below)
I am still new to VBA, but my thought was to split my comma separated values into an array, and then try to find a match. However unfortunately I already got stuck at the first match (i.e. finding 100%).
Sub CheckNumberMatches()
Dim i As Long
Dim Elem As Variant
Dim myArr As Variant
With Worksheets("data")
For i = 1 To .Cells(.Rows.Count, "A").End(xlUp).Row
myArr = Split(.Range("A" & i).Value, ",")
' loop through array elements and look for 1,2,3,4,5
For Each Elem In myArr
If Elem Like "1,2,3,4,5" Then
.Range("B" & i).Value = "100%"
Exit For
End If
Next Elem
Next i
End With
End Sub
After #FunThomas his reply, I realize my requirements are not very clear, let me provide a few more examples of what can happen:
The main criteria is (1,2,3,4,5) needs to be found in the cell, but this does not need to be in numerical order, i.e. can be random (2,4,1,3,5). If all these numbers are found in any order it should be 100%.
If for example all five numbers are found (1,2,3,4,5) in the cell, but the cell also includes other numbers (1,2,3,4,5,6,7) - it should still be counted as 100%.
If for example only four numbers of the main criteria are found (for example: 1,2,4,5) it should be considered as 80% (as long as 4 out of main numbers are found), likewise for 3, 2, 1 and 0 matches.
The data can have gaps, i.e. it can be a range of (5,2,7,11,12), in this particular example it should be counted as 40% (2 out of 5 choices are found).
Duplicate numbers are not possible.
I hope that clarifies.
Array approach
Instead of looping through each cell in column "A" which can be time consuming, you can benefit from using arrays:
Assign data to 1-based 2-dim data field array (see section 3),
analyze each splitted element cur in a single loop (section 4), where counting the result of Match() with two array inputs receives the wanted information in one go via
Application.Count(Application.Match(cur, base, 0))
Note that Application.Count() neglects errors resulting from non-findings.
All infos are reassigned to the data array and written back via rng.Offset(, 1) = data
Sub FoundBaseNumbers()
With Tabelle1
'1. Assign needed base numbers to 1-dim array
Dim base As Variant: base = Split("1,2,3,4,5", ",")
'2. Define data range
Dim lastRow As Long
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Dim rng As Range
Set rng = .Range("A1:A" & lastRow)
'3. Assign data to 1-based 2-dim data field array
Dim data As Variant: data = rng.Value2
'4. Analyze data
Dim i As Long, cur As Variant, cnt As Long
For i = 1 To UBound(data)
'a) count findings of current elements
cur = Split(data(i, 1), ",")
cnt = Application.Count(Application.Match(cur, base, 0))
'b) remember percentages using data field array
data(i, 1) = IIf(cnt, Format(cnt / (UBound(base) + 1), "0%"), "None")
Next i
'5. Write data to neighbour column
rng.Offset(, 1) = data
End With
End Sub
You started off well for your code. There are many ways something like this can be done. I've done up a simple way for you utiziling your code already. Have a look below and see if it's right for you.
I used Select Case as it allows to check for multiple things a lot easier than an If statement. You can even use ranges like Case 1 To 10. You can also do multiple Case lines to have different results do different things (like an ElseIf) etc.
Sub CheckNumberMatches()
Dim i As Long, Elem As Variant, myArr As Variant, Counter As Long
With Worksheets("data")
For i = 1 To .Cells(.Rows.Count, "A").End(xlUp).Row
myArr = Split(.Range("A" & i).Value, ",")
Counter = 0
' loop through array elements and look for 1,2,3,4,5
For Each Elem In myArr
Select Case Elem
Case 1, 2, 3, 4, 5
Counter = Counter + 1
End Select
Next
If Counter > 0 Then
.Cells(i, "B").Value = Format(Counter / 5, "0%")
Else
.Cells(i, "B").Value = "None"
End If
Next i
End With
End Sub

Copy First Cell of a row based on a criteria

I need to copy the Employee Name in Column I until a new Employee Comes up. For example, Copy Brown, Nat till row 8 i.e. Code: Shift 1, then start copying Brown, Rob. I used If with find function but i cant make it work.
Your question is mighty short on detail but the function below should help you get one step nearer to what you need. Please try it.
Private Function EmployeeData(ByVal FirstRow As Long) As Variant
' 233
Dim Arr As Variant
Dim Cl As Long ' last used column
Dim R As Long ' lop counter: rows
With Worksheets("Sheet1")
Arr = .Range(.Cells(1, "I"), .Cells(.Rows.Count, "I").End(xlUp)).Value
R = FirstRow
Do
If InStr(1, Arr(R + 1, 1), "Employee Name:", vbTextCompare) = 1 Then Exit Do
R = R + 1
Loop While R < UBound(Arr)
With .UsedRange
Cl = .Columns.Count + .Column - 1
End With
EmployeeData = .Range(.Cells(FirstRow, "A"), .Cells(R, Cl)).Value
End With
End Function
The function takes one argument. That is the number of the row where the employee's name is first found. Starting from that row, the function searches until the next name is found in column I and returns the data from the rows in between. It returns all data, from column A to the last used column.
Use the procedure below to test. Observe that EmployeeData(14) specifies row 14 as the first row of a block and that the loop that follows just prints column I:I although the array contains all the columns.
Sub GetData()
' 233
Dim Arr As Variant
Dim R As Long
Arr = EmployeeData(14)
For R = 1 To UBound(Arr)
Debug.Print Arr(R, 9)
Next R
End Sub
In real life, you will probably need to search for the first row before you can run this code. That search, if needed, is easy to integrate into the test procedure.

Separating values in a cell into multiple rows

I have these values in one excel row that was imported from a csv file
Col1 Col2 Col3 Col4
name#email.com `value1 value2 value 3`
Name2#email.com
name3#email.com
I need these separated into 3 rows with the same value of cols 2-4 in each row. Note: the number of entries in column 1 is not consistent. I could range from 1 to 12 lines. And, there may be duplicates.
I found some code that was supposed to remove duplicate which would have helped but it would not be the complete solution and besides, it didn't work. I also found some formulas but none of them were exactly what I wanted either.
Thanks in advance for the help.
I would suggest using the SPLIT function to split the cell with more than one lines into an array. You can split at every line break using the Line feed character (character code 10).
For example, if you start with the following data:
Reproduce this data
And run the following:
Sub SplitRow()
Dim wb As Workbook
Set wb = Workbooks("MyWorkbook.xlsm")
Dim ws As Worksheet
Set ws = wb.Sheets("MySheet")
Dim rng As Range
Set rng = ws.Range("A1")
Dim MyArray As Variant
MyArray = Split(rng, Chr(10))
'Insert the number of rows needed
If UBound(MyArray) > 0 Then
rng.Offset(1, 0).Resize(UBound(MyArray), 1).EntireRow.Insert xlShiftDown, xlFormatFromLeftOrAbove
End If
'Write the array to sheet
rng.Resize(UBound(MyArray) + 1, 1).Value2 = Application.Transpose(MyArray)
'Copy over the other columns
Dim i As Long, j As Long
For i = 1 To 3
For j = 1 To UBound(MyArray) + 1
rng.Offset(i, j).Value2 = rng.Offset(0, j)
Next j
Next i
End Sub
You'd then have this:
Potential problem with line breaks
Note that there might not be any line feed character in your cell. In that case, you'd have to find another character to serve as delimiter to split.
To know if you cell contains a line feed, you can select the cell and run the following :
Sub StringDrillDown(str As String)
Dim ws As Worksheet
With ActiveWorkbook
Set ws = .Sheets.Add(AFTER:=.Sheets(.Sheets.Count))
End With
ws.Range("A1") = "Character"
ws.Range("B1") = "Ascii Code"
Dim i As Long
For i = 1 To Len(str)
ws.Cells(i + 1, 1).Value2 = Mid$(str, i, 1)
ws.Cells(i + 1, 2).Value2 = Asc(Mid$(str, i, 1))
Next i
End Sub
For exemple, if I run it on the original content of Range A1 (before spliting), I would get :
Hopefully, your CSV is formatted in a way where you have a character between each element so you can split them properly. If not, you might have to have a look into regular expression.
Cautionary notes:
The SPLIT function is zero-based meaning that the first element of MyArray is MyArray(0).
If you plan to run this procedure on multiple rows of data, make sure that your loop starts from the bottom to avoid issues with inserted rows messing up the rows position.

Loop through name list and if names exist in selection start after last name

I apologize, this is my first crack at Excel VBA so excuse my lack of knowledge!
So I have a list of (currently) 3 names to assign to the days in column A in a repeating order in Excel.
Currently my VBA code allows it to populate the selected cells with the names in a repeating pattern (this part is good), however there are two pieces I need help with.
1- with current code, once it reaches the bottom of the names it checks for the blank box that would end that list and starts over at the tops as directed but it puts a blank cell first (see screenshot). How can I have it put next name without adding blank cell first?
2- I want to be able to (once this gets going)select the entire D column through what dates need to be filled and:
-check the lowest non blank box
-match to list and set the
counter to name below that so
it continues the name order
from the last person who was
assigned
This is code I have now:
Sub EXAMPLE()
Dim count As Integer
count = 0
For Each c In Selection
c.Value = Range("X1").Offset(count, 0).Value
If c.Value = "" Then count = -1 And c.Value = Range("x1").Offset(count, 0).Value
count = count + 1
Next c
End Sub
Sorry I know that was long, I hope this makes sense.
I think it's worth reading about arrays, as this task is ideally suited to their use. Your best bet would be to read the names into an array and then build a recurring array whose dimension is equal to the number of rows in your dates column (or selection, or however you want to define the size of the output range).
Code would look a little like this:
Dim v As Variant
Dim people() As Variant, output() As Variant
Dim rowCount As Long, i As Long, j As Long
Dim endRange As Range
'Read the list of names into an array.
'This just takes all data in column "X" -> amend as desired
With Sheet1
Set endRange = .Cells(.Rows.Count, "X").End(xlUp)
v = .Range(.Cells(1, "X"), endRange).Value
End With
'Sense check on the names data.
If IsEmpty(v) Then
MsgBox "No names in Column ""X"""
Exit Sub
End If
If Not IsArray(v) Then
ReDim people(1 To 1, 1 To 1)
people(1, 1) = v
Else
people = v
End If
'Acquire the number of rows for repeating list of names.
'This just takes all data in column "A" -> amend as desired
With Sheet1
Set endRange = .Cells(.Rows.Count, "A").End(xlUp)
rowCount = .Range(.Cells(3, "A"), endRange).Rows.Count
End With
'Sense check date data.
If endRange.Row < 3 Then
MsgBox "No dates in Column ""A"""
Exit Sub
End If
'Make a recurring array.
ReDim output(1 To rowCount, 1 To 1)
i = 1
Do While i <= rowCount
For j = 1 To UBound(people, 1)
output(i, 1) = people(j, 1)
i = i + 1
If i > rowCount Then Exit Do
Next
Loop
'Write the output to column "D"
Sheet1.Range("D3").Resize(UBound(output, 1)).Value = output

Faster way to Find and Copy Values

Hello, I am doing a macro that copy the values on the columns, VALUES1, VALUES2, VALUES3 if it is not blank when the ARTICLE is the same.
I would have the first spreadsheet and I want the macro to return the second Spreadsheet.
I have managed how to make it:
Sub test()
Dim i, last, j, x As Integer
Dim R As Range
last = Sheets("List2").Range("A100000").End(xlUp).Row - 2
For i = 0 To last
Set R = Sheets("List2").Range("A2")
If Not WorksheetFunction.CountIf(Sheets("List2").Columns(1), _
Sheets("List2").Range("A2").Offset(i, 0).Value) = 0 Then
For j = 1 To WorksheetFunction.CountIf(Sheets("List2").Columns(1), _
Sheets("List2").Range("A2").Offset(i, 0).Value)
Set R = Sheets("List2").Columns(1).Find(Sheets("List2").Range("A2"). _
Offset(i, 0).Value, R, LookAt:=xlWhole)
For x = 0 To 2
If Not Sheets("List2").Range("B2").Offset(i, x).Value = "" Then
R.Offset(0, "1" + x).Value = Sheets("List2"). _
Range("B2").Offset(i, x).Value
End If
Next x
Next j
End If
Next i
End Sub
but the problem it takes too long, 'cause I have around 10.000 Rows and 20 Columns, and besides the Spreadsheet is not in order, so it could be to has a disorder, something like (A, B, B, A, ...)
Is there any way to make it faster o better???
Thanks a lot. Themolestones.
Here is a very easy solution with formulas to your problem:
Sheet2!A1=Sheet1!A1
Sheet2!B1=SUMIF(Sheet1!$A:$A,Sheet2!$A1,Sheet1!B:B)
Sheet2!C1=SUMIF(Sheet1!$A:$A,Sheet2!$A1,Sheet1!C:C)
Sheet2!D1=SUMIF(Sheet1!$A:$A,Sheet2!$A1,Sheet1!D:D)
Put these formulas in the cells left of the = and copy down. You really need only the first two, because you can copy the second also to the right.
You need Sheet1 to be sorted by article.
That's it.
Of course, there might be occasions, when it is just necessary to implement this with VBA. Usually the fastest way to handle large amounts of cells with VBA, is to use array-copies of your ranges. Using worksheet-functions and looping through single cell references slows you down heavily.
Edit:
This would be my VBA solution
Public Sub Demo()
Dim arrRange() As Variant
Dim arrRangeResult() As Variant
Dim i As Long
Dim j As Long
Dim copyVal As Variant
Dim copyCond As Variant
Dim copyCol As Long
'create two copies of the origin data
arrRange = Range("A:D")
arrRangeResult = Range("A:D")
'loop through first data-copy, downwards through the articles
For i = LBound(arrRange, 1) + 1 To UBound(arrRange, 1)
'stop loop, if no article was found
If arrRange(i, 1) = "" Then Exit For
'store current article ID
copyCond = arrRange(i, 1)
'loop sideways through value-columns
For j = LBound(arrRange, 2) + 1 To UBound(arrRange, 2)
'store value & column, when found
If arrRange(i, j) <> "" Then
copyVal = arrRange(i, j)
copyCol = j
Exit For
End If
Next j
'loop through output array and paste value
For j = LBound(arrRangeResult, 1) + 1 To UBound(arrRangeResult, 1)
If arrRangeResult(j, 1) = copyCond Then
'paste-down found value to all occurences of article
arrRangeResult(j, copyCol) = copyVal
ElseIf arrRangeResult(j, 1) = "" Then
'early stop, when no article ID is found
Exit For
End If
Next j
Next i
'create output
Range("K:N") = arrRangeResult
End Sub

Resources