Split text in cells from multiple columns in a range of rows into several lines - excel

There is a table with each row needs to be separated so as there is only one name/date/transfer method in a cell in each row. I was able to separate it by names but I struggle to get the dates and e-mail-thing right, because sometimes the cells in respective columns are empty:
Desired result
I did this for names:
Sub splitcells()
Dim splitVals As Variant
Dim totalVals As Long
Set sh1 = ThisWorkbook.Sheets(1)
Set sh2 = ThisWorkbook.Sheets(2)
lrow1 = sh1.Range("A65356").End(xlUp).Row
For j = 2 To lrow1
splitVals = split(sh1.Cells(j, 2), Chr(10))
For i = LBound(splitVals) To UBound(splitVals)
lrow2 = sh2.Range("B65356").End(xlUp).Row
lrow3 = sh2.Range("A65356").End(xlUp).Row
sh2.Cells(lrow3 + 1, 1) = sh1.Cells(j, 1)
sh2.Cells(lrow3 + 1, 2) = splitVals(i)
Next i
Next j
End SubI tried to do the same thing for the rest by moving all the column indicators to column 3 like below, but it doesn`t work properly, filling dates in every cell in a column while some of them should be empty:
Sub splitcells2()
Dim splitVals As Variant
Dim totalVals As Long
Set sh1 = ThisWorkbook.Sheets(1)
Set sh3 = ThisWorkbook.Sheets(3)
lrow1 = sh1.Range("A65356").End(xlUp).Row
For j = 2 To lrow1
splitVals = split(sh1.Cells(j, 3), Chr(10))
For i = LBound(splitVals) To UBound(splitVals)
lrow2 = sh3.Range("B65356").End(xlUp).Row
lrow3 = sh3.Range("A65356").End(xlUp).Row
sh3.Cells(lrow3 + 1, 1) = sh1.Cells(j, 1)
sh3.Cells(lrow3 + 1, 2) = splitVals(i)
Next i
Next j
End Sub

If I understand you correctly, maybe something like this ?
Sub test()
Dim splitVals As Variant
Set sh1 = ThisWorkbook.Sheets(1)
Set sh2 = ThisWorkbook.Sheets(2)
colcount = 6 'change if not the same with the actual table
lrow1 = sh1.Range("A65356").End(xlUp).Row
For j = 2 To lrow1
Set oFill = sh2.Range("A65356").End(xlUp).Offset(1, 0)
With sh1.Cells(j, 2)
If InStr(.Value, Chr(10)) Then
cnt = Len(.Text) - Len(Replace(.Text, Chr(10), "")) + 1
Set oFill = oFill.Resize(cnt, 1)
End If
End With
For i = 1 To colcount
With sh1.Cells(j, i)
If InStr(.Value, Chr(10)) Then
splitVals = Application.Transpose(Split(.Value, Chr(10)))
Else
splitVals = .Value
End If
End With
oFill.Offset(0, i - 1).Value = splitVals
Next i
Next j
End Sub
The code has two loop. The first, loop to each row in column A, the second, loop to each column of the table.
At the first loop, it check if the looped cell offset(j,2) has a line break then it set the oFill (the target cell to be filled) to resize as much as the rows needed.
At the second loop, it check if the looped cell has a line break then it get the value of the looped cell with split function as splitVals variable. If no line break, the splitVals value is the same with the looped cell. Then finally it put the splitVals to the oFill range. Do the same with the rest of the column.
Please be noticed, the code assumes that if in column B there are N names, then the rest of the column (same row) value is either with N lines or blank.
After from VBasic2008 help to my code, please change this line:
lrow1 = sh1.Range("A65356").End(xlUp).Row
Set oFill = sh2.Range("A65356").End(xlUp).Offset(1, 0)
into something like this :
lrow1 = sh1.Range("A" & rows.count).End(xlUp).Row
Set oFill = sh2.Range("A" & rows.count).End(xlUp).Offset(1, 0)

Related

Can I give an if statement by subtracting time?

Is there a way to make my VBA code work for my macro? I want my macro's if function to read the first column of each worksheet in my excel (it has as many sheets as days in the exact month i'm working on), read through each cell and if the currently read cell is equal to or larger than '15 minutes compared to the first cell, then the code would execute, otherwise go to the next cell in the first column.
This is the format of the worksheets i'm working on:
TimeStamp
Power Consumption
Power Production
Inductive Power Consumption
2021.01.01. 8:12:38 +00:00
747
575
3333
2021.01.01. 8:17:35 +00:00
7674
576
3333
... etc ,
And my code looks something like this:
Sub stackoverflow()
Dim w As Integer 'index of worksheets
Dim i As Integer 'row index that steps through the first column
Dim t As Integer 'reference row index i inspect the time to
Dim x As Integer 'row index where i want my data to be printed
Dim j As Integer 'col index
Dim Timediff As Date 'not sure if this is even needed
t = 2
j = 1
x = 1
'Timediff = ("00:15:00")
For w = 3 To ActiveWorkbook.Worksheets.Count 'for every sheet from the 3rd to the last
lRow = ActiveWorkbook.Worksheets(w).Cells(Rows.Count, 1).End(xlUp).Row 'find the last row in each worksheet
lCol = ActiveWorkbook.Worksheets(w).Cells(1, Columns.Count).End(xlToLeft).Column 'find the last column in each worksheet
For x = 2 To lRow
For i = 2 To lRow
'If the time in cell(i,j) is >= then cell(t,j) + 15 minutes,
If Cells(i, j) >= DateAdd("n", 15, Cells(t, j)) Then
ActiveWorkbook.Worksheets(w).Range(i, j).Copy ActiveWorkbook.Worksheets(2).Range(x, j)
ActiveWorkbook.Worksheets(w).Range(i, j + 1).Copy ActiveWorkbook.Worksheets(2).Range(x, j + 1)
'put the new reference point after the found 15 minute mark
t = i + 1
Else
End If
Next i
Next x
Next w
End Sub
So all in all I want my code to notice when the first column reaches a 15 minute mark, and execute some code (subtracting the values of the 15 minute mark from the reference where it started, put the value in the'2nd sheet, and then step to the next cell, and repeat the process).
I'm not entirely sure which information you are attempting to copy to the second worksheet but the following code should be able to get you there pretty easily. Additionally, I've added a function that will fix the format of your TimeStamp field so that excel will recognize it and we can then do math with it
Sub TestA()
Dim xlCellA As Range
Dim xlCellB As Range
Dim xlCellC As Range
Dim i As Integer
Dim j As Integer
Dim lRow As Long
Dim lCol As Long
Set xlCellA = ActiveWorkbook.Worksheets(2).Cells(2, 1)
For i = 3 To ActiveWorkbook.Worksheets.Count
lRow = ActiveWorkbook.Worksheets(i).Cells.SpecialCells(xlCellTypeLastCell).Row
lCol = ActiveWorkbook.Worksheets(i).Cells.SpecialCells(xlCellTypeLastCell).Column
Set xlCellB = ActiveWorkbook.Worksheets(i).Cells(2, 1)
xlCellB.Value = FixFormat(xlCellB.Value)
xlCellB.Offset(0, lCol + 1).Value = "=DATEVALUE(MID(" & xlCellB.Address & ",1,10))+TIMEVALUE(MID(" & xlCellB.Address & ",12,8))"
For j = 3 To lRow
Set xlCellC = ActiveWorkbook.Worksheets(i).Cells(j, 1)
xlCellC.Value = FixFormat(xlCellC.Value)
xlCellC.Offset(0, lCol + 1).Value = "=DATEVALUE(MID(" & xlCellC.Address & ",1,10))+TIMEVALUE(MID(" & xlCellC.Address & ",12,8))"
If xlCellC.Offset(0, lCol + 1) - xlCellB.Offset(0, lCol + 1) >= ((1 / 24) / 4) Then
With xlCellA
.Value = xlCellC.Value
.Offset(0, 1).Value = xlCellC.Offset(0, 1).Value
End With
Set xlCellA = xlCellA.Offset(1, 0)
End If
Next j
Next i
Set xlCellA = Nothing
Set xlCellB = Nothing
Set xlCellC = Nothing
End Sub
Private Function FixFormat(ByVal dStr As String) As String
Dim tmpStr As String
Dim i As Integer
For i = 1 To Len(dStr)
If Mid(dStr, i, 1) <> "." Then
tmpStr = tmpStr & Mid(dStr, i, 1)
Else
If Mid(dStr, i + 1, 1) <> " " Then tmpStr = tmpStr & "-"
End If
Next i
FixFormat = tmpStr
End Function
It's not really clear what needs to happen when the 15min threshold is met but this should get you most of the way there:
Sub stackoverflow()
Dim w As Long, Timediff As Double
Dim wb As Workbook, wsData As Worksheet, wsResults As Worksheet, col As Long
Dim baseRow As Range, dataRow As Range, rngData As Range, resultRow As Range
Timediff = 1 / 24 / 4 '(15min = 1/4 of 1/24 of a day)
Set wb = ActiveWorkbook 'or ThisWorkbook
Set wsResults = wb.Worksheets("Results")
'first row for recording results
Set resultRow = wsResults.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).EntireRow
For w = 3 To wb.Worksheets.Count 'for every sheet from the 3rd to the last
Set rngData = wb.Worksheets(w).Range("A1").CurrentRegion 'whole table
Set rngData = rngData.Offset(1, 0).Resize(rngData.Rows.Count - 1) 'exclude headers
Set baseRow = rngData.Rows(1) 'set comparison row
For Each dataRow In rngData.Rows 'loop over rows in data
If (dataRow.Cells(1).Value - baseRow.Cells(1).Value) > Timediff Then
resultRow.Cells(1).Value = dataRow.Cells(1) 'copy date
For col = 2 To dataRow.Cells.Count 'loop columns and subtract
resultRow.Cells(col).Value = _
dataRow.Cells(col).Value - baseRow.Cells(col).Value
Next col
Set resultRow = resultRow.Offset(1, 0)
Set baseRow = dataRow.Offset(1, 0) 'reset comparison row to next row
End If
Next dataRow
Next w
End Sub

Setting cell equal to random value if cell isn't blank in range

At a high level I am trying to set a cell equal to a random cell within a range. The issue I am having is that in this range I want to pull a random Value from, the Value I am taking is the result of an 'if' expression that either sets the cell to a Value or "". So when I chose the random value I only want to choose cells that have an actual value, not the "".
Does anyone know how to get this expected behavior?
The code below shows what I have tried currently, each large block is commented to help with understanding. The block I need help with replaces the values in each column until the next cell is blank then moves to the next column.
upperBound = 1798
lowerBound = 2
Randomize
'This loop section populates the data area with a static value in cell 9,3 then 9,4 etc..
For j = 3 To 15
val = Cells(9, j).Value
For i = 1 To val
Cells(12 + i, j).Value = Cells(9, j)
Next i
Next j
'This loop section uses the cells already populated down each column and replaces that value with the random value from the other range
Dim x As Integer
' Set numrows = number of rows of data.
For j = 3 To 15
NumRows = Range(Cells(13, j), Cells(13, j).End(xlDown)).Rows.Count
' Select cell 13,j.
Cells(13, j).Select
' Establish "For" loop to loop "numrows" number of times.
For x = 1 To NumRows
ActiveCell.Value = Worksheets("2017 Role IDs").Cells(Int((upperBound - lowerBound + 1) * Rnd + lowerBound), 2).Value
' Selects cell down 1 row from active cell.
ActiveCell.Offset(1, 0).Select
Next
Next j
This is the data before the second block runs. I want to replace the values that just match the number in the second row with the random number in the range:
This is what I would like to look like:
But currently it looks like this because the random selector is taking blank values:
Something like this should work for you:
Sub tgr()
Dim wb As Workbook
Dim wsNums As Worksheet
Dim wsDest As Worksheet
Dim aData As Variant
Dim vData As Variant
Dim aNums() As Double
Dim aResults() As Variant
Dim lNumCount As Long
Dim lMaxRows As Long
Dim lRowCount As Long
Dim ixNum As Long
Dim ixResult As Long
Dim ixCol As Long
Set wb = ActiveWorkbook
Set wsNums = wb.Worksheets("2017 Role IDs")
Set wsDest = wb.ActiveSheet
With wsNums.Range("B2", wsNums.Cells(wsNums.Rows.Count, "B").End(xlUp))
If .Row < 2 Then Exit Sub 'No data
lNumCount = WorksheetFunction.Count(.Cells)
If lNumCount = 0 Then Exit Sub 'No numbers
ReDim aNums(1 To lNumCount)
If .Cells.Count = 1 Then
ReDim aData(1 To 1, 1 To 1)
aData(1, 1) = .Value
Else
aData = .Value
End If
'Load populated numeric cells into the aNums array
For Each vData In aData
If Len(vData) > 0 And IsNumeric(vData) Then
ixNum = ixNum + 1
aNums(ixNum) = vData
End If
Next vData
End With
lMaxRows = Application.Max(wsDest.Range("C9:O9"))
If lMaxRows = 0 Then Exit Sub 'Row count not populated in row 9 for each column
ReDim aResults(1 To WorksheetFunction.Max(wsDest.Range("C9:O9")), 1 To 13)
'Populate each column accordingly and pull a random number from aNums
For ixCol = 1 To UBound(aResults, 2)
If IsNumeric(wsDest.Cells(9, ixCol + 2).Value) Then
For ixResult = 1 To CLng(wsDest.Cells(9, ixCol + 2).Value)
Randomize
aResults(ixResult, ixCol) = aNums(Int(Rnd() * lNumCount) + 1)
Next ixResult
End If
Next ixCol
wsDest.Range("C13").Resize(UBound(aResults, 1), UBound(aResults, 2)).Value = aResults
End Sub

Dynamically reformatting an excel sheet

I have a very messy excel sheet that I'm trying to reformat into something readable. Currently, it is structured as such (each large separation resembles a new cell):
Title1 Var1 Var1_Value Var1.1 Var1.1_Value ... Var1.K Var1.K_Value
Title2 Var2 Var2_Value Var2.1 Var2.1_Value ... Var2.L Var2.L_Value
...
TitleM VarM VarM_Value VarM.1 VarM.1_Value ... VarM.N VarM.N_Value
To clarify, the amount of variables and values per column varies for each row, however every variable will have a value. Ultimately, my end goal is to create something formatted as such:
Title1 Var1 Var1_Value
Title1 Var1.1 Var1.1_Value
...
TitleM VarM.N VarM.N_Value
Where the Title string is repeated for each Var and Var_Value in its row.
I don't know a lot about VBA, so I'm looking for help on the best avenue to achieve this formatting. Here is my thought process in psuedocode below, I tried to format to be VBA-esque when I could.
for idx = 1 To lastRow
' Will likely have to create a function to find
' last filled column in a row -- lastColForRow
tempArray = data(idx,2 To lastColforRow(idx))
for jdx = 1 To length(tempArray)-1 Step 2
newCell(end+1,1) = data(idx,1)
newCell(end+1,2) = tempArray(j)
newCell(end+1,3) = tempArray(j+1)
next jdx
next idx
This code should do it (note that it assumes that there is no header row)
Public Sub Reformat()
Dim lastrow As Long
Dim lastcol As Long
Dim numrows As Long
Dim i As Long, ii As Long
Application.ScreenUpdating = False
With ActiveSheet
lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = lastrow To 1 Step -1
lastcol = .Cells(i, .Columns.Count).End(xlToLeft).Column
'integer division so as to get the number of value pairs
numrows = lastcol \ 2
'only do anything if we have more than one value pair
If numrows > 1 Then
'insert extra rows for extra value pairs
.Rows(i + 1).Resize(numrows - 1).Insert
'copy the titles down to all new rows
.Cells(i, "A").Copy .Cells(i, "A").Resize(numrows)
'a value pair at a time, cut and copy to next new row
For ii = 4 To lastcol Step 2
'target row is current row (i) + the value pair index ((ii /2)-1)
.Cells(i, ii).Resize(, 2).Cut .Cells(i + (ii / 2) - 1, "B")
Next ii
End If
Next i
End With
Application.ScreenUpdating = True
End Sub
This does it with arrays onto a new sheet
Sub climatefreak()
Dim lastrow&
Dim ws As Worksheet
Dim lastcolumn&
Dim idx&
Dim ClmIdx&
Dim tws As Worksheet
Dim i&
Dim trw&
Set tws = Sheets("Sheet3")
Set ws = ActiveSheet
With ws
lastrow = .Cells(.Rows.Count, 1).End(xlUp).Row
For idx = 1 To lastrow
Dim temparr
lastcolumn = .Cells(idx, .Columns.Count).End(xlToLeft).Column
temparr = Range(.Cells(idx, 1), .Cells(idx, lastcolumn)).Value
For i = LBound(temparr, 2) + 1 To UBound(temparr, 2) Step 2
trw = tws.Range("A" & tws.Rows.Count).End(xlUp).Row + 1
tws.Cells(trw, 1) = temparr(UBound(temparr, 1), 1)
tws.Cells(trw, 2).Resize(, 2) = Array(temparr(1, i), temparr(1, i + 1))
Next i
Next idx
End With
End Sub

Loop through first row and if Cell Value = "Item Cost" then loop through that column and carry out subtotal in blanks

Very new to VBA but have managed to learn a lot in the last few weeks and stitch together some code for a project at work.
I am struggling with a loop within a loop.
Essentially I want to find every column in Row 1 that has cell value of "Item Cost" then loop down through each row in that column and place a subtotal in the blanks.
Any help with a solution would be greatly appreciated. It is part of a much larger project but I am at this sticking Point.
Code:
[VBA]
Sub InsertTotals()
Dim sh As Worksheet
Dim rw As Range
Dim RowCount As Integer
RowCount = 0
Set sh = ActiveSheet
For Each rw In sh.Rows
If sh.Cells(rw.Row, 1).Value = "Item Cost" Then
Dim ThisCell As Range
Dim MySum As Double
Set ThisCell = rw.offset(-1)
nxt:
Do While ThisCell <> ""
MySum = MySum + ThisCell
Set ThisCell = ThisCell.offset(1, 0)
Loop
ThisCell.Value = MySum
If ThisCell.offset(1, 0) <> "" Then
Set ThisCell = ThisCell.offset(1, 0)
MySum = 0
GoTo nxt
End If
End If
Next rw
End Sub
[VBA]
I've changed the way that the start and stop of the ranges to be subtotaled were collected. Additionally, every row has a subtotal at the bottom since there is likely an empty cell there.
Sub Insert_SubTotals()
Dim sh As Worksheet
Dim rw As Long, srw As Long, col As Long
Set sh = ActiveSheet
With sh
For col = 1 To Cells(1, Columns.Count).End(xlToLeft).Column
If .Cells(1, col) = "Item Cost" Then
srw = 2
For rw = 2 To .Cells(Rows.Count, col).End(xlUp).Row + 1
If IsEmpty(.Cells(rw, col)) And rw > srw Then
'.Cells(rw, col).Value = Application.Sum(.Range(.Cells(srw, col), .Cells(rw - 1, col)))
.Cells(rw, col).Formula = "=SUM(" & .Cells(srw, col).Address(0, 0) & _
Chr(58) & .Cells(rw - 1, col).Address(0, 0) & ")"
.Cells(rw, col).NumberFormat = _
"[color5]_($* #,##0.00_);[color9]_($* (#,##0.00);[color15]_("" - ""_);[color10]_(#_)"
srw = rw + 1
End If
Next rw
End If
Next col
End With
Set sh = Nothing
End Sub
I applied a blue Accounting style number format to distinguish the subtotals from the rest of the numbers. Modify that as you see fit. The subtotals remain as =SUM(...) formulas. I've added (and commented) a line that would simply leave the values just above the formula assignment.

Excel copy cell values X times with increasing numbers in the end

I have a similar task as in there:
Copy value N times in Excel
But mine is a bit more complex.
So, I have this kind of sheet:
A B
dog-1.txt 3
cat-1.txt 2
rat-1.txt 4
cow-1.txt 1
The final result needs to be the following:
A
dog-1.txt
dog-2.txt
dog-3.txt
cat-1.txt
cat-2.txt
rat-1.txt
rat-2.txt
rat-3.txt
rat-4.txt
cow-1.txt
As you see it doesn't only multiply the cell content X times taken from column B, but it also increases the number in file name the same number of times with 1 step increase.
How could I achieve that?
Try the following (tried and tested):
Sub Extend()
Dim Rng As Range, Cell As Range
Dim WS As Worksheet, NewCell As Range
Dim Dict As Object, NewStr As String
Set WS = ThisWorkbook.Sheets("Sheet1") 'Modify as necessary.
Set Rng = WS.Range("A1:A5") 'Modify as necessary.
Set Dict = CreateObject("Scripting.Dictionary")
For Each Cell In Rng
If Not Dict.Exists(Cell.Value) Then
Dict.Add Cell.Value, Cell.Offset(0, 1).Value
End If
Next Cell
Set NewCell = WS.Range("C1") 'Modify as necessary.
For Each Key In Dict
For Iter = 1 To CLng(Dict(Key))
NewStr = "-" & Iter & ".txt"
NewStr = Mid(Key, 1, InStrRev(Key, "-") - 1) & NewStr
NewCell.Value = NewStr
Set NewCell = NewCell.Offset(1, 0)
Next Iter
Next Key
End Sub
Screenshot (after running):
The logic here is to get each name from the first column, store it as a dictionary key, then get the value beside it and store that that as the key-value. We then iterate inside each of the dictionary's keys, where we use the key-value as the upperbound of the iteration. During each iteration, we modify the string to change its number to the "current digit" of the iteration.
We choose C1 as the initial target cell. Every iteration, we offset it one (1) row below to accommodate the new/next iteration.
Let us know if this helps.
Tested , is this what u wanted :) ? (Working fine in my system)
Sub teststs()
Dim erange As Range
Dim lrow As Integer
Dim cnt As Integer
Dim rnt As Integer
Dim str As String
Dim lrow2 As Integer
With ActiveSheet
lrow = .Range("A" & Rows.Count).End(xlUp).Row ' finding the last row
For Each erange In .Range("A1:A" & lrow) ' loop though each each cell in the A column
cnt = erange.Offset(0, 1).Value
rnt = Mid(erange.Value, InStr(erange.Value, "-") + 1, 1)
For i = 1 To cnt 'Looping to cnt times
With Sheets("Sheet2")
lrow2 = .Range("A" & Rows.Count).End(xlUp).Row + 1
str = Replace(erange.Value, rnt, i, InStr(erange.Value, "-") + 1)
.Range("A" & lrow2).Value = Left(erange.Value, InStr(erange.Value, "-")) & str
End With
Next i
Next erange
End With
End Sub

Resources