sumif with dynamic column and range in VBA - excel

I want to using sumifs function in VBA. And result paste in same column as previous data.

Twice a SUMIF with Overwrite
Workbook
Download
(Dropbox)
Couldn't find any indication of SUMIFS, so I did it as if there is
twice a SUMIF:
For Columns A and C, and for Columns B and D.
Option Explicit
Sub SumUnique(UniqueFirstCell As Range, ValueFirstCell As Range)
Dim rng As Range ' Unique Last Used Cell
Dim dict As Object ' Dictionary
Dim key As Variant ' Dictionary Key Counter (For Each Control Variable)
Dim vntU As Variant ' Unique Range Array
Dim vntV As Variant ' Value Range Array
Dim vntUT As Variant ' Unique Array
Dim vntVT As Variant ' Value Array
Dim curV As Variant ' Current Value
Dim NorS As Long ' Source Number of Rows
Dim NorT As Long ' Target Number of Rows
Dim i As Long ' Source/Target Row Counter
' Copy Unique Range to Unique Range Array.
With UniqueFirstCell
Set rng = .Worksheet.Columns(.Column) _
.Find("*", , xlFormulas, , , xlPrevious)
Set rng = .Resize(rng.Row - .Row + 1)
End With
vntU = rng
' Copy Value Range to Value Range Array.
With ValueFirstCell
Set rng = .Worksheet.Columns(.Column) _
.Find("*", , xlFormulas, , , xlPrevious)
Set rng = .Resize(rng.Row - .Row + 1)
End With
vntV = rng
' Create Unique Values and SumIf Values in Dictionary.
Set dict = CreateObject("Scripting.Dictionary")
NorS = UBound(vntU)
For i = 1 To NorS
curV = vntU(i, 1)
If curV <> "" Then
dict(curV) = dict(curV) + vntV(i, 1)
End If
Next
NorT = dict.Count
' Resize Unique and Value Arrays to Target Number of Rows.
ReDim vntUT(1 To NorT, 1 To 1)
ReDim vntVT(1 To NorT, 1 To 1)
i = 0
For Each key In dict.keys
i = i + 1
' Write Dictionary Keys to Unique Array.
vntUT(i, 1) = key
' Write Dictionary Values to Value Array.
vntVT(i, 1) = dict(key)
Next
' Copy Unique Array to Target Unique Range.
With UniqueFirstCell
Set rng = .Resize(.Worksheet.Rows.Count - .Row + 1)
rng.ClearContents
Set rng = .Resize(NorT)
End With
rng = vntUT
' Copy Value Array to Target Value Range.
With ValueFirstCell
Set rng = .Resize(.Worksheet.Rows.Count - .Row + 1)
rng.ClearContents
Set rng = .Resize(NorT)
End With
rng = vntVT
End Sub
Sub Uni()
Uni1
Uni2
End Sub
Sub Uni1()
Const cUni As String = "A2"
Const cVal As String = "C2"
With ThisWorkbook.Worksheets("Sheet1")
SumUnique .Range(cUni), .Range(cVal)
End With
End Sub
Sub Uni2()
Const cUni As String = "B2"
Const cVal As String = "D2"
With ThisWorkbook.Worksheets("Sheet1")
SumUnique .Range(cUni), .Range(cVal)
End With
End Sub
I created two command buttons and put the following code into the sheet module:
Option Explicit
Private Sub cmdRevert_Click()
[A2:D31] = [J2:M31].Value
End Sub
Private Sub cmdUnique_Click()
Uni
End Sub

You can use SumIfs in VBA through the Application or WorksheetFunction object with VBA style range references. You are only going to want to use it once for each pair of column A and column B values. If you use it once then loop to another row with the same pair of column A and column B values, you cannot use it again without getting false results due to the changes you made the first time.
However, those false results are okay if you are just going to delete them anyways and RemoveDuplicates deletes from the bottom up leaving the top-most column A and column B pairs with the correct totals.

Related

How do I code a macro in VBA that deletes columns in excel that don't appear in an array?

I'm creating a macro that is formatting a collection of files and a step in this process is to delete columns that aren't required, keeping a specific set of columns.
I know I can delete columns based on their location and I have this approach implemented already ie 1,3,7 etc or A, C, G etc. But I'm conscious that the report being used might change layout or add extra columns over time and I want to ensure the required columns are kept.
Ideally this code would cycle through each column header starting at A1 until the last column and delete an entire column if the header value isn't found in a list. This list will be an array captured from a range in one of the sheets in the workbook.
List = {Blue, Green, Orange}
Original Table
Blue
Red
Green
Orange
Black
row
row
row
row
row
Formatted Table
Blue
Green
Orange
row
row
row
Does anyone have any suggestions on the approach I could take to get this working or if it's even possible? Any help would be greatly appreciated
You might profit from the following approach by reordering a datafield array via Application.Index which allows even to move the existing columns to any new position.
Note: this flexible solution can be time consuming for greater data sets,
where I would prefer other ways you can find in a lot of answers at SO.
Sub ReorderColumns()
Const headerList As String = "Blue,green,Orange"
'a) define source range
Dim src As Range
Set src = Tabelle3.Range("A1:E100")
'b) define headers
Dim allHeaders: allHeaders = src.Resize(1).Value2
Dim newHeaders: newHeaders = Split(headerList, ",")
'c) get column positions in old headers
Dim cols
cols = getCols(newHeaders, allHeaders)
'd) define data
Dim data As Variant
data = src.Value2
'e) reorder data based on found column positions
data = Application.Index(data, Evaluate("row(1:" & UBound(data) & ")"), cols)
'f) overwrite source data
src = vbNullString ' clear
src.Resize(UBound(data), UBound(data, 2)) = data
End Sub
Help function getCols()
Function getCols(individualHeaders, allHeaders)
'Purp: get 1-based column numbers of found headers via Match
getCols = Application.Match(individualHeaders, allHeaders, 0) ' 1-based
End Function
Please, test the next code. It is compact and fast enough. It will build the columns to be deleted range using Application.Match for the two involved arrays (the existing headers one and the ones to be kept). This code assumes that the headers exist in the first row of the processed sheets, starting from A:A column (If starting from a different column, the code can be adapted:
Sub DeleteColunsNotInArrayDel()
Dim sh As Worksheet, arrStay, lastCol As Long, arrH, arrCols, rngDel As Range
Set sh = ActiveSheet 'use here the sheet you need to process
lastCol = sh.cells(1, sh.Columns.count).End(xlToLeft).Column 'last column on the first row
arrStay = Split("Blue,Green,Orange", ",") 'the headers to not be deleted array
arrH = Application.Transpose(Application.Transpose(sh.Range(sh.cells(1, 1), sh.cells(1, lastCol)).Value2)) 'existing headers array
arrCols = Application.IfError(Application.match(arrH, arrStay, 0), "xx") 'match the two arrays and place "xx" where no match has been found
makeColsRng(arrCols).Delete 'delete the columns range, at once
End Sub
Function makeColsRng(arr) As Range
Dim i As Long, colL As String, strAddr As String
For i = LBound(arr) To UBound(arr) 'iterate between the matched arrays array
If arr(i) = "xx" Then 'for the not matching case:
colL = Split(cells(1, i).Address, "$")(1) 'extract the letter of the respective column
strAddr = strAddr & colL & "1," 'build the string of the columns to be deleted range
End If
Next i
Set makeColsRng = Range(left(strAddr, Len(strAddr) - 1)).EntireColumn 'return the necessary range
End Function
In case of headers not starting from the first sheet column, the function can easily be adapted by adding a new parameter (the first column number) to be added when the range to be deleted is built.
The above suggested solution is a fancy one, just for the sake of showing the respective approach, which is not too often used. It may have a limitation of the range building, in case of a string bigger than 254 digits, No error handling for the case of everything matching (even, easy to be added). The next version is standard VBA, compact, more reliable, faster and easier to be understood:
Sub DeleteColunsRangeNotInArray()
Dim sh As Worksheet, arrStay, lastCol As Long, rngH As Range, rngDel As Range, i As Long
Set sh = ActiveSheet
lastCol = sh.cells(1, sh.Columns.count).End(xlToLeft).Column'last column on the first row
arrStay = Split("Blue,Green,Orange", ",") 'the headers to not be deleted array
Set rngH = sh.Range(sh.cells(1, 1), sh.cells(1, lastCol)) 'existing headers range
For i = 1 To rngH.Columns.count
If IsError(Application.match(rngH(i).Value, arrStay, 0)) Then 'if not a match in arrStay:
addToRange rngDel, rngH(i) 'build a Union range
End If
Next i
'delete the not necessary columns at once:
If Not rngDel Is Nothing Then rngDel.EntireColumn.Delete
End Sub
Private Sub addToRange(rngU As Range, rng As Range)
If rngU Is Nothing Then
Set rngU = rng
Else
Set rngU = Union(rngU, rng)
End If
End Sub
Dynamic Named Range
I think a dynamic named range is an excellent choice for storing and retrieving your required columns. Please see the link I provided from https://exceljet.net/ to setup your dynamic named range.
Generic formula =$A$2:INDEX($A:$A,COUNTA($A:$A))
Regular Expression Approach
After reading in your named range, one approach for testing your columns is using regular expressions. To use this you will need to set a library reference to Microsoft VBScript Regular Expressions 5.5. The pipe character | represents an or statement, so we can join our array using that delimiter.
Deleting Ranges in loops
When deleting columns or rows within a loop, the best approach I have found is to union the ranges together in a variable and execute the deletion in one go. This helps performance and it prevents errors from deleting ranges the loop is working on.
I do this so often that I created a custom function for this UnionRange
' Helper function that allows
' concatinating ranges together
Public Function UnionRange( _
ByRef accumulator As Range, _
ByRef nextRange As Range _
)
If accumulator Is Nothing Then
Set UnionRange = nextRange
Else
Set UnionRange = Union(accumulator, nextRange)
End If
End Function
Putting it all together
Below is my implementation of what your code could look like, just make sure to first:
Create a Dynamic Named Range and populate with your required headers
Add Microsoft VBScript Regular Expressions 5.5 reference
Update Sheet1 to whatever sheet your table exists (possibly change logic for finding header row based on your needs)
' Need Regular Expressions Referenced in order to work!
' #libraryReference {Microsoft VBScript Regular Expressions 5.5}
Public Sub DemoDeletingNonRequiredColumns()
' Make sure to create a named range
' otherwise this section will fail. In this
' example the named range is `RequiredColumns`
Dim requiredColumns() As Variant
requiredColumns = Application.WorksheetFunction.Transpose( _
Range("RequiredColumns").Value2 _
)
' To test if the column is in the required
' columns this method uses regular expressions.
With New RegExp
.IgnoreCase = True
' The pipe charactor is `or` in testing.
.Pattern = Join(requiredColumns, "|")
Dim headerRow As Range
' This example uses `Sheet1`, but update to
' the actual sheet you are using.
With Sheet1
Set headerRow = .Range("A1", .Cells(1, Columns.Count).End(xlToLeft))
End With
Dim column As Range
For Each column In headerRow
' If the column name doesn't match the
' pattern, then concatenate it to the
' toDelete range.
If Not .Test(column.Value2) Then
Dim toDelete As Range
Set toDelete = UnionRange(toDelete, column.EntireColumn)
End If
Next
End With
' toDelete is used as it provides better performance
' and it also prevents errors when deleting columns
' while looping.
If Not toDelete Is Nothing Then
toDelete.Delete
Set toDelete = Nothing
End If
End Sub
Delete Columns Not In a List
Option Explicit
Sub DeleteIrrelevantColumns()
' Source - the worksheet containing the list of headers.
Const sName As String = "Sheet2"
Const sFirstCellAddress As String = "A2"
' Destination - the worksheet to be processed.
Const dName As String = "Sheet1"
Const dFirstCellAddress As String = "A1"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim sfCell As Range: Set sfCell = sws.Range(sFirstCellAddress)
Dim sData() As Variant
With sfCell
Dim slCell As Range: Set slCell = .Resize(sws.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
sData = .Resize(slCell.Row - .Row + 1).Value
End With
Dim sDict As Object: Set sDict = CreateObject("Scripting.Dictionary")
sDict.CompareMode = vbTextCompare
Dim sValue As Variant
Dim sr As Long
For sr = 1 To UBound(sData)
sValue = sData(sr, 1)
If Not IsError(sValue) Then ' exclude error values
If Len(sValue) > 0 Then ' exclude blanks
sDict(sValue) = Empty ' write
End If
End If
Next sr
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim dfCell As Range: Set dfCell = dws.Range(dFirstCellAddress)
Dim drg As Range
With dfCell
Dim dlCell As Range: Set dlCell = _
.Resize(, dws.Columns.Count - .Column + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
Set drg = .Resize(, dlCell.Column - .Column + 1)
End With
Dim dData() As Variant: dData = drg.Value
Dim dCells As Range
Dim dValue As Variant
Dim dc As Long
For dc = 1 To UBound(dData, 2)
dValue = dData(1, dc)
If sDict.Exists(dValue) Then
' If duplicate columns, keep only the left-most.
sDict.Remove dValue
Else
' Combine the irrelevant header cells into a range.
If dCells Is Nothing Then
Set dCells = drg.Cells(dc)
Else
Set dCells = Union(dCells, drg.Cells(dc))
End If
End If
Next dc
' Delete columns in one go.
If Not dCells Is Nothing Then
dCells.EntireColumn.Delete
End If
' Inform.
If sDict.Count = 0 Then
MsgBox "Irrelevant columns deleted.", vbInformation
Else
MsgBox "Irrelevant columns deleted." & vbLf & vbLf _
& "Columns not found:" & vbLf _
& Join(sDict.Keys, vbLf), vbCritical
End If
End Sub

Select first empty cell in column AND works for empty column [duplicate]

This question already has answers here:
Find last used cell in Excel VBA
(14 answers)
Closed 1 year ago.
I need to find the first blank cell in a column. The solution for this is easy assuming there are 2 or more filled cells in the column.
Range("A1").End(xlDown).Offset(1, 0).Select
This stops working if the only populated cell is A1 or if A1 is blank.
In these cases it will select the last cell in the workbook.
Is there any work around that will always select the first blank cell in the column even if that cell happens to be A1 or A2?
Here is a solution that tests if the cell we find is empty and if A1 is empty:
Dim Rng As Range
Set Rng = Range("A1").End(xlDown)
If Rng.Value = "" Then
If Range("A1").Value = "" Then
Range("A1").Select
Else
Range("A2").Select
End If
Else
Rng.Offset(1, 0).Select
End If
In the comment you write that you don't like the order of the code, here is another example:
If Range("A1").Value = "" Then
Range("A1").Select
ElseIf Range("A2").Value = "" Then
Range("A2").Select
Else
Range("A1").End(xlDown).Offset(1, 0).Select
End If
And here is another example that avoids the use of End() and Offset():
Dim Cnt As Long
Cnt = ActiveSheet.UsedRange.Rows.Count
If Cnt = 1 And Range("A1").Value = "" Then Cnt = 0
Range("A" & Cnt + 1).Select
If you add a header row, then this example works:
Range("A" & ActiveSheet.UsedRange.Rows.Count + 1).Select
I always include a header row in all sheets with tabular data, to limit special cases - it's also more user friendly.
Find First Empty Cell by Looping
Empty
Except looping through cells, there are various more or less reliable ways to do it.
If there are hidden rows or columns, many of them will not work.
Even worse, if the worksheet is filtered, probably most of them will not work.
The Basic Loop
If you loop through the cells and test each one of them, you will surely get the correct result.
Function RefFirstEmptyCellInColumnBasic( _
ByVal FirstCell As Range) _
As Range
' Validate the given range ('FirstCell').
If FirstCell Is Nothing Then Exit Function
' Create a reference to the Column Range ('crg').
With FirstCell.Cells(1)
Dim crg As Range: Set crg = .Resize(.Worksheet.Rows.Count - .Row + 1)
End With
' Loop.
Dim cCell As Range
' Loop through the cells of the Column Range...
For Each cCell In crg.Cells
' ... until an empty cell is found.
If IsEmpty(cCell) Then
' Create a reference to the current cell.
Set RefFirstEmptyCellInColumnBasic = cCell
Exit Function
End If
Next cCell
End Function
The issue is that it may take a long time. It will 'behave' for a few thousand rows but e.g. if the first empty cell is the last cell in the column, the previous code takes 'forever' (5s) on my machine.
Loop in Memory (Array)
To remedy this, you can introduce an array into the previous code which will reduce the execution time ten times (0.5s). (Note that it will roughly take 0.05s each time for just writing the values to the array.)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the top-most empty cell
' in the one-column range from the first cell of a range
' ('FirstCell') through the last cell in its column.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefFirstEmptyCellInColumn( _
ByVal FirstCell As Range) _
As Range
' Validate the given range ('FirstCell').
If FirstCell Is Nothing Then Exit Function
' Create a reference to the Column Range ('crg').
With FirstCell.Cells(1)
Dim crg As Range: Set crg = .Resize(.Worksheet.Rows.Count - .Row + 1)
End With
' Write the values from the Column Range to the Column Data Array ('cData').
Dim cData As Variant
If crg.Rows.Count = 1 Then ' only one cell
ReDim cData(1 To 1, 1 To 1): cData(1, 1) = crg.Value
Else
cData = crg.Value
End If
' Loop.
Dim r As Long
' Loop through the elements of the Column Data Array...
For r = 1 To UBound(cData, 1)
' ... until an empty value is found.
If IsEmpty(cData(r, 1)) Then
' Create a reference to the r-th cell of the Column Range.
Set RefFirstEmptyCellInColumn = crg.Cells(r)
Exit Function
End If
Next r
End Function
The Test
To test the previous you can do the following.
Sub RefFirstEmptyCellInColumnTEST()
Dim ws As Worksheet: Set ws = ActiveSheet
Dim fCell As Range: Set fCell = ws.Range("A3")
' Empty
Dim feCell As Range: Set feCell = RefFirstEmptyCellInColumn(fCell)
If Not feCell Is Nothing Then
Debug.Print feCell.Address(0, 0)
End If
End Sub
Blank
You can do the same for blank cells i.e. empty cells or cells containing a single quote (') or cells containing formulas evaluating to "". Note that cells containing spaces are neither blank nor empty.
Function RefFirstBlankCellInColumnBasic( _
ByVal FirstCell As Range) _
As Range ' (Empty, ="" and ')
' Validate the given range ('FirstCell').
If FirstCell Is Nothing Then Exit Function
' Create a reference to the Column Range ('crg').
With FirstCell.Cells(1)
Dim crg As Range: Set crg = .Resize(.Worksheet.Rows.Count - .Row + 1)
End With
' Loop.
Dim cCell As Range
' Loop through the cells of the Column Range...
For Each cCell In crg.Cells
' (exclude cell containing error value)
If Not IsError(cCell) Then
' ... until a blank cell is found.
If Len(cCell.Value) = 0 Then
' Create a reference to the current cell.
Set RefFirstBlankCellInColumnBasic = cCell
Exit Function
End If
End If
Next cCell
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the top-most blank cell
' in the one-column range from the first cell of a range
' ('FirstCell') through the last cell in its column.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefFirstBlankCellInColumn( _
ByVal FirstCell As Range) _
As Range ' (Empty, ="" and ')
' Validate the given range ('FirstCell').
If FirstCell Is Nothing Then Exit Function
' Create a reference to the Column Range ('crg').
With FirstCell.Cells(1)
Dim crg As Range: Set crg = .Resize(.Worksheet.Rows.Count - .Row + 1)
End With
' Write the values from the Column Range to the Column Data Array ('cData').
Dim cData As Variant
If crg.Rows.Count = 1 Then ' only one cell
ReDim cData(1 To 1, 1 To 1): cData(1, 1) = crg.Value
Else
cData = crg.Value
End If
' Loop.
Dim r As Long
' Loop through the elements of the Column Data Array...
For r = 1 To UBound(cData, 1)
' (exclude error values)
If Not IsError(cData(r, 1)) Then
' ... until a blank is found.
If Len(cData(r, 1)) = 0 Then
' Create a reference to the r-th cell of the Column Range.
Set RefFirstBlankCellInColumn = crg.Cells(r)
Exit Function
End If
End If
Next r
End Function
Sub RefFirstBlankCellInColumnTEST()
Dim ws As Worksheet: Set ws = ActiveSheet
Dim fCell As Range: Set fCell = ws.Range("A3")
' Blank
Dim fbCell As Range: Set fbCell = RefFirstBlankCellInColumn(fCell)
If Not fbCell Is Nothing Then
Debug.Print fbCell.Address(0, 0)
End If
End Sub

Column Table convert Matrix Table in VBA code

Current i using formula(index and Match) to create matrix i wish using VBA coding, this will make more fast compair to formula. Thanks in advance
enter image description here
Sub columntomatrix
Dim mS As Worksheet
Dim eS As Worksheet
Set mS = ThisWorkbook.Sheets("Matrix")
Set eS = ThisWorkbook.Sheets("Price Entry Book")
Dim Matrix() As String
Dim entryPrice() As String
Dim Product As Range
Dim PriceBook As Range
Set Product = Range("Product")
Set PriceBook = Range("PriceBookName")
With mS.Range("B2")
.Formula = "=IFERROR(INDEX(ListPrice,
MATCH(" & .Offset(0,-1).Address(False, True) & "&" &
.Offset(-1, 0).Address(True, False) & ",ProductKey,0)),"" N/A "")"
Product.Copy
'offset(0,-1) = selected cells move to left 1 column'
.Offset(0, -1).PasteSpecial
PriceBook.Copy
.offset(-1,0) = selected cells move to up 1 row'
.Offset(-1, 0).PasteSpecial Paste:=xlPasteAll, Transpose:=True
With Range(.Offset(0, 0), .Offset(Product.Rows.Count - 2, PriceBook.Rows.Count - 2))
.FillDown
.FillRight
End with
End with
End Sub
Pivot RCV
Copy all four procedures to a standard module, e.g. Module1.
Carefully adjust the values in the Define constants. section of pivotRCV.
Only run the first procedure pivotRCV, the others are being called by it.
The Code
Option Explicit
Sub pivotRCV() ' RCV: Row Labels, Column Labels, and Values
' Define constants.
' Define Source constants.
Const srcName As String = "Price Entry Book"
Const srcFirst As String = "A2"
Const rlCol As Long = 1
Const clCol As Long = 2
Const vCol As Long = 4
Const rlSort As Boolean = False
Const clSort As Boolean = False
' Define Target constants.
Const tgtName As String = "Matrix"
Const tgtFirst As String = "A2"
' Define workbooks.
Dim src As Workbook
Set src = ThisWorkbook
Dim tgt As Workbook
Set tgt = ThisWorkbook
' Define Source Range.
' Define Source Worksheet.
Dim ws As Worksheet
Set ws = src.Worksheets(srcName)
' Define Source Range.
Dim rng As Range
Set rng = defineEndRange(ws.Range(srcFirst))
' Write values from Source Range to arrays.
' Write values from Source Range to 1D Unique Row Labels Array.
Dim rLabels As Variant
rLabels = getUniqueColumn1D(rng.Columns(rlCol).Resize(rng.Rows.Count - 1) _
.Offset(1))
If rlSort Then
sort1D rLabels
End If
' Write values from Source Range to 1D Unique Column Labels Array.
Dim cLabels As Variant
cLabels = getUniqueColumn1D(rng.Columns(clCol).Resize(rng.Rows.Count - 1) _
.Offset(1))
If clSort Then
sort1D cLabels
End If
' Write values from Source Range to 2D Source Array.
Dim Source As Variant
Source = rng.Value
' Prepare to write values from Source Array to Target Array.
' Define Target Array.
Dim Target As Variant
ReDim Target(1 To UBound(rLabels) - LBound(rLabels) + 2, _
1 To UBound(cLabels) - LBound(cLabels) + 2)
' Define counters.
Dim n As Long
Dim i As Long
i = 1
' Write values from Source Arrays to Target Array.
' Write first row/column label.
Target(1, 1) = Source(1, 1)
' Write row labels.
For n = LBound(rLabels) To UBound(rLabels)
i = i + 1
Target(i, 1) = rLabels(n)
Next n
' Write column labels.
Dim j As Long
j = 1
For n = LBound(cLabels) To UBound(cLabels)
j = j + 1
Target(1, j) = cLabels(n)
Next n
' Write values.
For n = 2 To UBound(Source, 1)
i = Application.Match(Source(n, rlCol), rLabels, 0) + 1
j = Application.Match(Source(n, clCol), cLabels, 0) + 1
Target(i, j) = Source(n, vCol)
Next n
' Write values from Target Array to Target Range.
' Define Target Worksheet.
Set ws = tgt.Worksheets(tgtName)
' Define Target First Row Range.
With ws.Range(tgtFirst).Resize(, UBound(Target, 2))
' Clear contents from Target First Row Range to the bottom-most row.
.Resize(ws.Rows.Count - .Row + 1).ClearContents
' Define Target Range.
Set rng = .Resize(UBound(Target, 1))
End With
' Write values from Target Array to Target Range.
rng.Value = Target
' Inform user.
MsgBox "Data transferred.", vbInformation, "Success"
End Sub
' Defines the range from a specified first cell to the last cell
' of its Current Region.
Function defineEndRange(FirstCellRange As Range) _
As Range
' Define Current Region ('rng').
Dim rng As Range
Set rng = FirstCellRange.CurrentRegion
' Define End Range.
Set defineEndRange = FirstCellRange _
.Resize(rng.Rows.Count + rng.Row - FirstCellRange.Row, _
rng.Columns.Count + rng.Column - FirstCellRange.Column)
End Function
' Returns the unique values from a column range.
Function getUniqueColumn1D(ColumnRange As Range, _
Optional ByVal Sorted As Boolean = False) _
As Variant
Dim Data As Variant
Data = ColumnRange.Columns(1).Value
With CreateObject("Scripting.Dictionary")
.CompareMode = vbTextCompare
Dim Key As Variant
Dim i As Long
For i = 1 To UBound(Data, 1)
Key = Data(i, 1)
If Not IsError(Key) And Not IsEmpty(Key) Then
.Item(Key) = Empty
End If
Next i
getUniqueColumn1D = .Keys
End With
End Function
' Sorts a 1D array only if it contains the same data type.
Sub sort1D(ByRef OneD As Variant, _
Optional ByVal Descending As Boolean = False)
With CreateObject("System.Collections.ArrayList")
Dim i As Long
For i = LBound(OneD) To UBound(OneD)
.Add OneD(i)
Next i
.Sort
If Descending Then
.Reverse
End If
OneD = .ToArray
End With
End Sub

Fuction has an array inside, gives back error 1004

I have a formula that works fine when hardcoded but gives me error 1004 when I put it in code. I think it's because I am using an array inside the formula. I tried .FormulaArray but it still returns an error.
ws_a.Range("D2:D" & LastRowCriar).Formula = "=IFERROR(IFERROR(IFERROR(INDEX(IBAN!D:D,MATCH(B2&I2,IBAN!F:F&IBAN!E:E,0)),INDEX(IBAN!D:D,MATCH(B2&I2-1,IBAN!F:F&IBAN!E:E,0))),INDEX(IBAN!D:D,MATCH(B2&I2-2,IBAN!F:F&IBAN!E:E,0))),INDEX(IBAN!D:D,MATCH(B2&I2-3,IBAN!F:F&IBAN!E:E,0)))"
Once again, the formula works when hardcoded, I just need some help on how to use it in VBA. Probably, I have to declare those arrays but I am not sure (if I have to or how to do it).
Hardcoded:
=IFERROR(IFERROR(IFERROR(INDEX(IBAN!D:D;MATCH(B2&I2;IBAN!F:F&IBAN!E:E;0));INDEX(IBAN!D:D;MATCH(B2&I2-1;IBAN!F:F&IBAN!E:E;0)));INDEX(IBAN!D:D;MATCH(B2&I2-2;IBAN!F:F&IBAN!E:E;0)));INDEX(IBAN!D:D;MATCH(B2&I2-3;IBAN!F:F&IBAN!E:E;0)))
Thank you!
Replace Slow Formula With VBA
With this setup, the code will write the resulting values to the 4th ("D") column (cSh1C3) in Sheet1 named "Sheet1". The sheet name wasn't mentioned, so change it appropriately, Also change the other constants to fit your needs. Maybe change cSh1C3 to an empty column first, to see if the code does the expected. There will be no formulas just the values since your formula is slowing down the worksheet considerably. If this code is doing the expected, then the formula isn't. In some cases the results differ, but I think the code is correct. When this happens, check the accuracy manually.
Option Explicit
Sub ReplaceSlowFormulaWithVBA()
' Sheet1
Const cSh1 As String = "Sheet1" ' Sheet1 Name
Const cSh1FR As Long = 2 ' Sheet1 First Row Number
Const cSh1C1 As Variant = 2 ' "B" ' Sheet1 First Column Number/Letter
Const cSh1C2 As Variant = 9 ' "I" ' Sheet1 Second Column Number/Letter
Const cSh1C3 As Variant = 4 ' "D" ' Target Column Number/Letter
' (Sheet1 Third Column Number/Letter)
Const cReduce As Long = 3 ' Reduce Number
' Sheet2
Const cSh2 As String = "IBAN" ' Sheet2 Name
Const cSh2FR As Long = 2 ' Sheet2 First Row Number
Const cSh2C1 As Variant = 6 ' "F" ' Sheet2 First Column Number/Letter
Const cSh2C2 As Variant = 5 ' "E" ' Sheet2 Second Column Number/Letter
Const cSh2C3 As Variant = 4 ' "D" ' Source Column Number/Letter
' Sheet2 Third Column Number/Letter
Dim ws1 As Worksheet ' First Worksheet
Dim ws2 As Worksheet ' Second Worksheet
Dim rng As Range ' Various Ranges
Dim vnt1 As Variant ' Sheet1 Array
Dim vnt1C1 As Variant ' Sheet1 First Column Array
Dim vnt1C2 As Variant ' Sheet1 Second Column Array
Dim vntT As Variant ' Target Array (Sheet1 Third Column Array)
Dim vnt2 As Variant ' Sheet2 Array
Dim vnt2C1 As Variant ' Sheet2 First Column Array
Dim vnt2C2 As Variant ' Sheet2 Second Column Array
Dim vntS As Variant ' Source Array (Sheet2 Third Column Array)
Dim LR As Long ' Last Row Compare Number
Dim sh1LR As Long ' Sheet1 (Current) Last Row Number
Dim sh2LR As Long ' Sheet2 (Current) Last Row Number
Dim UB1 As Long ' Sheet1 Arrays Upper Bound
Dim UB2 As Long ' Sheet2 Arrays Upper Bound
Dim i As Long ' Various Counters
Dim j As Long ' Second Array Elements Counter
Dim k As Long ' Reduce Counter
Dim lng1 As Long ' Current Sheet1 Array Value
Dim lng2 As Long ' Current Sheet2 Array Value
' IN RANGES
' Define Worksheets.
Set ws1 = ThisWorkbook.Worksheets(cSh1)
Set ws2 = ThisWorkbook.Worksheets(cSh2)
' Calculate Sheet1 Last Row Number.
Set rng = ws1.Columns(cSh1C1): GoSub LastRow: sh1LR = LR
Set rng = ws1.Columns(cSh1C2): GoSub LastRow
If LR > sh1LR Then sh1LR = LR
' Calculate Sheet2 Last Row Number.
Set rng = ws2.Columns(cSh2C1): GoSub LastRow: sh2LR = LR
Set rng = ws2.Columns(cSh2C2): GoSub LastRow
If LR > sh2LR Then sh2LR = LR
Set rng = ws2.Columns(cSh2C3): GoSub LastRow
If LR > sh2LR Then sh2LR = LR
' Write Column Ranges to Arrays.
vnt1C1 = ws1.Cells(cSh1FR, cSh1C1).Resize(sh1LR - cSh1FR + 1)
vnt1C2 = ws1.Cells(cSh1FR, cSh1C2).Resize(sh1LR - cSh1FR + 1)
vnt2C1 = ws2.Cells(cSh2FR, cSh2C1).Resize(sh2LR - cSh2FR + 1)
vnt2C2 = ws2.Cells(cSh2FR, cSh2C2).Resize(sh2LR - cSh2FR + 1)
vntS = ws2.Cells(cSh2FR, cSh2C3).Resize(sh2LR - cSh2FR + 1)
' Define Target Range.
Set rng = ws1.Cells(cSh1FR, cSh1C3).Resize(sh1LR - cSh1FR + 1)
' Release worksheet object variables.
Set ws2 = Nothing
Set ws1 = Nothing
' IN ARRAYS
' Define and populate Sheet1 Array from the two Sheet1 Column Arrays.
UB1 = UBound(vnt1C1)
ReDim vnt1(1 To UB1) ' 1D 1-based (1-row)
For i = 1 To UB1: vnt1(i) = vnt1C1(i, 1) & vnt1C2(i, 1): Debug.Print vnt1(i): Next i
' Erase the two Sheet1 Column Arrays.
Erase vnt1C1: Erase vnt1C2
' Define and populate Sheet2 Array from the two Sheet2 Column Arrays.
UB2 = UBound(vnt2C1)
ReDim vnt2(1 To UB2) ' 1D 1-based (1-row)
For i = 1 To UB2: vnt2(i) = vnt2C1(i, 1) & vnt2C2(i, 1): Next i
' Erase the two Sheet2 Column Arrays.
Erase vnt2C1: Erase vnt2C2
' Resize Target Array to rows defined by the number of elements
' in Sheet1 Array.
ReDim vntT(1 To UB1, 1 To 1) ' 2D 1-based 1-column
' Loop through elements of Sheet1 Array.
For i = 1 To UB1
If IsNumeric(vnt1(i)) Then
' Loop through Reduce Values.
For k = 0 To cReduce
' Calculate Current Sheet1 Array Value.
lng1 = vnt1(i) - k
' Loop through elements of Sheet2 Array.
For j = 1 To UB2
If IsNumeric(vnt2(j)) Then
' Calculate Current Sheet2 Array Value.
lng2 = vnt2(j)
' Compare current Sheet1 and Sheet2 Array Values.
If lng1 = lng2 Then
' Write value of current element (row) in Source
' Array to current element (row) in Target Array.
vntT(i, 1) = vntS(j, 1)
' Ensure exiting "For k"-loop immediately after
' exiting "For j"-loop.
k = cReduce
' Exit "For j"-loop.
Exit For
End If
End If
Next j
Next k
End If
Next i
' IN RANGES
' Write Target Array to Target Range.
rng = vntT
Exit Sub
LastRow:
LR = 0
Set rng = rng.Find("*", , xlFormulas, , , xlPrevious)
If Not rng Is Nothing Then LR = rng.Row
Return
End Sub

how to merge cells with same value in one row

How do I merge cells with the same value and color in a row?
and the result should be :
I think you could try this:
Option Explicit
Sub test()
Dim LastRow As Long, i As Long, Value As Long
Dim Color As Double
With ThisWorkbook.Worksheets("Sheet1")
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = LastRow To 2 Step -1
Value = .Range("A" & i).Value
Color = .Range("A" & i).Interior.Color
If .Range("A" & i - 1).Value = Value And .Range("A" & i - 1).Interior.Color = Color Then
.Rows(i).EntireRow.Delete
End If
Next i
End With
End Sub
Copy Consecutive to One
Adjust the values in the constants section to fit your needs.
The image looks like you want all this to happen in the same column
of the same worksheet, which is adjusted in the constants section.
Before writing to Target Column (cTgtCol), the code will clear its
contents. Be careful not to lose data.
Colors are applied using a loop, which will slow down the fast array approach of copying the data.
The Code
Sub CopyConsecutiveToOne()
' Source
Const cSource As Variant = "Sheet1" ' Worksheet Name/Index
Const cSrcCol As Variant = "A" ' Column Letter/Number
Const cSrcFR As Long = 1 ' Column First Row Number
' Target
Const cTarget As Variant = "Sheet1" ' Worksheet Name/Index
Const cTgtCol As Variant = "A" ' Column Letter/Number
Const cTgtFR As Long = 1 ' Column First Row Number
Dim rng As Range ' Source Column Last Used Cell Range,
' Source Column Range, Target Column Range
Dim vntS As Variant ' Source Array
Dim vntT As Variant ' Target Array
Dim vntC As Variant ' Color Array
Dim i As Long ' Source Range/Array Row/Element Counter
Dim k As Long ' Target/Color Array Element Counter
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
'On Error GoTo ProcedureExit
' In Source Worksheet
With ThisWorkbook.Worksheets(cSource).Columns(cSrcCol)
' Calculate Source Column Last Used Cell Range.
Set rng = .Find("*", , xlFormulas, xlWhole, xlByColumns, xlPrevious)
' Check if data in Source Column.
If Not rng Is Nothing Then ' Data found.
' Calculate Source Range.
Set rng = .Range(.Cells(cSrcFR), .Cells(rng.Row))
' Copy values from Source Range to Source Array.
vntS = rng
Else ' Data Not Found.
With .Cells(1)
MsgBox "No Data in column '" & .Split(.Address, "$")(1) & "'."
GoTo ProcedureExit
End With
End If
End With
' In Arrays
' Count the number of elements in Target/Color Array.
k = 1 ' The first element will be included before the loop.
' Loop through elements of Source Array.
For i = 2 To UBound(vntS)
' Check if current value is different then the previous one.
If vntS(i, 1) <> vntS(i - 1, 1) Then
' Count element of Target/Color Array.
k = k + 1
End If
Next
' Write to Target/Color Arrays
' Resize Target/Color Arrays.
ReDim vntT(1 To k, 1 To 1)
ReDim vntC(1 To k, 1 To 1)
' Reset Counter
k = 1 ' The first element will be included before the loop.
' Write first value from Source Array to Target Array.
vntT(1, 1) = vntS(1, 1)
' Write first color value to Target Color Array.
vntC(1, 1) = rng.Cells(1, 1).Interior.Color
' Loop through elements of Source Array.
For i = 2 To UBound(vntS)
' Check if current value is different then the previous one.
If vntS(i, 1) <> vntS(i - 1, 1) Then
' Count element of Target/Color Array.
k = k + 1
' Write from Source Array to Target Array.
vntT(k, 1) = vntS(i, 1)
' Write color values from Source Range to Color Array.
vntC(k, 1) = rng.Cells(i, 1).Interior.Color
End If
Next
' All necessary data is in Target/Color Arrays.
Erase vntS
Set rng = Nothing
' In Target Worksheet
With ThisWorkbook.Worksheets(cTarget).Cells(cTgtFR, cTgtCol)
' Clear contents of range from Target First Cell to Target Bottom Cell.
.Resize(Rows.Count - .Row + 1).ClearContents
' Calculate Target Column Range.
Set rng = .Resize(k)
' Copy Target Array to Target Range.
rng = vntT
' Apply colors to Target Range.
With rng
' Loop through cells of Target Column Range.
For i = 1 To k
' Apply color to current cell of Target Range using the values
' from Color Array.
.Cells(i, 1).Interior.Color = vntC(i, 1)
Next
End With
End With
ProcedureExit:
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
End Sub
Create a custom function in Visual Basic Editor that will return to the color index of the cell:
Function COLOR(Target As Range)
COLOR = Target.Interior.ColorIndex
End Function
Then in the right column use a formula similar to this:
=IF(OR(A2<>A3,COLOR(A2)<>COLOR(A3)),1,0)
Then filter to show only 1's.

Resources