Excel VBA - Selecting random rows based on multiple criteria - vba

I have the below code set which takes a list of ticket data, and randomly selected three rows based on the username in Col D.
However, with a recent change in our ticketing system, I now need to update it to not select certain tickets. Specifically, I need only INC and SCTASK tickets to be selected, and not RITM tickets.
I am not quite sure how to add the filter so that tickets with RITM in the ticket number (ticket numbers are in Col A) are not included in this search.
Sub DailyTicketAudit()
'Set parameters and variables
Const sDataSheet As String = "Page 1"
Const sUserCol As String = "D"
Const lHeaderRow As Long = 1
Const lShowRowsPerUser As Long = 3
Const bSortDataByUser As Boolean = False
Dim wb As Workbook, ws As Worksheet
Dim rData As Range, rShow As Range
Dim aData() As Variant, aUserRows() As Variant
Dim i As Long, j As Long, k As Long, lRandIndex As Long, lTotalUnqUsers As Long, lMaxUserRows As Long
Set wb = Workbooks.Open("D:\Users\stefan.bagnato\Desktop\Raw Data Files\Audit Tickets Created")
Set ws = ActiveWorkbook.Sheets(sDataSheet)
Sheets("Page 1").name = "Audit Tickets"
'Work with the data range set by parameters
With ws.Range(sUserCol & lHeaderRow + 1, ws.Cells(ws.Rows.Count, sUserCol).End(xlUp))
If .Row < lHeaderRow + 1 Then
MsgBox "No data found in [" & sDataSheet & "]" & Chr(10) & _
"Verify column containing users is Column [" & sUserCol & "] or correct sUserCol in code." & Chr(10) & _
"Verify header row is Row [" & lHeaderRow & "] or correct lHeaderRow in code." & Chr(10) & _
"Once corrections have been made and data is available, try again."
Exit Sub
End If
lTotalUnqUsers = Evaluate("SUMPRODUCT((" & .Address(external:=True) & "<>"""")/COUNTIF(" & .Address(external:=True) & "," & .Address(external:=True) & "&""""))")
lMaxUserRows = Evaluate("max(countif(" & .Address(external:=True) & "," & .Address(external:=True) & "))")
If bSortDataByUser Then .Sort .Cells, xlAscending, Header:=xlNo
Set rData = .Cells
aData = .Value
ReDim aUserRows(1 To lTotalUnqUsers, 1 To 3, 1 To lMaxUserRows)
End With
'Load all available rows into the results array, grouped by the user
For i = LBound(aData, 1) To UBound(aData, 1)
For j = LBound(aUserRows, 1) To UBound(aUserRows, 1)
If IsEmpty(aUserRows(j, 1, 1)) Or aUserRows(j, 1, 1) = aData(i, 1) Then
If IsEmpty(aUserRows(j, 1, 1)) Then aUserRows(j, 1, 1) = aData(i, 1)
k = aUserRows(j, 2, 1) + 1
aUserRows(j, 2, 1) = k
aUserRows(j, 3, k) = i + lHeaderRow
Exit For
End If
Next j
Next i
'Select random rows up to lShowRowsPerUser for each user from the grouped results array
For j = LBound(aUserRows, 1) To UBound(aUserRows, 1)
Do
Randomize
lRandIndex = Int(Rnd() * aUserRows(j, 2, 1)) + 1
If Not rShow Is Nothing Then
Set rShow = Union(rShow, ws.Cells(aUserRows(j, 3, lRandIndex), sUserCol))
Else
Set rShow = ws.Cells(aUserRows(j, 3, lRandIndex), sUserCol)
End If
Loop While rShow.Cells.Count < j * Application.Min(lShowRowsPerUser, aUserRows(j, 2, 1))
Next j
rData.EntireRow.Hidden = True
rShow.EntireRow.Hidden = False
'Format table
'Sort by Opened By
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
Worksheets("Audit Tickets").Sort.SortFields.Add Key:=Range("D1"), SortOn:=xlSortOnValues, Order:=xlAscending
With Worksheets("Audit Tickets").Sort
.SetRange Range("A2:G" & LastRow)
.Orientation = xlTopToBottom
.Apply
End With
'Widen columns
Range("A:B,G:G").ColumnWidth = 15
Columns("C:D").ColumnWidth = 18
Columns("E:E").ColumnWidth = 50
Columns("F:F").ColumnWidth = 22
'Wrap text
Range("E1:E" & LastRow).WrapText = True
End Sub

Far more efficient, assuming aData holds all the data and the first column is tickets, is to simply process only the two of interest with the following.
Change 1 in aData(i, 1) to whichever column holds the items of interest in the array.
For i = LBound(aData, 1) To UBound(aData, 1)
If aData(i, 1) = "INC" Or aData(i, 1) = "SCTASK" Then
For j = LBound(aUserRows, 1) To UBound(aUserRows, 1)
''other code
End If
Next i

You could use advanced filter:
Sheets("Emps").Range("A1:D8").AdvancedFilter Action:=xlFilterCopy, _
CriteriaRange:=Sheets("Emps").Range("F5:F6"), CopyToRange:=Range("A1:B1"), _
Unique:=False
Data to selectively copy:
Data copied:
Reference this short YouTube video; You can record a marco to help yourself with the code also:
https://www.youtube.com/watch?v=bGUKjXmEi2E
A more thorough tutorial is found here:
http://www.contextures.com/xladvfilter01.html
This tutorial shows how to get the source data from outside Excel:
https://www.extendoffice.com/documents/excel/4189-excel-dynamic-filter-to-new-sheet.html
This tutorial shows how to split data values based on a column to different sheets (Fruit column; Apple sheet, Pear sheet, etc.):
https://www.extendoffice.com/documents/excel/2884-excel-save-filtered-data-new-sheet-workbook.html

Related

Excel VBA - Randomly select 3 rows per username

I have a large list of tickets with a total of 6 different user names. What I need the code to do is randomly select 3 rows of data per user (18 total) and hide the rest of the rows, as I only need to see the selected rows.
The code will be something like the below, but I am not sure how to write the "random" part.
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
With Range("A2:F" & LastRow)
*Select 3 random rows for user A*
*Select 3 random rows for user B*
*The same for C-F*
*Hide all other rows*
End With
Found this to be an interesting challenge. Something like this should work for you. Commented code for clarity.
Sub tgr()
'Adjust these parameters as necessary
Const sDataSheet As String = "Sheet1"
Const sUserCol As String = "A"
Const lHeaderRow As Long = 1
Const lShowRowsPerUser As Long = 3
Const bSortDataByUser As Boolean = False
'Declare variables
Dim ws As Worksheet
Dim rData As Range
Dim rShow As Range
Dim aData() As Variant
Dim aUserRows() As Variant
Dim lTotalUnqUsers As Long
Dim lMaxUserRows As Long
Dim i As Long, j As Long, k As Long
Dim lRandIndex As Long
'Test if sDataSheet name provided exists in ActiveWorkbook
On Error Resume Next
Set ws = ActiveWorkbook.Sheets(sDataSheet)
On Error GoTo 0
If ws Is Nothing Then
MsgBox "No sheet named [" & sDataSheet & "] found in " & ActiveWorkbook.Name & Chr(10) & _
"Correct sDataSheet in code and try again."
Exit Sub
End If
ws.Cells.EntireRow.Hidden = False 'Reset rows to show all data
'Work with the data range set by parameters
With ws.Range(sUserCol & lHeaderRow + 1, ws.Cells(ws.Rows.Count, sUserCol).End(xlUp))
'Verify data exists in specified location
If .Row < lHeaderRow + 1 Then
MsgBox "No data found in [" & sDataSheet & "]" & Chr(10) & _
"Verify column containing users is Column [" & sUserCol & "] or correct sUserCol in code." & Chr(10) & _
"Verify header row is Row [" & lHeaderRow & "] or correct lHeaderRow in code." & Chr(10) & _
"Once corrections have been made and data is available, try again."
Exit Sub
End If
lTotalUnqUsers = Evaluate("SUMPRODUCT((" & .Address(external:=True) & "<>"""")/COUNTIF(" & .Address(external:=True) & "," & .Address(external:=True) & "&""""))") 'Get total unique users
lMaxUserRows = Evaluate("max(countif(" & .Address(external:=True) & "," & .Address(external:=True) & "))") 'Get max rows per user
If bSortDataByUser Then .Sort .Cells, xlAscending, Header:=xlNo 'If bSortByUser is set to True, then sort the data
Set rData = .Cells 'Store the data in a range object for later use
aData = .Value 'Load the data into an array to speed operations
ReDim aUserRows(1 To lTotalUnqUsers, 1 To 3, 1 To lMaxUserRows) 'Ready the results array that random rows will be selected from
End With
'Load all available rows into the results array, grouped by the user
For i = LBound(aData, 1) To UBound(aData, 1)
For j = LBound(aUserRows, 1) To UBound(aUserRows, 1)
If IsEmpty(aUserRows(j, 1, 1)) Or aUserRows(j, 1, 1) = aData(i, 1) Then 'Find correct user
If IsEmpty(aUserRows(j, 1, 1)) Then aUserRows(j, 1, 1) = aData(i, 1) 'If user isn't in results array yet, add it
k = aUserRows(j, 2, 1) + 1 'Increment row counter for this user
aUserRows(j, 2, 1) = k
aUserRows(j, 3, k) = i + lHeaderRow 'Load this row into this user's group of rows
Exit For
End If
Next j
Next i
'Select random rows up to lShowRowsPerUser for each user from the grouped results array
For j = LBound(aUserRows, 1) To UBound(aUserRows, 1)
Do
Randomize
lRandIndex = Int(Rnd() * aUserRows(j, 2, 1)) + 1
If Not rShow Is Nothing Then
Set rShow = Union(rShow, ws.Cells(aUserRows(j, 3, lRandIndex), sUserCol))
Else
Set rShow = ws.Cells(aUserRows(j, 3, lRandIndex), sUserCol)
End If
Loop While rShow.Cells.Count < j * Application.Min(lShowRowsPerUser, aUserRows(j, 2, 1))
Next j
rData.EntireRow.Hidden = True 'Hide all relevant rows
rShow.EntireRow.Hidden = False 'Only show the rows that have been randomly selected
End Sub

VBA – Alteration in a code

I have a code which performs different checks for 3 different columns. It works absolutely fine, but I want some alteration. Let’s see the code first.
Sub test()
On Error Resume Next
Dim cel As Range
Dim colCStr As String, colDStr As String, colEStr As String
Set ws = Sheets("Sheet1")
With ws
LastRow = .Cells(.Rows.count, "C").End(xlUp).row
For Each cel In .Range("C2:C" & LastRow)
'condition for Column C (cell not empty & characters in cell are alphabet)
For i = 1 To Len(cel)
If Not (Not IsEmpty(cel) And Asc(UCase(cel)) > 64 And Asc(UCase(cel)) < 91) Then
colCStr = colCStr & "," & cel.row
Exit For
End If
Next i
'condition for Column D (cell is numeric & length of cell value is 2 or 3)
If Not (IsNumeric(cel.Offset(0, 1)) And (Len(cel.Offset(0, 1)) = 2 Or Len(cel.Offset(0, 1)) = 3)) Then
colDStr = colDStr & "," & cel.Offset(0, 1).row
End If
'condition for Column E (cell is numeric & length of cell value is 7 or 8 or cell value is 0)
If Not (IsNumeric(cel.Offset(0, 2)) And (Len(cel.Offset(0, 2)) = 7 Or Len(cel.Offset(0, 2)) = 8) Or cel.Offset(0, 2) = 0) Then
colEStr = colEStr & "," & cel.Offset(0, 2).row
End If
Next cel
End With
'disply message box only if there's error
If Len(colCStr) > 0 Then
Sheets("Error_sheet").Range("A2" & row).Value = "Errors in Column C" & " : " & Mid(colCStr, 2, Len(colAStr))
If Len(colDStr) > 0 Then
Sheets("Error_sheet").Range("B2" & row).Value = "Errors in Column D" & " : " & Mid(colDStr, 2, Len(colDStr))
If Len(colEStr) > 0 Then
Sheets("Error_sheet").Range("C2" & row).Value = "Errors in Column E" & " : " & Mid(colEStr, 2, Len(colEStr))
Else
End If
End If
End Sub
The code performs following checks:
Column C: Cell not empty & characters in cell are alphabet (Actually I don’t want to perform any checks over here in Column C, but if I delete the lines of code which validate Column C the rest of code stops getting executed too).
Column D: Cell is numeric & length of cell value is 2 or 3 (I want the absolutely same checks).
Column E: Cell is numeric & length of cell value is 7 or 8 or cell value is 0 (I want the absolutely same checks).
I appreciate your time and efforts.
This version doesn't use Offset so it should be easier to update (and more efficient)
Option Explicit
Public Sub CheckColDandE()
Dim ws As Worksheet, lr As Long, arr As Variant, r As Long
Dim dOk As Boolean, eOk As Boolean, dErr As String, eErr As String
Set ws = ThisWorkbook.Worksheets("Sheet1")
lr = ws.Cells(ws.Rows.Count, "C").End(xlUp).Row
arr = ws.Range("D2:E" & lr)
For r = 1 To lr - 1
dOk = IsNumeric(arr(r, 1)) And arr(r, 1) > 9 And arr(r, 1) < 1000
eOk = IsNumeric(arr(r, 2))
eOk = eOk And (arr(r, 2) > 999999 And arr(r, 2) < 100000000 Or arr(r, 2) = 0)
If Not dOk Then dErr = dErr & r + 1 & ", "
If Not eOk Then eErr = eErr & r + 1 & ", "
Next
With ws.Range("D" & lr + 1 & ":E" & lr + 1)
.Value2 = vbNullString
If Len(dErr) > 0 Then .Cells(1) = "Rows with Errors: " & Left(dErr, Len(dErr) - 2)
If Len(eErr) > 0 Then .Cells(2) = "Rows with Errors: " & Left(eErr, Len(eErr) - 2)
End With
End Sub
Delete the following lines (and update your comments! The column names in comments and code ae not the same):
'condition for Column A (cell not empty & characters in cell are alphabet)
For i = 1 To Len(cel)
If Not (Not IsEmpty(cel) And Asc(UCase(cel)) > 64 And Asc(UCase(cel)) < 91) Then
colCStr = colCStr & "," & cel.row
Exit For
End If
Next i

Having trouble manipulating seriescollections on vba charts

Is something outdated on msdn (here, in particular https://msdn.microsoft.com/en-us/library/office/ff821866.aspx ), or am I just really dumb? I have some code:
'Cel is a range, CelCol is a long, i, j, k, l are all long, LastColumn is a long, GraphDataStationBlock is a constant (long), wsh1 and wsh2 are worksheets, and chrt is the chart
'option explicit is on so if I missed mentioning a variable, it *was* declared, I just missed it
'I tried setting the source data both before and after all this just in case it mattered but nothing changed
j = 1
For Each Cel In wsh1.Range(wsh1.Cells(GraphDataStationBlock * i + 1, 1), wsh1.Cells(GraphDataStationBlock * (i + 1), 1)).Cells
If Cel.Offset(0, 1) <> vbNullString Then
wsh1.Cells(Cel.Row, CelCol) = WorksheetFunction.Max(wsh2.Range(wsh2.Cells(Cel.Row, 3), wsh2.Cells(Cel.Row, 26)))
chrt.SeriesCollection(j).XValues = wsh1.Range("B3:B5") 'all but straight from the msdn website, still doesn't work!
'I also tried a standard range(cell1, cell2) format (not letter/number) in case that would work but it did not, even though msdn says ranges should be fine
chrt.SeriesCollection(j).name = wsh1.Cells(Cel.Row, 1) & vbSpace & wsh1.Cells(Cel.Row, 2)
'always gives "unable to get name property of the series class"
j = j + 1
End If
Next Cel
'there's a lot more besides this of course but this is just the problematic part
long story short, it copies a row maxima from sheet A (representing hourly data for a single day), puts it into the appropriate column on sheet B (representing each day of that month), and then maps it onto chart C. Or it's supposed to. In practice, it copies the maxima over just fine and then I get an endless series of run-time error 1004, for both the name and the XValues portion of the seriescollection.
I haven't really built all that many charts - namely, none - so I'm kind of blundering around a bit, and if there's a better method of making a chart then I'm all ears, but otherwise...
Edit: the data it's getting this from is pretty straightforward - day numbers (1 to the last day of the month) across the top, labels in the left two columns, and then data that gets filled in each day. Come to think of it, it wouldn't matter that at any given time a fair amount of the source data is empty, right?
Also, more code. This still isn't nearly the entire program, but it covers a lot more than the above loop.
Option Explicit
'vbSpace will be mentioned, I just saved it as a public variable equalling " " because I find it easier to type
Sub UpdateMonthlyGraphData(ByVal wsh2 As Worksheet, ByVal Yesterdate As Date, ByVal FirstDate As Date, ByVal LastDate As Date)
'wsh2 has the daily information
With Excel.Application
.DisplayAlerts = False
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
Dim LastColumn As Long, i As Long, j As Long, SheetCount As Long, CelCol As Long
Dim FirstDay As Long, LastDay As Long, FirstWeekday As Long
Dim Yesteryear As Long, Yestermonth As Long, Yesterday As Long
Dim FormattedMonth As String, ChartType As String
Dim Cel As Range
Dim SerCol As Series
Dim wsh1 As Worksheet
Dim wb1 As Workbook
Dim chrt As Chart
Set wb1 = ThisWorkbook
FirstWeekday = Weekday(FirstDate, vbSunday)
LastDay = Day(LastDate)
FormattedMonth = Format(Yesterdate, "MMM YYYY")
SheetCount = wb1.Sheets.Count
Yesteryear = Year(Yesterdate)
Yestermonth = Month(Yesterdate)
Yesterday = Day(Yesterdate)
LastColumn = 2 + LastDay 'Set the data range to the appropriate size according the number of days in the month
If Not CBool(WorksheetExists(MonthName(Yestermonth, True) & vbSpace & Yesteryear & vbSpace & "Monthly Graph Data")) Then
'WorksheetExists just looks thruogh each worksheet and either returns the index of the sheet named (if it exists) or zero (if not).
SheetCount = wb1.Sheets.Count 'Monthly Data sheet creation
wb1.Worksheets("Template Monthly Graph Data").Copy after:=wb1.Sheets(SheetCount)
SheetCount = SheetCount + 1
Set wsh1 = wb1.Sheets(SheetCount)
wsh1.Move after:=wb1.Worksheets("Template WOT Main")
wsh1.name = MonthName(Yestermonth, True) & vbSpace & Yesteryear & vbSpace & "Monthly Graph Data"
LastDay = Day(DateSerial(Yesteryear, Yestermonth + 1, 0))
For i = 1 To 31 'only sort of tested code, be sure to check in on it to make sure it works properly
If i <= LastDay Then
wsh1.Cells(2, i + 2) = i & " : " & WeekdayName(Weekday(DateSerial(Yesteryear, Yestermonth, i), vbSunday), True)
Else
wsh1.Cells(2, i + 2) = "N/A"
End If
Next i
Else
Set wsh1 = wb1.Worksheets(MonthName(Yestermonth, True) & vbSpace & Yesteryear & vbSpace & "Monthly Graph Data")
LastDay = DateSerial(Yesteryear, Yestermonth + 1, 0)
End If
Set Cel = wsh1.Range(wsh1.Cells(2, 3), wsh1.Cells(2, LastDay + 2)).Cells.Find(Yesterday & " : " & WeekdayName(Weekday(Yesterdate, vbSunday), True))
'importing yesterday's data
If Not Cel Is Nothing Then
Set Cel = wsh1.Cells(GraphDataStationBlock - 3, Cel.Column)
CelCol = Cel.Column
Cel = WorksheetFunction.Max(wsh2.Range(wsh2.Cells(GraphDataStationBlock - 3, 3), wsh2.Cells(GraphDataStationBlock - 3, 26)))
Cel.Offset(1, 0) = WorksheetFunction.Max(wsh2.Range(wsh2.Cells(GraphDataStationBlock - 2, 3), wsh2.Cells(GraphDataStationBlock - 2, 26)))
wsh1.Range(Cel, Cel.Offset(1, 0)).NumberFormat = "0.0"
Else
MsgBox "Monthly Graph Data Sheet did not initialize correctly. Please review code and results."
Exit Sub 'just in case?
End If
For i = 0 To 2
Select Case i
Case 0
ChartType = "Winding"
Case 1
ChartType = "Oil"
Case 2
ChartType = "MW"
End Select
If Not CBool(ChartExists(FormattedMonth & " Monthly " & ChartType & " Graph")) Then 'the chart counterpart to the above "WorksheetExists"
wb1.Charts("Template Monthly " & ChartType & " Graph").Copy after:=wb1.Sheets(SheetCount)
SheetCount = SheetCount + 1
Set chrt = wb1.Sheets(SheetCount)
chrt.name = FormattedMonth & " Monthly " & ChartType & " Graph"
chrt.Move before:=wb1.Worksheets(FormattedMonth & " Monthly Graph Data")
chrt.Legend.Font.Size = 10 'was there before, keep it I guess?
If i < 2 Then
chrt.ChartTitle.Characters.Text = FormattedMonth & vbSpace & ChartType & " Temp Peaks"
Else
chrt.ChartTitle = FormattedMonth & vbSpace & ChartType & " Peaks"
End If
Else
Set chrt = wb1.Charts(FormattedMonth & " Monthly " & ChartType & " Graph")
End If
chrt.SetSourceData Source:=Union(wsh1.Range(wsh1.Cells(GraphDataStationBlock * i + 3, 3), wsh1.Cells(GraphDataStationBlock * i + 2 + Feeders138, LastColumn)), wsh1.Range(wsh1.Cells(GraphDataStationBlock * (i + 1) - Feeders416 - 4, 3), wsh1.Cells(GraphDataStationBlock * (i + 1) - 5, LastColumn))), PlotBy:=xlRows
For Each SerCol In chrt.SeriesCollection
Debug.Print SerCol.ChartType 'this didn't work
Stop 'it'd be convenient if it did, though, and if I can get the code to work at all, I will probably try and make it look all pretty and compact like this
Next SerCol
j = 1
For Each Cel In wsh1.Range(wsh1.Cells(GraphDataStationBlock * i + 1, 1), wsh1.Cells(GraphDataStationBlock * (i + 1), 1)).Cells
If Cel.Offset(0, 1) <> vbNullString Then
wsh1.Cells(Cel.Row, CelCol) = WorksheetFunction.Max(wsh2.Range(wsh2.Cells(Cel.Row, 3), wsh2.Cells(Cel.Row, 26)))
'noted limitation: as is this requires that the order of the feeders in the monthly and daily graph data sheets be structured the same way
chrt.SeriesCollection(j).XValues = wsh1.Range("B3:B5") '"='" & wsh1.name & "'!" & wsh1.Range(wsh1.Cells(2, 3), wsh1.Cells(2, LastColumn)).Address '"='" & Yesterdate & " Graph Data'!R2C3:R2C26"
chrt.SeriesCollection(j).name = wsh1.Cells(Cel.Row, 1) & vbSpace & wsh1.Cells(Cel.Row, 2)
j = j + 1
End If
Next Cel
Next i

Evaluate and Store Complex Expression in Excel VBA

I am working on an accounting VBA program that will post Journal entries to a Ledger, and then generate trial balances (i.e. print out the values on a new sheet following "Bal. " in the Ledger). To do this, I need a way to assign the numerical part of the balance cells to a variable or collection. Unfortunately, when I use Debug.Print I see the only value stored is 0 (I am testing just with Common Stock). My expression is: y = Application.Evaluate("=SUM(R[-" & x & "]C:R[-1]C)-SUM(R[-" & x & "]C[1]:R[-1]C[1])") where y represents the balance of Common Stock. How do I properly store the balance value in a variable?
' TODO BE ABLE TO RUN MULTIPLE TIMES
' CHECK FOR POSTED MARK & START WRITING WHEN
' r = "one of the keys", or just creates new Ledger Worksheet every time
Sub MacCompileData()
Application.ScreenUpdating = False
Dim lastRow As Long, x As Long
Dim data, Key
Dim r As Range
Dim cLedger As Collection, cList As Collection
Set cLedger = New Collection
With Worksheets("Journal")
lastRow = .Range("B" & .Rows.Count).End(xlUp).Row
For x = 2 To lastRow
Key = Trim(.Cells(x, 2))
On Error Resume Next
Set cList = cLedger(Key)
If Err.Number <> 0 Then
Set cList = New Collection
cLedger.Add cList, Key
End If
On Error GoTo 0
cLedger(Key).Add Array(.Cells(x, 1).Value, .Cells(x, 3).Value, .Cells(x, 4).Value)
Worksheets("Journal").Cells(x, 5).Value = ChrW(&H2713)
Next
End With
With Worksheets("Ledger")
Dim IsLiability As Boolean
Dim y As Integer
For Each r In .Range("A1", .Range("A" & .Rows.Count).End(xlUp))
If r <> "" Then
On Error Resume Next
Key = Trim(r.Text)
If Key = "LIABILITIES" Then
IsLiability = True
End If
data = getLedgerArray(cLedger(Key))
If Err.Number = 0 Then
Set list = cLedger(Key)
x = cLedger(Key).Count
With r.Offset(2).Resize(x, 3)
.Insert Shift:=xlDown, CopyOrigin:=r.Offset(1)
.Offset(-x).Value = data
If IsLiability Then
.Offset(0, 2).Resize(1, 1).FormulaR1C1 = "=""Bal. "" & TEXT(SUM(R[-" & x & "]C:R[-1]C)-SUM(R[-" & x & "]C[1]:R[-1]C[1]),""$#,###"")"
' LOOK HERE FOR Y
y = Application.Evaluate("=SUM(R[-" & x & "]C:R[-1]C)-SUM(R[-" & x & "]C[1]:R[-1]C[1])")
Debug.Print "Common Stock Balance Equals "; y
Else
.Offset(0, 1).Resize(1, 1).FormulaR1C1 = "=""Bal. "" & TEXT(SUM(R[-" & x & "]C:R[-1]C)-SUM(R[-" & x & "]C[1]:R[-1]C[1]),""$#,###"")"
End If
r.Offset(1).EntireRow.Delete
End With
End If
On Error GoTo 0
End If
Next
End With
Application.ScreenUpdating = True
End Sub
Function getLedgerArray(c As Collection)
Dim data
Dim x As Long
ReDim data(1 To c.Count, 1 To 3)
For x = 1 To c.Count
data(x, 1) = c(x)(0)
data(x, 2) = c(x)(1)
data(x, 3) = c(x)(2)
Next
getLedgerArray = data
End Function
Here is a solution that I was able to figure out, though I am not sure if it is the most efficient. In line before the formula is set, I set a Range named BalanceCell to the cell where the formula will be written. I then used the Mid Function to get the string number value from the cell (since the length of "Bal. " is always 5 characters) after the formula is put into BalanceCell.
If IsLiability Then
Set BalanceCell = .Offset(0, 2).Resize(1, 1)
BalanceCell.FormulaR1C1 = "=""Bal. "" & TEXT(SUM(R[-" & x & "]C:R[-1]C)-SUM(R[-" & x & "]C[1]:R[-1]C[1]),""$#,###"")"
y = Mid(BalanceCell.Value, 6, Len(BalanceCell.Value))
Debug.Print "Common Stock Balance is "; y

Optimise excel VBA code - combine resident address

I have done the following 2 VBA code in excel. Main purpose is to combine multiple address rows into a single line. Problem is it takes forever to run. Is there anyway I can optimise it?
The data is as such, there is a case# for each of the customer address. The customer address can be split into multiple rows. Example: "Address row 1 - Block 56", "Address row 2 - Parry Avenue", "address row 3 - Postal code". There is a blank space between each new address.
My purpose is to combine the address into a single line, and remove the empty rows in between the case numbers eg "Block 56 Parry Avenue Postal code". There are approx 26K case numbers.
Sub test()
Dim l As Long
Dim lEnd As Long
Dim wks As Worksheet
Dim temp As String
Application.EnableEvents = False
Application.ScreenUpdating = False
Set wks = Sheets("data")
wks.Activate
lEnd = ActiveSheet.UsedRange.Rows.Count
For l = 3 To lEnd
If Not IsEmpty(Cells(l, 1)) Then
Do Until IsEmpty(Cells(l + 1, 4))
temp = Cells(l, 4).Value & " " & Cells(l + 1, 4).Value
Cells(l, 4).Value = temp
Cells(l + 1, 4).EntireRow.Delete
Loop
Else: Cells(l, 1).EntireRow.Delete
Do Until IsEmpty(Cells(l + 1, 4))
temp = Cells(l, 4).Value & " " & Cells(l + 1, 4).Value
Cells(l, 4).Value = temp
Cells(l + 1, 4).EntireRow.Delete
Loop
End If
Next l
End Sub
and the 2nd code I tried
Sub transformdata()
'
Dim temp As String
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Range("A3").Select
Do Until IsEmpty(ActiveCell) And IsEmpty(ActiveCell.Offset(1, 0))
Do Until IsEmpty(ActiveCell.Offset(1, 3))
temp = ActiveCell.Offset(, 3).Value & " " & ActiveCell.Offset(1, 3).Value
ActiveCell.Offset(, 3).Value = temp
ActiveCell.Offset(1, 3).EntireRow.Delete
Loop
ActiveCell.Offset(1, 0).EntireRow.Delete
ActiveCell.Offset(1, 0).Select
Loop
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Change the line lEnd = ActiveSheet.UsedRange.Rows.Count. Incorrect way of finding last row. You may want to see This
To delete rows where Cells(l, 1) is empty, use Autofilter. See This
Do not delete rows in a straight loop. Use a reverse loop. Or what you could do is identify the cells that you want to delete in a loop and then delete them in one go after the loop. You may want to see This
Here is a basic example.
Let's say your worksheet looks like this
If you run this code
Sub test()
Dim wks As Worksheet
Dim lRow As Long, i As Long
Dim temp As String
Application.ScreenUpdating = False
Set wks = Sheets("data")
With wks
'~~> Find Last Row
lRow = .Range("C" & .Rows.Count).End(xlUp).Row
For i = lRow To 2 Step -1
If Len(Trim(.Range("C" & i).Value)) <> 0 Then
If temp = "" Then
temp = .Range("C" & i).Value
Else
temp = .Range("C" & i).Value & "," & temp
End If
Else
.Range("D" & i + 1).Value = temp
temp = ""
End If
Next i
End With
End Sub
You will get this output
Now simply run the autofilter to delete the rows where Col D is empty :) I have already give you the link above for the same.
The code below will copy all the data into an array, consolidate it, and add it to a new worksheet. You'll need to make COLUMNCOUNT = the number of columns that contain data.
Sub TransformData2()
Const COLUMNCOUNT = 4
Dim SourceData, NewData
Dim count As Long, x1 As Long, x2 As Long, y As Long
SourceData = Range("A" & Range("D" & Rows.count).End(xlUp).Row, Cells(3, COLUMNCOUNT))
For x1 = 1 To UBound(SourceData, 1)
count = count + 1
If count = 1 Then
ReDim NewData(1 To 4, 1 To count)
Else
ReDim Preserve NewData(1 To 4, 1 To count)
End If
For y = 1 To UBound(SourceData, 2)
NewData(y, count) = SourceData(x1, y)
Next
x2 = x1 + 1
Do
NewData(4, count) = NewData(4, count) & " " & SourceData(x2, 4)
x2 = x2 + 1
If x2 > UBound(SourceData, 1) Then Exit Do
Loop Until IsEmpty(SourceData(x2, 4))
x1 = x2
Next
ThisWorkbook.Worksheets.Add
Range("A1").Resize(UBound(NewData, 2), UBound(NewData, 1)).Value = WorksheetFunction.Transpose(NewData)
End Sub