I'm running a dataimport macro, and I want to merge all rows in a dataset that have equal values in column x, and then I want to get a row that represents the average of group x[y] x being the column, and y being the value of the column x for that particular grouping.
Is there a simple function to do this, or must I create an extensive loop with a lot of spare cycles?
Explicitation:
So my dataset looks like
A | B | C
1 2 4
1 2 5
2 7 3
2 5 1
3 2 1
1 5 6
Now I want to merge rows by column A value, so all A's with equal value get the rest of their rows averaged so I would get somethin that looked like:
A | B | C
1 3 5
2 6 2
3 2 1
So far I've been trying to loop over the possible values of column A (1 to 10) manually by this function, but it keeps crashing excel, and I can't figure out why, I must have an endless loop somewhere in this function:
Function MergeRows(sheet, column, value)
Dim LastRow
Dim LastCol
Dim numRowsMerged
Dim totalValue
numRowsMerged = 1
LastRow = sheet.UsedRange.Rows.Count
LastCol = sheet.UsedRange.Columns.Count
With Application.WorksheetFunction
For iRow = LastRow - 1 To 1 Step -1
'enter loop if the cell value matches what we're trying to merge
Do While Cells(iRow, column) = value
For iCol = 1 To LastCol
'skip the column that we're going to use as merging value, and skip the column if it contains 3 (ikke relevant)
If Not (iCol = column) And Not (Cells(iRow, iCol) = 3) Then
Cells(iRow, iCol) = (Cells(iRow, iCol) * numRowsMerged + Cells(iRow + 1, iCol)) / (numRowsMerged + 1)
End If
Next iCol
'delete the row merged
Rows(iRow + 1).Delete
Loop
'add one to the total number of rows merged
numRowsMerged = numRowsMerged + 1
Next iRow
End With
End Function
solution
I ended up creating a range that I would gradually extend using Union, like this:
Function GetRowRange(sheet, column, value) As range
Dim LastRow
Dim LastCol
Dim numRowsMerged
Dim totalValue
Dim rowRng As range
Dim tempRng As range
Dim sheetRange As range
numRowsMerged = 1
Set sheetRange = sheet.UsedRange
LastRow = sheet.UsedRange.Rows.Count
LastCol = sheet.UsedRange.Columns.Count
With Application.WorksheetFunction
For iRow = 1 To LastRow Step 1
'enter loop if the cell value matches what we're trying to merge
If (sheetRange.Cells(iRow, column) = value) Then
Set tempRng = range(sheetRange.Cells(iRow, 1), sheetRange.Cells(iRow, LastCol))
If (rowRng Is Nothing) Then
Set rowRng = tempRng
Else
Set rowRng = Union(rowRng, tempRng)
End If
End If
'add one to the total number of rows merged
numRowsMerged = numRowsMerged + 1
Next iRow
End With
Set GetRowRange = rowRng
End Function
Is this what you are trying? Since you wanted VBA code, I have not used Pivots but used a simpler option; formulas to calculate your average.
Option Explicit
Sub Sample()
Dim col As New Collection
Dim wsI As Worksheet, wsO As Worksheet
Dim wsIlRow As Long, wsOlRow As Long, r As Long, i As Long
Dim itm
'~~> Chnage this to the relevant sheets
Set wsI = ThisWorkbook.Sheets("Sheet1")
Set wsO = ThisWorkbook.Sheets("Sheet2")
'~~> Work with the input sheet
With wsI
wsIlRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> get unique values from Col A
For i = 1 To wsIlRow
On Error Resume Next
col.Add .Range("A" & i).Value, """" & .Range("A" & i).Value & """"
On Error GoTo 0
Next i
End With
r = 1
'~~> Write unique values to Col A
With wsO
For Each itm In col
.Cells(r, 1).Value = itm
r = r + 1
Next
wsOlRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Use a simple formula to find the average
For i = 1 To wsOlRow
.Range("B" & i).Value = Application.Evaluate("=AVERAGE(IF(" & wsI.Name & _
"!A1:A" & wsIlRow & "=" & .Range("A" & i).Value & _
"," & wsI.Name & "!B1:B" & wsIlRow & "))")
.Range("C" & i).Value = Application.Evaluate("=AVERAGE(IF(" & wsI.Name & _
"!A1:A" & wsIlRow & "=" & .Range("A" & i).Value & _
"," & wsI.Name & "!C1:C" & wsIlRow & "))")
Next i
End With
End Sub
SCREENSHOT
This is easy to do with a pivot table.
Here's a picture of the end result.
Related
I want to get a sum based on the criteria of the preceding column data. Suppose I have three columns say A, B and C. So, "A" columns have the Sr.no. let's say, "B" has the quantity and "C" have the total quantity. I am trying to sum the quantity in column "B" based on the Sr.no. in column "A" and paste it to column "C" (after merging that many cells) against the respective Sr.no. (Which we have in column "A"). Refer image attached Image.
Sub sum_on_condition()
Dim sum_criteria As Double
Dim lastrow As Long, x As Long
Dim l_array As Variant
Dim l_number As Long
lastrow = range("A" & Rows.Count).End(xlUp).Row
l_array = range("I2:I21").Value
counter = 1
While counter <= UBound(l_array)
l_number = l_array(counter, 1)
For x = 2 To lastrow
If range("e" & x).Value = l_number Then
sum_criteria = sum_criteria + range("f" & x).Value
End If
Next x
counter = counter + 1
Wend
Debug.Print sum_criteria
End Sub
I have written this code but what it does it sums the total value rather than the individual value. I am not able to figure out how I do this!
Here's another approach:
Sub SumAndMerge()
Dim wb As Workbook
Dim ws As Worksheet
Dim lastRow As Long
Dim firstItem As Long, lastItem As Long
Dim i As Long, j As Long
Dim c As Range, d As Range
Dim valueToFind As String
Dim total As Long
Set wb = ThisWorkbook
Set ws = wb.Sheets(1)
lastRow = ws.Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To lastRow
'Get valueToFind
valueToFind = ws.Cells(i, 1).value
'Get range of cells with .Find : look up for first value and last value and get row number.
With ws.Range("A" & i & ":" & "A" & lastRow)
Set c = .Find(valueToFind, LookAt:=xlWhole)
firstItem = c.Row - 1
Set d = .Find(valueToFind, LookAt:=xlWhole, SearchDirection:=xlPrevious)
lastItem = d.Row
End With
'Get total
total = WorksheetFunction.Sum(ws.Range("B" & firstItem & ":" & "B" & lastItem))
'Assign total to first cell
ws.Range("C" & firstItem).value = total
'Merge cells
ws.Range("C" & firstItem & ":" & "C" & lastItem).Merge
'Go to lastItem to adapt the loop
i = lastItem
Next i
End Sub
Gives the following output:
Rather than using an array, this macro aims at using the Find function. In a loop, we find the first value and the last value. We extract row numbers and then we can assign the total and finally merge cells.
This code can be improved by replacing harcoded A, B and C. But this gives you an example.
I have a list of about 2000 values in column A in Excel, and then a list of the start and end of value ranges in the next two columns. The range values don't correspond to the values in the first column. I want to check, for every value in column A, whether the value falls within ANY of the ranges listed in columns B and C.
So for example, in the image below, see whether A2 falls within B2-C2, B3-C3, OR B4-C4. Then the same again for A3 and A4. For each of these I want true/false to be entered in column D. The true/false value would correspond to the values in column A.
I've been trying to do this in VBA but I'm not totally confident with getting it to search the ranges. Current code is below.
Sub CheckRg()
Dim wk As Worksheet, frow As Long, i As Long
Set wk = Sheet1
frow = wk.Range("A" & Rows.Count).End(xlUp).Row
For i = 2 To frow
If wk.Range("A" & i).Value >= wk.Range("B:B").Value And wk.Range("A" & i).Value <= wk.Range("C:C").Value Then
wk.Range("D" & i).Value = "TRUE"
Else
wk.Range("D" & i).Value = "FALSE"
End If
Next i
End Sub
This formula should do the trick without VBA:
=COUNTIFS($B:$B,"<="&A2,$C:$C,">="&A2)<>0
You can use it in your code like this:
Sub CheckRg()
Dim wk As Worksheet, frow As Long, i As Long
Set wk = Sheet1
frow = wk.Range("A" & Rows.Count).End(xlUp).Row
For i = 2 To frow
With Excel.WorksheetFunction
wk.Range("D" & i).Value = .CountIfs(wk.Range("B:B"), Evaluate("""<=""" & "&A" & i), wk.Range("C:C"), Evaluate(""">=""" & "&A" & i)) <> 0
End With
Next i
End Sub
An Inefficient Double Loop
A better way to go is presented in the solution by Evil Blue Monkey.
You need to check each cell in column A against each cell pair of columns B and C which requires a second loop that slows down the operation when thousands of rows are expected.
Here's an example of how you could go about that.
Sub CheckRg()
Dim ws As Worksheet: Set ws = Sheet1
Dim lRow As Long: lRow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Application.ScreenUpdating = False
Dim i As Long
Dim j As Long
Dim MatchFound As Boolean
For i = 2 To lRow
For j = 2 To lRow
If ws.Range("A" & i).Value >= ws.Range("B" & j).Value _
And ws.Range("A" & i).Value <= ws.Range("C" & j).Value Then
MatchFound = True
Exit For
End If
Next j
If MatchFound Then
ws.Range("D" & i).Value = True
MatchFound = False
Else
ws.Range("D" & i).Value = False
End If
Next i
Application.ScreenUpdating = True
MsgBox "Range checked.", vbInformation
End Sub
Im super new to coding, i hope you guys can help me.
In column A i have some strings and would like to count all the same ones up in Column L. And it should jump to the next row if there is nothig in Column G
How it should look:
A G L
zu=host,out=fr x 1
zu=host,out=fr x 2
zu=host,out=de x 1
zu=host,out=de x 2
zu=host,out=en x 1
zu=host,out=sw x 1
zu=host,out=sw x 2
zu=host,out=nw
zu=host,out=tw x 1
This is my try, which sadly does not work:
Dim i As Integer
Dim ws As Worksheet
Dim counter As Integer
Set ws = ActiveSheet
counter = 1
For i = 1 To 5000
If IsEmpty(ws.Range("A" & i)) Then
Exit For
End If
If ws.Range("A" & i).Value = ws.Range("A" & i + 1).Value Then
ws.Range("L" & i).Value = counter
counter = counter + 1
Exit For
Else: ws.Range("L" & i).Value = 1
counter = 1
Exit For
End If
Next i
MsgBox ("Finished ")
No need for VBA. A simple formula can achieve it. Put =COUNTIF($A$1:A1,A1) in cell L1 and drag it down. I am assuming that your data starts from cell A1.
If you still want VBA, then do this:
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim LRow As Long
'~~> Set this to the relevant sheet
Set ws = Sheet1
With ws
'~~> Find last row in col A
LRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Put the formula in the Col L in 1 go!
.Range("L1:L" & LRow).Formula = "=COUNTIF($A$1:A1,A1)"
'~~> Convert formula to values
.Range("L1:L" & LRow).Value = .Range("L1:L" & LRow).Value
End With
End Sub
Screenshot
Having blank rows means it's not a simple as comparing the next or previous rows.
Sub CountUp()
Dim ws As Worksheet
Dim LastRow As Long, i As Long, counter As Long
Dim sLastA As String
Set ws = ActiveSheet
LastRow = ws.Cells(Rows.Count, "A").End(xlUp).Row
counter = 1
For i = 1 To LastRow
If ws.Cells(i, "G") > 0 Then
If ws.Cells(i, "A") = sLastA Then
counter = counter + 1
Else
counter = 1
End If
ws.Cells(i, "L") = counter
sLastA = ws.Cells(i, "A")
End If
Next i
MsgBox ("Finished ")
End Sub
Here are some suggestions to get the original code working:
Option Explicit
Sub Count()
Dim i As Long
Dim ws As Worksheet
Dim counter As Integer
Set ws = ActiveSheet
counter = 1
' Start at row 2
For i = 2 To 10
Debug.Print ("i=" & i)
' This will exit completely if it finds a blank in column G
If IsEmpty(ws.Range("G" & i)) Then
Exit For
End If
'Compare the current row to the previous row
If ws.Range("A" & i).Value = ws.Range("A" & i - 1).Value Then
Debug.Print ("i1=" & i)
counter = counter + 1
' Don''t need to compare to previous value in column L - if current row doesn't match previous row, this just resets counter to 1.
Else
Debug.Print ("i2=" & i)
counter = 1
End If
' Always write counter to column L
ws.Range("L" & i).Value = counter
Next i
MsgBox ("Finished ")
End Sub
I need to write a code to perform the vlookup in H column in workbook 1, the match is from column A in a separate workbook and the result is column B for the other workbook.
No idea how to go about this
could someone please help
Sub y()
Dim rw As Long, x As Range, v As Variant
Dim extwbk As Workbook, twb As Workbook
Dim wsActiveSheet As Worksheet
Columns("H").Insert
Range("H1") = "1st phase"
Set wsActiveSheet = Application.ActiveSheet
Set extwbk = Workbooks.Open("C:\Users\OUROBOROS\Desktop\Goldratt\24-6-19\1st phase stores.xlsx") 'file with reference table
Set x = extwbk.Worksheets("Sheet1").Range("A2:A300")
For rw = 2 To wsActiveSheet.Cells(Rows.Count, "G").End(xlUp).Row
v = Application.Match(wsActiveSheet.Cells(rw, "G").Value, x, 0)
If IsNumeric(v) Then
wsActiveSheet.Cells(rw, "H").Value = extwbk.Worksheets("Sheet1").Cells(rw, "b").Value 'G is in the table
Else
wsActiveSheet.Cells(rw, "H").Value = "NA" ''G is NOT in the table
End If
Next rw
Dim LR As Long
Range("a1").EntireRow.Insert
LR = Range("v" & Rows.Count).End(xlUp).Row
Range("v1").Formula = "=SUBTOTAL(9,v3:v" & LR & ")"
LR = Range("v" & Rows.Count).End(xlUp).Row
Range("w1").Formula = "=SUBTOTAL(9,w3:w" & LR & ")"
LR = Range("v" & Rows.Count).End(xlUp).Row
Range("x1").Formula = "=SUBTOTAL(9,x3:x" & LR & ")"
LR = Range("v" & Rows.Count).End(xlUp).Row
Range("y1").Formula = "=SUBTOTAL(9,y3:y" & LR & ")"
End Sub
I'm not sure if this will be what you actually need because you were saying some columns on your question and there are totally different ones on your code. You can change the columns by chaning the numbers refering to them arr(i, X) the X is the column number.
Option Explicit
Sub y()
Dim arrSource, arrLookUp
Dim DictData As New Scripting.Dictionary 'Needs the Microsoft Scripting Runtime 'Tools->References
Dim i As Long
Dim extwbk As Workbook
With ThisWorkbook.Sheets("MySheet") 'Change MySheet for your sheet name(the one were you are doing the vlookup)
.Columns("H").Insert
.Range("H1") = "1st phase"
arrSource = .UsedRange.Value 'store the whole sheet inside the array
End With
Set extwbk = Workbooks.Open("C:\Users\OUROBOROS\Desktop\Goldratt\24-6-19\1st phase stores.xlsx") 'file with reference table
With extwbk.Sheets("MyOtherSheet") 'Change MyItherSheet for the name of the sheet holding the reference table
arrLookUp = .UsedRange.Value 'store the whole sheet inside the array
End With
extwbk.Close SaveChanges:=False 'close the file with the reference table (the data is already in the array)
'Create a dictionary holding the index for the lookup
For i = 2 To UBound(arrLookUp) 'loop through the reference table
If Not DictData.Exists(arrLookUp(i, 1)) Then 'check if the value in column A is not duplicated
DictData.Add arrLookUp(i, 1), arrLookUp(i, 2) 'add the matching value from column A with it's value in column B
End If
Next i
'Loop through your original table to find the matches
For i = 2 To UBound(arrSource)
If Not DictData.Exists(arrSource(i, 7)) Then 'check if we have a match inside the dictionary for column G
arrSource(i, 8) = "NA" 'if column G value is not found in the dictionary, column H will have a "NA"
Else
arrSource(i, 8) = DictData(arrSource(i, 7)) 'if column G value is found in the dictionary, column H will have column B from the other workbook
End If
Next i
Dim LR As Long
With ThisWorkbook.Sheets("MySheet") 'Change MySheet for your sheet name(the one were you are doing the vlookup)
.UsedRange.Value = arrSource 'drop back the array into the sheet
.Range("a1").EntireRow.Insert
LR = Range("v" & .Rows.Count).End(xlUp).Row
.Range("v1").Formula = "=SUBTOTAL(9,v3:v" & LR & ")"
LR = .Range("v" & .Rows.Count).End(xlUp).Row
.Range("w1").Formula = "=SUBTOTAL(9,w3:w" & LR & ")"
LR = .Range("v" & .Rows.Count).End(xlUp).Row
.Range("x1").Formula = "=SUBTOTAL(9,x3:x" & LR & ")"
LR = .Range("v" & .Rows.Count).End(xlUp).Row
.Range("y1").Formula = "=SUBTOTAL(9,y3:y" & LR & ")"
End With
End Sub
I need a macro to write the row values present in column A if there is a value present in column B .
For example :
Column A Column B
Arjun
Arun 12
For the above example, I need a macro which can write "Arun 12" in Sheet2 of the work book with the Headers "Name" and "Hours".Before this the macro should clear the data present in Sheet two completely.
This will copy the all rows of columns A and B from Sheet1 to Sheet2 if B is not a Null string. And also will add the headers "Name" and "Hours".
Option Explicit 'requires that every variable has to be defined before use, e.g. with a Dim statement.
Sub DoStuff_GoodPractice()
Dim lastRowSrc As Long, lastRowDest As Long, i As Long 'declare row counts as Long so all rows can be used
Dim shtSource As Worksheet, shtDestination As Worksheet
Set shtSource = ThisWorkbook.Worksheets("Sheet1") 'full qualified identification of the worksheets
Set shtDestination = ThisWorkbook.Sheets("Sheet2")
lastRowSrc = shtSource.Range("A" & shtSource.Rows.Count).End(xlUp).Row 'determine the last used row
'clear destination sheet and write headers:
shtDestination.Cells.Clear
shtDestination.Range("A1").Value = "Name"
shtDestination.Range("B1").Value = "Hours"
lastRowDest = 1 'start with row 1 as destination
For i = 1 To lastRowSrc 'loop through all used rows
If shtSource.Range("A" & i).Value <> vbNullString And _
shtSource.Range("B" & i).Value <> vbNullString Then 'check if cells are not a null string
shtSource.Range("A" & i & ":B" & i).Copy Destination:=shtDestination.Range("A" & lastRowDest + 1) 'copy current row
lastRowDest = lastRowDest + 1 'jump to the last used row in destination
End If
Next i
End Sub
This should accomplish what you're after.
Sub DoStuff()
Dim lastRow As integer, lastRowSheet2 As integer, i As Integer
Dim sheet1 As WorkSheet, sheet2 As Worksheet
Set sheet1 = Sheets("Sheet1")
Set sheet2 = Sheets("Sheet2")
lastRow = sheet1.Range("A" & Rows.Count).End(xlUp).Row
sheet2.Cells.Clear
For i = 1 To lastRow
If sheet1.Range("A" & i).Value <> "" And sheet1.Range("B" & i).Value <> "" then
lastRowSheet2 = sheet2.Range("A" & Rows.Count).End(xlUp).Row
sheet1.Range("A" & i & ":B" & i).Copy Destination:= sheet2.Range("A" & lastRowSheet2 + 1)
End If
Next i
End Sub