VBA sum row base on different two condition and delete row - excel

I would like to create a macro that will sum the unit column base by Product & DC. It will populate out the same product name, code with dc code and code without.
Sub Button1_Click()
For i = 2 to lstrow
Next I
MsgBox ("Done")
End Sub
Product
DC
Unit
ABC
0
2
ABC
1234
4
ABC
1234
4
DEF
5678
2
DEF
5678
2
GHI
9012
2
I want to the output as below:-
Product
Unit with DC Code
Unit Without DC Code
ABC
6
2
DEF
4
0
GHI
2
0

Sum Unique
Adjust the values in the constants section.
If you want to overwrite ('mimic' RemoveDuplicates), then just replace Sheet2 with Sheet1 and uncomment the Delete below section. Keep in mind that you won't be able to undo.
The Code
Option Explicit
Sub sumUnique()
' Define constants.
Const sName As String = "Sheet1"
Const sFirstCellAddress As String = "A2"
Const dName As String = "Sheet2"
Const dFirstCellAddress As String = "A1"
' Define workbook.
Dim wb As Workbook: Set wb = ThisWorkbook
' Write values from Source Range to Data Array.
Dim LastRow As Long
Dim Data As Variant
With wb.Worksheets(sName).Range(sFirstCellAddress)
LastRow = .Worksheet.Cells(.Worksheet.Rows.Count, .Column).End(xlUp).Row
Data = .Resize(LastRow - .Row + 1, 3)
End With
' Write unique values from Data Array to Unique Dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim arr(1 To 2) As Variant
Dim Key As Variant
Dim cArr As Variant
Dim r As Long
For r = 1 To UBound(Data, 1)
Key = Data(r, 1)
If Not IsError(Key) Then
If Not dict.Exists(Key) Then
dict.Add Key, arr
End If
If Data(r, 2) = 0 Then
cArr = dict(Key)
cArr(2) = cArr(2) + Data(r, 3)
dict(Key) = cArr
Else
cArr = dict(Key)
cArr(1) = cArr(1) + Data(r, 3)
dict(Key) = cArr
End If
End If
Next r
' Write values from Unique Dictionary to Result Array.
Dim rCount As Long: rCount = dict.Count + 1
Dim Result As Variant: ReDim Result(1 To rCount, 1 To 3)
Result(1, 1) = "Product"
Result(1, 2) = "Unit With DC Code"
Result(1, 3) = "Unit Without DC Code"
If rCount > 1 Then
r = 1
For Each Key In dict.Keys
r = r + 1
Result(r, 1) = Key
Result(r, 2) = CLng(dict(Key)(1))
Result(r, 3) = CLng(dict(Key)(2))
Next Key
End If
' Write values from Result Array to Destination Range.
With wb.Worksheets(dName).Range(dFirstCellAddress).Resize(, 3)
.Resize(rCount).Value = Result
' Delete below.
'.Resize(.Worksheet.Rows.Count - .Row - rCount + 1) _
.Offset(rCount).ClearContents
End With
End Sub

Here is an example of VBA code that should do the trick:
Sub SubWhyNotSUMIFS()
'Declarations.
Dim RngResult As Range
Dim RngTarget As Range
Dim RngSource As Range
Dim StrPreviousProduct As String
'Settings.
Set RngResult = Range("D1")
Set RngSource = Range("A2:C2")
Set RngSource = Range(RngSource, RngSource.End(xlDown))
'Creating space for the result.
RngResult.EntireColumn.Insert
RngResult.EntireColumn.Insert
RngResult.EntireColumn.Insert
'Reporting the headers of the result list.
Set RngResult = RngResult.Offset(0, -3)
RngResult.Value = "Product"
RngResult.Offset(0, 1) = "Unit with DC Code"
RngResult.Offset(0, 2) = "Unit Without DC Code"
'Covering each cell of the first column of RngSource.
For Each RngTarget In RngSource.Columns(1).Cells
'Checking if it's a different product.
If StrPreviousProduct <> RngTarget.Value Then
'Setting RngResult for a new row.
Set RngResult = RngResult.Offset(1, 0)
'Changing StrPreviousProduct.
StrPreviousProduct = RngTarget.Value
'Reporting the results.
RngResult.Value = RngTarget.Value
RngResult.Offset(0, 1).Value = Excel.WorksheetFunction.SumIfs(RngSource.Columns(3), RngSource.Columns(1), RngTarget.Value, RngSource.Columns(2), "<>0")
RngResult.Offset(0, 2).Value = Excel.WorksheetFunction.SumIfs(RngSource.Columns(3), RngSource.Columns(1), RngTarget.Value, RngSource.Columns(2), 0)
End If
Next
End Sub
You can reverse-engineer it. The lines you will be most interested into are:
RngResult.Offset(0, 1).Value = Excel.WorksheetFunction.SumIfs(RngSource.Columns(3), RngSource.Columns(1), RngTarget.Value, RngSource.Columns(2), "<>0")
RngResult.Offset(0, 2).Value = Excel.WorksheetFunction.SumIfs(RngSource.Columns(3), RngSource.Columns(1), RngTarget.Value, RngSource.Columns(2), 0)
Translated in actual formulas they are basically this:
=SUMIFS(C2:C7,A2:A7,D2,B2:B7,"<>0")
=SUMIFS(C2:C7,A2:A7,D2,B2:B7,"=0")
Assuming that your list spans from cell A1 (headers) to cell C7, that in cell D2 there is the unique product you are looking for. The code itself dynamically covers the list. Since it looks like your list is the only thing in their own columns, formulas like these:
=SUMIFS(C:C,A:A,D2,B:B,"<>0")
=SUMIFS(C:C,A:A,D2,B:B,"=0")
should still be effective.

Related

Loop through ID's and keeps track of whether they pass/fail

I have:
Column A: (IDs)
A
A
A
C
C
Z
Column B: (Values)
3
2
-6
-12
6
2
I'm trying to create a macro that fills all unique ID's into column C, and counts whether they pass/fail in column D. A pass would be having an associated value in column B between -5 and 5.
Column C/D would look like:
C
D
A
2
C
0
Z
1
If anyone can start me off or link a similar example id appreciate.
You can do it using formulas. But if you like/want VBA, please try the next piece of code. It uses arrays and a dictionary. Working only in memory, it should be very fast, even for large ranges:
Sub CountPassed()
Dim dict As Object, sh As Worksheet, lastR As Long
Dim arr, arrFin, i As Long
Set sh = ActiveSheet
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row
arr = sh.Range("A2:B" & lastR).value 'place the range in an array for faster iteration
Set dict = CreateObject("Scripting.Dictionary")
For i = 1 To UBound(arr) 'extract unique keys and their item value according to the rule:
dict(arr(i, 1)) = dict(arr(i, 1)) + IIf(arr(i, 2) >= -5 And arr(i, 2) <= 5, 1, 0)
Next i
'create the necessary final array:
ReDim arrFin(1 To dict.count, 1 To 2)
For i = 0 To dict.count - 1
arrFin(i + 1, 1) = dict.Keys()(i)
arrFin(i + 1, 2) = dict.items()(i)
Next i
'drop the final array at once
sh.Range("C2").Resize(UBound(arrFin), 2).value = arrFin
End Sub
Count Unique With Limits
Adjust the values in the constants section.
Option Explicit
Sub CountUniqueWithLimits()
Const sName As String = "Sheet1"
Const sFirstCellAddress As String = "A1"
Const dName As String = "Sheet1"
Const dFirstCellAddress As String = "C1"
Const lLimit As String = ">=-5"
Const uLimit As String = "<=5"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim srg As Range
Dim rCount As Long
With sws.Range(sFirstCellAddress)
Dim lCell As Range: Set lCell = .Resize(sws.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Sub
rCount = lCell.Row - .Row + 1
Set srg = .Resize(rCount, 2)
End With
Dim Data As Variant: Data = srg.Value
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim Key As Variant
Dim nkey As Variant
Dim r As Long
For r = 1 To rCount
Key = Data(r, 1)
If Not IsError(Key) Then
If Len(Key) > 0 Then
If Not dict.Exists(Key) Then
dict(Key) = 0
End If
nkey = Data(r, 2)
If IsNumeric(nkey) Then
If Len(nkey) > 0 Then
If Evaluate(nkey & lLimit) Then
If Evaluate(nkey & uLimit) Then
dict(Key) = dict(Key) + 1
End If
End If
End If
End If
End If
End If
Next r
rCount = dict.Count
If rCount = 0 Then Exit Sub
ReDim Data(1 To rCount, 1 To 2)
r = 0
For Each Key In dict.Keys
r = r + 1
Data(r, 1) = Key
Data(r, 2) = dict(Key)
Next Key
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
With dws.Range(dFirstCellAddress).Resize(, 2)
.Resize(rCount).Value = Data
.Resize(dws.Rows.Count - .Row - rCount + 1).Offset(rCount).ClearContents
End With
MsgBox "Unique values with limits counted.", vbInformation
End Sub
Well, it may happen you are not familiar of writing VBA Codes, then you may try any of the options using Excel Formula (Formulas Shown Below Are Exclusively For Excel 2021 & O365 Users)
=CHOOSE({1,2},UNIQUE(ID),COUNTIFS(ID,UNIQUE(ID),Values,">=-5",Values,"<=5"))
In the above formula, we are combining two arrays within a CHOOSE Function.
• The first array contains the unique values in the database
UNIQUE(ID)
Where ID refers to the range =$A$3:$A$8, created using the Define Name Manager.
• The second array is essentially the COUNTIFS Function,
COUNTIFS(ID,UNIQUE(ID),Values,">=-5",Values,"<=5")
Where Values refers to the range =$B$3:$B$8, created using the Define Name Manager.
The CHOOSE function combines both the arrays into a single array, which produces as a two-column table as shown in the image below.
Note that we can also use the LET function to elegantly perform, by defining a variable, U to hold the unique values,
• Formula can also be used in cell C3
=LET(U,UNIQUE(ID),CHOOSE({1,2},U,COUNTIFS(ID,U,Values,">=-5",Values,"<=5")))
You may see that this version of the formula calls the UNIQUE function once only, storing the result in U, which is used twice!

wrong results with VBA dictionnary

I'm new in VBA and I’m getting wrong results by doing a dictionary in VBA.
Input :
column B : societies's ID
column A : their stores'IDs
column C : amounts
Output expected:
Column E: societies ID
Column F : stores ID (unique values)
Column G : total amount of each stores ID
What I get :
Example: For the store ID FRPAN3 I’m supposed to have 351,48.
Code :
Option Explicit
Dim dico As Object, f As Worksheet, i&
Sub ValeursUniques()
Set dico = CreateObject("Scripting.Dictionary")
Set f = Sheets("Feuil1")
For i = 2 To f.Range("B" & Rows.Count).End(xlUp).Row
dico(f.Range("B" & i).Value) = dico(f.Range("B" & i).Value) + Val(f.Range("C" & i))
Next i
Range("F2").Resize(dico.Count, 1) = Application.Transpose(dico.keys)
Range("G2").Resize(dico.Count, 1) = Application.Transpose(dico.items)
End Sub
Any idea why I get those results ?
The Val function may not return the correct value. If your values in column F Val(f.Range("F" & i)) are actually non-integers their decimals can get cut off!
The documentation says
The Val function stops reading the string at the first character that it can't recognize as part of a number.
The Val function recognizes only the period ( . ) as a valid decimal separator. When different decimal separators are used, as in international applications, use CDbl instead to convert a string to a number.
So if there is any character in your number it will cut off. In your case the , counts as a character and therefore your values are turned into integers as the , is not treated as a decimal seperator.
Make sure to use Type conversion functions instead:
cDbl(f.Range("F" & i))
will convert the value into a floating point with double precision.
Uniquify Data by Using a Dictionary
If a value in the first Unique column (in this case column 2) is an error value or a blank,
the record will not be included.
If a value in the other Unique columns (in this case only column 1) is an error value,
it will be converted to Empty (implicitly).
If a value in the Value column (in this case column 3) is not a number,
0 (zero) will be used instead.
Adjust (play with) the values in the constants section.
Option Explicit
Sub UniquifyData()
' Source
Const sName As String = "Feuil1"
Const sFirstCellAddress As String = "A1"
Dim uCols As Variant: uCols = VBA.Array(2, 1)
Const svCol As Long = 3
' Destination
Const dName As String = "Feuil1"
Const dFirstCellAddress As String = "E1"
' Both
Const Delimiter As String = "#"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the source range and write its values to the source array.
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim srg As Range: Set srg = sws.Range(sFirstCellAddress).CurrentRegion
Dim Data As Variant: Data = srg.Value
Dim srCount As Long: srCount = UBound(Data, 1)
Dim cCount As Long: cCount = UBound(Data, 2)
' Write the headers from the source array to the headers array.
Dim cUpper As Long: cUpper = UBound(uCols)
Dim Headers As Variant: ReDim Headers(1 To cUpper + 2)
Dim c As Long
For c = 0 To cUpper
Headers(c + 1) = Data(1, uCols(c))
Next c
Headers(cCount) = Data(1, svCol)
' Write the unique values from the source array to a dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim Key As Variant
Dim sString As String
Dim r As Long
For r = 2 To srCount
For c = 0 To cUpper
Key = Data(r, uCols(c))
If c = 0 Then
If Not IsError(Key) Then
If Len(Key) > 0 Then
sString = CStr(Key)
End If
End If
If Len(sString) = 0 Then Exit For
Else
If IsError(Key) Then Key = ""
sString = sString & Delimiter & CStr(Key) ' join uniques
End If
Next c
If Len(sString) > 0 Then
If IsNumeric(Data(r, svCol)) Then
dict(sString) = dict(sString) + Data(r, svCol)
Else
If Not dict.Exists(sString) Then dict(sString) = 0
End If
sString = ""
End If
Next r
' Define the destination array.
Dim drCount As Long: drCount = dict.Count + 1
ReDim Data(1 To drCount, 1 To cCount)
' Write the headers from the headers array to the destination array.
For c = 1 To cCount
Data(1, c) = Headers(c)
Next c
' Write the values from the dictionary to the destination array.
r = 1
For Each Key In dict.Keys
r = r + 1
' Write uniques.
uCols = Split(Key, Delimiter) ' split uniques
For c = 0 To cUpper
Data(r, c + 1) = uCols(c)
Next
' Write value.
Data(r, cCount) = dict(Key)
Next Key
' Write the values from the destination array to the destination range.
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
With dws.Range(dFirstCellAddress).Resize(, cCount) ' reference first row
' Write data.
.Resize(drCount).Value = Data
' Clear below.
.Resize(dws.Rows.Count - .Row - drCount + 1).Offset(drCount).Clear
' Apply some formatting.
'.Font.Bold = True ' headers
'.EntireColumn.AutoFit ' columns
End With
' Inform.
MsgBox "Data uniquified.", vbInformation
End Sub

Nesting For loops in VBA

Sub adress()
Dim s As Long
Dim h As Long
Dim n As Long
Dim i As Long
s = 1
n = 1
h = 1
For n = 1 To 1800
For i = 1 To 2000
If ActiveSheet.Cells(h + 1, 13) = ActiveSheet.Cells(s + 1, 32) Then
ActiveSheet.Cells(h + 1, 48) = ActiveSheet.Cells(s + 1, 36)
ActiveSheet.Cells(h + 1, 51) = ActiveSheet.Cells(s + 1, 37)
End If
s = s + 1
Next i
h = h + 1
i = 1
Next n
End Sub
This code is written to grab a value in a column of an excel spread sheet, then go to the next column and search the whole column for a matching value. Once that is found it will print the value that is in a cell in the same row of the value in the second column it found, into a cell in the same row as the original value it was trying to match.
While the inner loop works and my code will do the correct process when ran, it only does it for one value in the first column. I have tried using ranges in the For Loops, I have tried do while loops and do until loops. If i manually change the value of "h" and run the code it will progress down the column and print the correct information but i cannot get "h" to update on its own.
Nested For Next Loops
Although Exit For and turning off the two application settings are used, the first procedure still takes 45 seconds on my machine (without the 'improvements' it might take half an hour).
In the second code the inner loop is replaced with Application.Match and the operations are performed using arrays. It takes less than a second.
The Code
Option Explicit
Sub loopSlow()
Dim i As Long
Dim k As Long
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
With ActiveSheet
For i = 2 To 1801
For k = 2 To 2001
If .Cells(i, 13).Value = .Cells(k, 32).Value Then
.Cells(i, 48).Value = .Cells(k, 36).Value
.Cells(i, 51).Value = .Cells(k, 37).Value
Exit For
End If
Next k
Next i
End With
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Sub loopFast()
' Source
Const sName As String = "Sheet2"
Const sColsList As String = "AF,AJ,AK"
Const sFirstRow As Long = 2
' Destination (Lookup)
Const dName As String = "Sheet2"
Const dColsList As String = "M,AV,AY"
Const dFirstRow As Long = 2
' Define workbook.
Dim wb As Workbook: Set wb = ThisWorkbook
' Declare variables.
Dim ws As Worksheet
Dim rg As Range
Dim Cols() As String
Dim cUpper As Long
Dim cOffset As Long
Dim n As Long
' Write values from Source Columns to arrays of Data Array.
Cols = Split(sColsList, ",")
cUpper = UBound(Cols)
Set ws = wb.Worksheets(sName)
Set rg = ws.Cells(ws.Rows.Count, Cols(0)).End(xlUp)
Set rg = ws.Range(ws.Cells(sFirstRow, Cols(0)), rg)
Dim Data As Variant: ReDim Data(0 To cUpper)
For n = 0 To cUpper
cOffset = ws.Columns(Cols(n)).Column - rg.Column
Data(n) = rg.Offset(, cOffset).Value
Next n
' Write values from Lookup Column to Lookup Array of Result Array.
Cols = Split(dColsList, ",")
Set ws = wb.Worksheets(dName)
Set rg = ws.Cells(ws.Rows.Count, Cols(0)).End(xlUp)
Set rg = ws.Range(ws.Cells(dFirstRow, Cols(0)), rg)
Dim Result As Variant: ReDim Result(0 To cUpper)
Result(0) = rg.Value
' Define the (remaining) Write Arrays of Result Array.
Dim ResultNew As Variant: ReDim ResultNew(1 To UBound(Result(0)), 1 To 1)
For n = 1 To cUpper
Result(n) = ResultNew
Next n
' Write values from Data Array to Write Arrays of Result Array.
Dim cIndex As Variant
Dim i As Long
For i = 1 To UBound(Result(0))
cIndex = Application.Match(Result(0)(i, 1), Data(0), 0)
If IsNumeric(cIndex) Then
For n = 1 To cUpper
Result(n)(i, 1) = Data(n)(cIndex, 1)
Next n
End If
Next i
' Write values from Write Arrays of Result Array to Destination Columns.
For n = 1 To cUpper
cOffset = ws.Columns(Cols(n)).Column - rg.Column
rg.Offset(, cOffset).Value = Result(n)
Next n
End Sub

VBA function to copy into new rows depending on the colum values

I`m not a super experienced VBA developer and mostly relly on the Macro recorder, hence would appreciate any help by the community in helping me wrap my head around this problem. I havent used loops in the past but imagine this would be the best application for my problem.
I have the following Table;
Name
Year
Sec A
Sec B
Sec C
Joe
2020
15
20
30
Mary
2019
5
25
0
Peter
2020
7
0
0
I would like to copy/paste the name,year and amounts bigger than zero on a new sheet like the following;
Name
Year
Section
Total
Joe
2020
A
15
Joe
2020
B
20
Joe
2020
C
30
Mary
2019
A
5
Mary
2019
B
25
Peter
2020
A
7
The copy/paste operation would continune until it reaches a "0" value on the section columns, then it would continue to the next row, until it reaches the end of the rows.
Many thanks!!!
#BigBen's comment is right.
In Excel, highlight your source table, choose Insert Table (or press ctrl-t) making sure you check that your table has a header row.
Then, in the table ribbon (when your cursor is in the table) rename your table to "Source"
Then, in the Data ribbon, in the "Get & Transform" section, click "From Table". This will create a query that pulls from this table, and present it for editing in the Power Query Editor.
In the Home ribbon of the Power Query editor, click Manage - Reference. This will create a new query that uses/starts with the current one. I recommend renaming it (in the right sidebar).
In the home ribbon of the Power Query editor, click Advanced Editor and paste the following:
let
Source = Source,
#"Renamed Columns" = Table.RenameColumns(Source,{{"Sec A", "A"}, {"Sec B", "B"}, {"Sec C", "C"}}),
#"Unpivoted Columns" = Table.UnpivotOtherColumns(#"Renamed Columns", {"Name", "Year"}, "Attribute", "Value"),
#"Filtered Rows" = Table.SelectRows(#"Unpivoted Columns", each [Value] <> 0)
in
#"Filtered Rows"
You'll now have what you want.
Don't be scared of that code, by the way. I didn't really type all that! After creating the second query,
I double-clicked the column headers to rename them.
I highlighted the last three columns and clicked "Unpivot Columns" from the Transform ribbon.
I clicked the filter for the "Value" column to only get rows where Value wasn't 0.
and that was it!
This function will do that. Just create a input table named ÌnputTable and an output table named OutputTable in your worksheet
Sub Macro3()
Dim input_table As Range, output_table As Range
Set input_table = Range("InputTable")
Set output_table = Range("OutputTable")
Dim i As Integer, j As Integer, k As Integer
Dim name As String, year As String, section As String
For i = 1 To input_table.Rows.Count
name = input_table(i, 1)
year = input_table(i, 2)
For j = 3 To 5
section = Chr(62 + j)
If input_table(i, j).Value > 0 Then
k = k + 1
output_table(k, 1) = name
output_table(k, 2) = year
output_table(k, 3) = section
output_table(k, 4) = input_table(i, j)
End If
Next j
Next i
End Sub
Custom UnPivot RCV by Rows
Adjust the values in the constants section.
The Code
Option Explicit
Sub UnPivotRCVbyRowsCustom()
' Define constants.
Const srcName As String = "Sheet1" ' Source Worksheet Name
Const srcFirst As String = "A1" ' Source First Cell Range
Const rlCount As Long = 2 ' Row Labels (repeating columns) Count
Const vException As Variant = 0 ' Value Exception
Const dstName As String = "Sheet2" ' Destination Worksheet Name
Const dstFirst As String = "A1" ' Destination First Cell Range
Const HeaderList As String = "Name,Year,Section,Total"
Dim wb As Workbook: Set wb = ThisWorkbook ' Workbook containing this code.
' Define Source Range.
Dim ws As Worksheet: Set ws = wb.Worksheets(srcName)
Dim rng As Range
Set rng = defineEndRange(ws.Range(srcFirst).CurrentRegion, srcFirst)
' Write values from Source Range to Data Array.
Dim Data As Variant: Data = rng.Value
Dim srCount As Long: srCount = UBound(Data, 1) ' Source Rows Count
Dim scCount As Long: scCount = UBound(Data, 2) ' Source Columns Count
' Calculate Exceptions Count.
Set rng = rng.Resize(srCount - 1, scCount - rlCount) _
.Offset(1, rlCount)
Dim eCount As Long: eCount = Application.CountIf(rng, vException)
' Rename column labels in Data Array.
Dim fvCol As Long: fvCol = 1 + rlCount ' First Value Column
Dim j As Long ' Source Columns Counter
For j = fvCol To scCount
Data(1, j) = Right(Data(1, j), 1)
Next j
' Define Result Array.
Dim drCount As Long ' Destination Rows Count
drCount = (srCount - 1) * (scCount - rlCount) - eCount + 1
Dim dcCount As Long: dcCount = rlCount + 2 ' Destination Columns Count
Dim Result As Variant: ReDim Result(1 To drCount, 1 To dcCount)
' Write headers to Result Array.
Dim Headers() As String: Headers = Split(HeaderList, ",")
For j = 1 To dcCount
Result(1, j) = Headers(j - 1)
Next j
' Write values from Data Array to Result Array.
Dim i As Long ' Source Rows Counter
Dim k As Long: k = 1 ' Destination Rows Counter
Dim l As Long ' Destination Columns Counter
For i = 2 To srCount
For j = fvCol To scCount
If Data(i, j) <> vException Then
k = k + 1
For l = 1 To rlCount
Result(k, l) = Data(i, l)
Next l
Result(k, l) = Data(1, j)
Result(k, l + 1) = Data(i, j)
End If
Next j
Next i
' Write values from Result Array to Destination Range.
With wb.Worksheets(dstName).Range(dstFirst).Resize(, dcCount)
.Resize(.Worksheet.Rows.Count - .Row + 1).ClearContents
.Resize(drCount).Value = Result
End With
End Sub
Function defineEndRange( _
rng As Range, _
ByVal FirstCellAddress As String) _
As Range
If Not rng Is Nothing Then
With rng.Areas(1)
On Error Resume Next
Dim cel As Range: Set cel = .Worksheet.Range(FirstCellAddress)
On Error GoTo 0
If Not cel Is Nothing Then
If Not Intersect(rng.Areas(1), cel) Is Nothing Then
Set defineEndRange = cel.Resize( _
.Rows.Count + .Row - cel.Row, _
.Columns.Count + .Column - cel.Column)
End If
End If
End With
End If
End Function
I am new to VBA as well, so I am taking this as a practice. Here is the code I wrote. May not be the best solution but it does work.
Sub copyandpastedata()
Dim lastrow As Long
Dim lastcol As Long
Dim i As Integer
Dim ws As Worksheet
Dim cell As Range
Dim char As String
'Define last position where a data exist
lastrow = Sheet1.Cells(Rows.Count, 1).End(xlUp).Row
lastcol = Sheet1.Cells(1, Columns.Count).End(xlToLeft).Column
'Delete all worksheets other than sheet1(where the raw data is)
Application.DisplayAlerts = False
For Each ws In Worksheets
If ws.Name <> "Sheet1" Then
ws.Delete
End If
Next
Application.DisplayAlerts = True
'Create a new sheet and name it to NewData
Sheets.Add(after:=Sheet1).Name = "NewData"
With Sheets("NewData")
.Range("A1") = "Name"
.Range("B1") = "Year"
.Range("C1") = "Section"
.Range("D1") = "Total"
End With
'Loop through raw data and find matches
i = 2
With Sheet1
For Each cell In .Range("C2", .Cells(lastrow, lastcol))
If VBA.IsNumeric(cell) Then
If cell > 0 Then
.Cells(cell.Row, 1).Copy Sheets("NewData").Cells(i, 1) 'Copy Name to the new sheet
.Cells(cell.Row, 2).Copy Sheets("NewData").Cells(i, 2) 'Copy Year to the new sheet
char = Right(.Cells(1, cell.Column), 1) 'Look for section letter ID
Sheets("NewData").Cells(i, 3) = char 'Copy section to the new sheet
.Cells(cell.Row, cell.Column).Copy Sheets("NewData").Cells(i, 4) 'Copy Total to the new sheet
i = i + 1
End If
End If
Next
End With
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

Resources