So I have a VBA macro that lets me use Cells like a Userform.
What it basically does is, that it takes the Values of the defined Range("E2:E11")
and transposes them to the next BlankRow.
So with that the User can easily generate a Matrix that ranges from C16:L100
The VBA Code looks like this:
Sub NeuesKFZ()
Dim sh As Worksheet, arr, lastERow As Long, matchCel As Range
Set sh = ActiveSheet
arr = sh.Range("E2:E11").Value
lastERow = sh.Range("C" & sh.Rows.Count).End(xlUp).Row + 1
If Range("E2") = "" Then
MsgBox "Wählen Sie ein KFZ aus!"
Range("E2").Select
Exit Sub
End If
If lastERow < 16 Then lastERow = 16
'check if the range has not been already copied:
Set matchCel = sh.Range("C16:C" & lastERow - 1).Find(WHAT:=sh.Range("E2").Value, LookIn:=xlValues, Lookat:=xlWhole, MatchCase:=False)
If Not matchCel Is Nothing Then
If MsgBox(sh.Range("E2").Value & " Existiert bereits " & vbCrLf & "Sollen die Daten aktualisiert werden?", vbYesNo) = vbYes Then
sh.Range("C" & matchCel.Row).Resize(1, UBound(arr)).Value = Application.Transpose(arr)
End If
sh.Range("E2:E11").ClearContents
Exit Sub
End If
sh.Range("C" & lastERow).Resize(1, UBound(arr)).Value = Application.Transpose(arr)
sh.Range("E2:E11").ClearContents
End Sub
My Problem is now that excel doesn't seem to know what kind of values are getting transposed to the new cells.
In my case I'm working with Dates and I want to know if a date is within the next three months.
But Excel doesn't recognize that it is working with date values.
Trying to format or delete all formatting doesn't help.
And when I'm trying to use this formula:
=if(and(C16>=$AA16$,C16<=$AB$17),TRUE,FALSE)
Some explenation:
C16 is the value that got transposed by the Macro.
AA16 and AB16 are the starting date and end date.
TRUE and FALSE are just to give me feedback if it works or not.
It just gives me FALSE all the time.
Is there a way to get the Date transposed so Excel still knows that its a Date?
Or Maybe force Excel to handle those values as dates.
Transpose Column
I think I read somewhere that besides the size limitation, Transpose 'doesn't like' dates. Anyway, make the following corrections to your code and copy the function to a standard module of your workbook.
Corrections
Replace arr = sh.Range("E2:E11").Value
with arr = GetRowData(sh.Range("E2:E11")).
Replace sh.Range("C" & matchCel.Row).Resize(1, UBound(arr)).Value = Application.Transpose(arr)
with sh.Range("C" & matchCel.Row).Resize(1, UBound(arr, 2)).Value = arr.
Replace sh.Range("C" & lastERow).Resize(1, UBound(arr)).Value = Application.Transpose(arr)
with sh.Range("C" & lastERow).Resize(1, UBound(arr, 2)).Value = arr
The Function
Function GetRowData( _
ByVal ColumnRange As Range) _
As Variant
If ColumnRange Is Nothing Then Exit Function
With ColumnRange.Columns(1)
Dim rCount As Long: rCount = .Rows.Count
Dim rData As Variant
If rCount = 1 Then
ReDim rData(1 To 1, 1 To 1): rData(1, 1) = .Value
Else
Dim cData As Variant: cData = .Value
ReDim rData(1 To 1, 1 To rCount)
Dim r As Long
For r = 1 To rCount
rData(1, r) = cData(r, 1)
Next r
End If
GetRowData = rData
End With
End Function
EDIT
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns a 2D one-based one-row array containing the values
' from a one-column range.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRowFromColumn( _
ByVal ColumnRange As Range) _
As Variant
If ColumnRange Is Nothing Then Exit Function
With ColumnRange.Columns(1)
Dim rCount As Long: rCount = .Rows.Count
Dim rData As Variant
If rCount = 1 Then
ReDim rData(1 To 1, 1 To 1): rData(1, 1) = .Value
Else
Dim cData As Variant: cData = .Value
ReDim rData(1 To 1, 1 To rCount)
Dim r As Long
For r = 1 To rCount
rData(1, r) = cData(r, 1)
Next r
End If
GetRowFromColumn = rData
End With
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns a 2D one-based one-column array containing the values
' from a one-row range.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetColumnFromRow( _
ByVal RowRange As Range) _
As Variant
If RowRange Is Nothing Then Exit Function
With RowRange.Rows(1)
Dim cCount As Long: cCount = .Columns.Count
Dim cData As Variant
If cCount = 1 Then
ReDim cData(1 To 1, 1 To 1): cData(1, 1) = .Value
Else
Dim rData As Variant: rData = .Value
ReDim cData(1 To cCount, 1 To 1)
Dim c As Long
For c = 1 To cCount
cData(c, 1) = rData(1, c)
Next c
End If
GetColumnFromRow = cData
End With
End Function
Related
I have a dataset in excel which looks like this:
There can be more cells with data. I am trying to extract data from these big cells and paste values to more comprehensive table, which should look like this:
What would be the best way to proceed? I imagine process should look like this:
Select range of filled cells, store row count as value
Do a loop for that many rows as value
Store whole cell value as string
Find "Btc = *" and store it as btc value. Paste that value into prefered table
Find "Qua= *" and store it as qua value. Paste that value into prefered table
..etc
Clean up cells in new table using Replace
I am stuck on extracting part of text to value. What function can I use to assign that "Btc = *" to variable? Like operator gets be a whole string, but I only need parts of it
Or maybe you have ideas on how to do this task easier?
Please, try the next code. It uses arrays and will be very fast (working mostly in memory). Please adapt your real sheet used as destination, where the processed result to be dropped (shDest). Now, the code returns in the next sheet. If you want it as it is, please insert/have an empty sheet after the one to be processed:
Sub extractValTranspose()
Dim sh As Worksheet, shDest As Worksheet, lastR As Long, arr, arrFin
Dim i As Long, k As Long, j As Long
Set sh = ActiveSheet 'use here the sheet to be processed
Set shDest = sh.Next 'use here the sheet where you want the processed result to be dropped
lastR = sh.Range("A" & sh.Rows.Count).End(xlUp).Row 'last row in A:A
arr = sh.Range("A2:A" & lastR).Value2 'place the range in an array for faster iteration/procesing
ReDim arrFin(1 To UBound(arr) / 10 + 1, 1 To 10) 'redim the final array (to keep the processed data)
'extract headers array:
For i = 1 To 10
arrFin(1, i) = Split(Replace(arr(i, 1), " ", ""), "=")(0) 'place the header on the first array row
arrFin(2, i) = Split(Replace(arr(i, 1), " ", ""), "=")(1) 'place the data on the second row
Next i
k = 3: j = 1 'initialize variables keeping the (next) row and columns
'place the rest of rows data:
For i = 11 To UBound(arr)
arrFin(k, j) = Split(Replace(arr(i, 1), " ", ""), "=")(1): j = j + 1
If j = 11 Then k = k + 1: j = 1 'reinitialize the variable for each 10 rows
Next
'drop the array content, at once and do a little formatting:
With shDest.Range("A1").Resize(UBound(arrFin), UBound(arrFin, 2))
.Value2 = arrFin
.EntireColumn.AutoFit
.Borders.Color = vbBlack
.BorderAround 1, xlMedium
With .Rows(1)
.Font.Bold = True
.BorderAround 1, xlMedium
.HorizontalAlignment = xlCenter
End With
End With
MsgBox "Ready..."
shDest.Activate
End Sub
Edited:
Nothing from your question made me understand that all groups of 10 strings to be processed are in the same cell...
Please, test the next piece of code able to process the above mentioned range type:
Sub extractValSameCellTranspose()
Dim sh As Worksheet, shDest As Worksheet, lastR As Long
Dim arr, arrCell, arrFin, i As Long, k As Long, j As Long
Set sh = ActiveSheet 'use here the sheet to be processed
Set shDest = sh.Next 'use here the sheet where you want the processed result to be dropped
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row 'last row in A:A
arr = sh.Range("A2:A" & lastR).Value2 'place the range in an array for faster iteration/procesing
ReDim arrFin(1 To UBound(arr), 1 To 10) 'redim the final array (to keep the processed data)
'extract headers and first row of data:
arrCell = Split(arr(1, 1), vbLf) 'split the cell content on vbLf (end of line)
For i = 0 To UBound(arrCell) 'iterate between the above array elements
arrFin(1, i + 1) = Split(VBA.replace(arrCell(i), " ", ""), "=")(0) 'place the header on the first array row
arrFin(2, i + 1) = Split(VBA.replace(arrCell(i), " ", ""), "=")(1) 'place the data on the second row
Next i
k = 3 'initialize variable keeping the array rows
'place the rest of rows data:
For i = 3 To UBound(arr)
arrCell = Split(arr(i, 1), vbLf)
For j = 0 To UBound(arrCell)
arrFin(k, j + 1) = Split(VBA.replace(arrCell(j), " ", ""), "=")(1)
Next j
k = k + 1
Next
'drop the array content, at once and do a little formatting:
With shDest.Range("A1").Resize(UBound(arrFin), UBound(arrFin, 2))
.Value2 = arrFin 'drop the array content
.EntireColumn.AutoFit 'autofit all columns
.Borders.Color = vbBlack 'place some thin borders on each cell
.BorderAround 1, xlMedium 'place a thicker border arround the whole range
With .rows(1) 'format the first (headers) row:
.Font.Bold = True 'make the font bold
.BorderAround 1, xlMedium 'place a thicker border arround
.HorizontalAlignment = xlCenter 'center the string horizontaly
End With
End With
MsgBox "Ready..."
shDest.Activate 'activate the sheet where the processed result has been dropped
End Sub
Please, send some feedback after testing it.
Transform Data With Split
Sub TransformData()
' Define constants.
Const SRC_NAME As String = "Sheet1"
Const SRC_COLUMN As Long = 1
Const SRC_ROW_DELIMITER As String = vbLf ' vbCrLf?
Const SRC_COL_DELIMITER As String = "="
Const DST_NAME As String = "Sheet2"
Const DST_FIRST_COLUMN As Long = 2
Const OVERWRITE_MODE As Boolean = False
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Write the source data to an array, the source array ('sData').
Dim sws As Worksheet: Set sws = wb.Sheets(SRC_NAME)
Dim sData() As Variant, srCount As Long
With sws.UsedRange
srCount = .Rows.Count - 1
If srCount = 0 Then
MsgBox "No data in the source worksheet.", vbExclamation
Exit Sub
End If
With .Columns(SRC_COLUMN).Resize(srCount).Offset(1) ' source data range
If srCount = 1 Then
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = .Value
Else
sData = .Value
End If
End With
End With
' Write the destination header data to an array ('hData') and reference
' the first destination row range ('dfrrg').
Dim dws As Worksheet: Set dws = wb.Sheets(DST_NAME)
Dim dfrrg As Range, hData() As Variant, dcCount As Long, dc As Long
With dws.UsedRange
Dim dcOffset As Long: dcOffset = DST_FIRST_COLUMN - 1
dcCount = .Columns.Count - dcOffset
With .Columns(1).Resize(, dcCount).Offset(, dcOffset) ' header range
If OVERWRITE_MODE Then
Set dfrrg = .Rows(1).Offset(1)
Else
Set dfrrg = .Rows(1).Offset(.Rows.Count)
End If
hData = .Value
End With
End With
' Write the destination header data to a dictionary.
Dim hDict As Object: Set hDict = CreateObject("Scripting.Dictionary")
hDict.Comparemode = vbTextCompare
For dc = 1 To dcCount: hDict(hData(1, dc)) = dc: Next dc
If hDict.Count < dcCount Then
MsgBox "No duplicates are allowed in the destination header row.", _
vbExclamation
Exit Sub
End If
Erase hData
' Define the destination array ('dData').
' Note that fewer rows are possible if blank or missing source values.
Dim dData() As Variant: ReDim dData(1 To srCount, 1 To dcCount)
' Using the dictionary, write the matching values from the source array
' to the destination array.
Dim sr As Long, sc As Long, sPos As Long, dr As Long, vLen As Long
Dim sArr() As String, sString As String, sTitle As String, sValue As String
Dim InNextRow As Boolean
For sr = 1 To srCount
sArr = Split(CStr(sData(sr, 1)), SRC_ROW_DELIMITER)
For sc = 0 To UBound(sArr)
sString = CStr(sArr(sc))
sPos = InStr(sString, SRC_COL_DELIMITER)
If sPos > 1 Then ' column delimiter it not the first character
sTitle = Left(sString, sPos - 1)
If hDict.Exists(sTitle) Then ' title found in the dictionary
vLen = Len(sString) - sPos
If vLen > 0 Then ' has a value
sValue = Right(sString, vLen)
If Not InNextRow Then dr = dr + 1: InNextRow = True
dData(dr, hDict(sTitle)) = sValue
End If
End If
End If
Next sc
If InNextRow Then InNextRow = False
Next sr
If dr = 0 Then
MsgBox "No values found in the source worksheet.", vbExclamation
Exit Sub
End If
Erase sData
' Write the values from the destination array to the destination range.
dfrrg.Resize(dr).Value = dData ' 'dr', not 'srCount'
If OVERWRITE_MODE Then ' clear below
dfrrg.Resize(dws.Rows.Count - dfrrg.Row - dr + 1).Offset(dr).Clear
End If
' Inform.
MsgBox "Data transformed.", vbInformation
End Sub
I've written the below function intended to take an input array, delete the duplicates and return an array of unique values. I've looked at other functions open source that are similar but could not get them to work either. Watching both input array and the function arrays, Arr and ArrCopy, they have the correct number and value for each index. Any ideas why I'm getting an out of range error?
Public Function getUnique(Arr As Variant) As Variant
Dim ArrCopy As Variant
Dim i As Variant
Dim j As Variant
Dim counter As Integer
'copies input array, loops through copy and clears dupates
ArrCopy = Arr
For i = LBound(Arr) To UBound(Arr)
For j = LBound(ArrCopy) To UBound(ArrCopy)
If Arr(i) = ArrCopy(j) And i <> j Then
ArrCopy(j).Clear
End If
Next j
Next i
'clears array, loops through copy and puts nonzero values back in Arr
Arr.Clear
counter = 0
For i = LBound(ArrCopy) To UBound(ArrCopy)
If ArrCopy(i) <> "" Then
ReDim Preserve Arr(0 To counter)
Arr(counter) = ArrCopy(i)
counter = counter + 1
End If
Next i
'returns unique values
getUnique = Arr
End Function
Update: This is how the array gets loaded. From FaneDuru's comment, I see in the watch table that the input array is actually 2D, so that's why I'm getting an out of range error....
'removes blanks from AO
wks.AutoFilterMode = False
wks.Range("A1:BO" & lastrow).AutoFilter Field:=41, Criteria1:="<>", Operator:=xlFilterValues
Set rng = wks.Range("AO2:AO" & lastrow).SpecialCells(xlCellTypeVisible)
'loads SNs into array
Erase serialNum
serialNum = rng.Value
Update 2:
This has me a lot closer. Using the 2d approach This will set all of the repeats to 0. Then I call a delete element sub I found (Deleting Elements in an Array if Element is a Certain value VBA). I am modifying the original to work with 2D array. I am getting a subscript out of range error on my Redim Preserve line within the DeleteElementAt() sub.
Public Function GetUnique(Arr As Variant) As Variant
Dim i As Variant
Dim j As Variant
Dim counter As Integer
For i = LBound(Arr) To UBound(Arr)
For j = LBound(Arr) To UBound(Arr)
If i <> j And Arr(i, 1) = Arr(j, 1) Then
Arr(j, 1) = "0"
End If
Next j
Next i
counter = 0
For i = LBound(Arr) To UBound(Arr)
If Arr(i, 1) = "0" Then
Call DeleteElementAt(i, Arr)
ReDim Preserve Arr(0 To UBound(Arr))
End If
Next i
GetUnique = Arr
End Function
Public Sub DeleteElementAt(ByVal index As Integer, ByRef Arr As Variant)
Dim i As Integer
' Move all element back one position
For i = index + 1 To UBound(Arr)
Arr(index, 1) = Arr(i, 1)
Next i
' Shrink the array by one, removing the last one
'ERROR HERE
ReDim Preserve Arr(LBound(Arr) To UBound(Arr) - 1, 1)
End Sub
Return the Unique Values From a Range in an Array
Option Explicit
Sub Test()
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim rg As Range: Set rg = ws.Range("A2:J21")
Dim Data As Variant: Data = GetRange(rg)
Dim Arr As Variant: Arr = ArrUniqueData(Data)
' Continue using 'Arr', e.g.:
If Not IsEmpty(Arr) Then
Debug.Print Join(Arr, vbLf)
Else
Debug.Print "Nope."
End If
' Dim n As Long
' For n = 0 To UBound(Arr)
' Debug.Print Arr(n)
' Next n
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a range ('rg') in a 2D one-based array.
' Remarks: If ˙rg` refers to a multi-range, only its first area
' is considered.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRange( _
ByVal rg As Range) _
As Variant
Const ProcName As String = "GetRange"
On Error GoTo ClearError
If rg.Rows.Count + rg.Columns.Count = 2 Then ' one cell
Dim Data As Variant: ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
GetRange = Data
Else ' multiple cells
GetRange = rg.Value
End If
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Writes the unique values from a 2D array
' to a 1D zero-based array, excluding error values and blanks.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function ArrUniqueData( _
Data As Variant, _
Optional ByVal CompareMethod As VbCompareMethod = vbTextCompare) _
As Variant
Const ProcName As String = "ArrUniqueDatae"
On Error GoTo ClearError
Dim cLower As Long: cLower = LBound(Data, 2)
Dim cUpper As Long: cUpper = UBound(Data, 2)
Dim Key As Variant
Dim r As Long
Dim C As Long
With CreateObject("Scripting.Dictionary")
.CompareMode = CompareMethod
For r = LBound(Data, 1) To UBound(Data, 1)
For C = cLower To cUpper
Key = Data(r, C)
If Not IsError(Key) Then ' exclude error values
If Len(Key) > 0 Then ' exclude blanks
.Item(Key) = Empty
End If
End If
Next C
Next r
If .Count = 0 Then Exit Function
ArrUniqueData = .Keys
End With
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
EDIT
This will continue your sub using the (SpecialCells) filtered one-column range. You still need the previous procedures (except the Test procedure) and there is a new function below.
' This is your procedure!
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: ...
' Calls: GetFilteredColumn
' GetRange
' ArrUniqueData
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub YourProcedure()
' ... whatever
Set Rng = wks.Range("AO2:AO" & lastrow).SpecialCells(xlCellTypeVisible)
'Erase serialNum ' you don't need to erase
serialNum = GetFilteredColumn(Rng)
Dim Arr As Variant: Arr = ArrUniqueData(serialNum)
' Continue using 'Arr', e.g.:
If Not IsEmpty(Arr) Then
Debug.Print Join(Arr, vbLf)
Else
Debug.Print "Nope."
End If
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the filtered values of a column range
' in a 2D one-based array.
' Calls: GetRange.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetFilteredColumn( _
ByVal FilteredColumnRange As Range) _
As Variant
Const ProcName As String = "GetFilteredColumn"
On Error GoTo ClearError
With FilteredColumnRange
Dim aCount As Long: aCount = .Areas.Count
Dim aData As Variant: ReDim aData(1 To aCount)
Dim arg As Range
Dim a As Long
For Each arg In .Areas
a = a + 1
aData(a) = GetRange(arg)
Next arg
Dim dData As Variant: ReDim dData(1 To .Cells.Count, 1 To 1)
Dim sr As Long
Dim dr As Long
For a = 1 To aCount
For sr = 1 To UBound(aData(a), 1)
dr = dr + 1
dData(dr, 1) = aData(a)(sr, 1)
Next sr
Next a
GetFilteredColumn = dData
End With
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
When you assign the values of an Excel range to a variant in VBA you always get a 2D array even if your range is a single column or row i.e. you get an array that is dimensioned as (1 to X,1 to 1). To get an array with dimensions (1 to X) you need to encapsulate the 'get values' code in a worksheetFunction.Transpose() call.
Assuming you have got your array into a 1D form you can then use either an ArrayList or Scripting.Dictionary to simplify compiling unique values. No need to get messyt with array indeces at all.
This is the ArrayList Version
Public Function getUnique(Arr As Variant) As Variant
Dim myList As Object
Set myList = CreateObject("System.collections.Arraylist")
Dim myItem As Variant
For Each myItem In Arr
If myItem <> 0 Then
If Not myList.Contains(myItem) Then
myList.Add myItem
End If
End If
Next
getUnique = myList.toarray
End Function
This is the Scripting.Dictionary version
Public Function getUnique(Arr As Variant) As Variant
Dim myList As Object
Set myList = CreateObject("Scripting.Dictionary")
Dim myItem As Variant
For Each myItem In Arr
If myItem <> 0 Then
If Not myList.exists(myItem) Then
myList.Add myList.Count, myItem
End If
End If
Next
getUnique = myList.Items
End Function
I have workbook with three sheets.
I copy data from sheet1 to sheet2 & sheet3 depend on specific condition on sheet1, value = "Yes" on columns T or U.
The below code works fine using for Loop, but it is slow.
Now I transferred all data of sheet1 to array .
MyArray = Sheet1.Range("A3:U" & LastRow).Value2
is it possible to copy data from this array (by condition if specific value on it) to the other sheets .
I am new to vba , so any help will be appreciated .
Sub Copy_Data_On_Condition()
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Dim LastRow As Long
Dim ris_column As Range
Dim cell As Object
Dim DestRng As Range
Dim MyArray() As Variant
LastRow = Sheet1.Cells(Rows.count, 1).End(xlUp).Row
MyArray = Sheet1.Range("A3:U" & LastRow).Value2
Set ris_column = Sheet1.Range("T3:T" & LastRow)
For Each cell In ris_column
If cell.value = "Yes" Then
Set DestRng = Sheet2.Range("A" & Rows.count).End(xlUp).Offset(1, 0)
cell.EntireRow.Copy DestRng
End If
Next cell
Set ris_column = Sheet1.Range("U3:U" & LastRow)
For Each cell In ris_column
If cell.value = "Yes" Then
Set DestRng = Sheet3.Range("A" & Rows.count).End(xlUp).Offset(1, 0)
cell.EntireRow.Copy DestRng
End If
Next cell
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Update: Both two answers works perfectly , I tested on a sheet with total 2180 rows and copied rows about 1200. "FaneDure" Code takes about 4 second to finish and "Super Symmetry" code takes 0.07 of second which is significantly faster .
Please, try the next code:
Sub Copy_Data_On_Condition()
Dim sh1 As Worksheet, sh2 As Worksheet, sh3 As Worksheet, LastRow As Long
Dim arr_column, rngT As Range, rngU As Range, i As Long, lastCol As Long
Set sh1 = Sheet1: Set sh2 = Sheet2: Set sh3 = Sheet3 'only to make the code more compact
LastRow = sh1.cells(rows.count, 1).End(xlUp).row 'last row in A:A column
lastCol = sh1.UsedRange.Columns.count 'last column of Sheet1, to avoid copying the whole row
arr_column = sh1.Range("T3:U" & LastRow).Value2 'put in an array the columns to be processed against "Yes" string
'process both columns in the same iteration to make code faster
For i = 1 To UBound(arr_column) 'iterate between the array rows and process the columns values
If arr_column(i, 1) = "Yes" Then 'finding a match in column T:T:
If rngT Is Nothing Then 'if the rngT keeping the range to be copied is not Set (yet)
Set rngT = sh1.Range(sh1.cells(i + 2, 1), sh1.cells(i + 2, lastCol)) 'the range is Set by the used range suitable row
Else
Set rngT = Union(rngT, sh1.Range(sh1.cells(i + 2, 1), sh1.cells(i + 2, lastCol))) 'add the suitable row to the existing range
End If
End If
If arr_column(i, 2) = "Yes" Then 'finding a match in column U:U:
If rngU Is Nothing Then 'if the rngU keeping the range to be copied is not Set (yet)
Set rngU = sh1.Range(sh1.cells(i + 2, 1), sh1.cells(i + 2, lastCol)) 'the range is Set by the used range suitable row
Else
Set rngU = Union(rngU, sh1.Range(sh1.cells(i + 2, 1), sh1.cells(i + 2, lastCol))) 'add the suitable row to the existing range
End If
End If
Next i
If Not rngT Is Nothing Then 'if rngT has been set (it contains at least a row), copy it in Sheet2
rngT.Copy Destination:=sh2.Range("A" & sh2.rows.count).End(xlUp).Offset(1) 'copy the range at once
End If
If Not rngU Is Nothing Then 'if rngU has been set (it contains at least a row), copy it in Sheet3
rngU.Copy Destination:=sh3.Range("A" & sh3.rows.count).End(xlUp).Offset(1) 'copy the range at once
End If
End Sub
Because a direct autofilter is not an option, processing the array in memory should give you the fastest result as it minimises the interaction of VBA with the excel application. I believe the following should make your code significantly faster:
Sub Copy_Data_On_Condition()
Dim dStart As Double: dStart = Timer
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Dim srcData As Variant
Dim sht2Data() As Variant
Dim sht2Rows As Long
Dim sht2CriteriaCol As Long: sht2CriteriaCol = 20 'T
Dim sht3Data() As Variant
Dim sht3Rows As Long
Dim sht3CriteriaCol As Long: sht3CriteriaCol = 21 'U
Dim outputCols As Long
Dim i As Long, j As Long
With Sheet1
srcData = .Range("A3:U" & .Cells(Rows.Count, 1).End(xlUp).Row).Value
End With
outputCols = UBound(srcData, 2)
For i = LBound(srcData) To UBound(srcData)
If srcData(i, sht2CriteriaCol) = "Yes" Then
sht2Rows = sht2Rows + 1
ReDim Preserve sht2Data(1 To outputCols, 1 To sht2Rows)
For j = 1 To outputCols
sht2Data(j, sht2Rows) = srcData(i, j)
Next j
End If
If srcData(i, sht3CriteriaCol) = "Yes" Then
sht3Rows = sht3Rows + 1
ReDim Preserve sht3Data(1 To outputCols, 1 To sht3Rows)
For j = 1 To outputCols
sht3Data(j, sht3Rows) = srcData(i, j)
Next j
End If
Next i
If sht2Rows > 0 Then
Sheet2.Cells(Rows.Count, "A").End(xlUp).Offset(1, 0).Resize(sht2Rows, outputCols).Value = WorksheetFunction.Transpose(sht2Data)
End If
If sht3Rows > 0 Then
Sheet3.Cells(Rows.Count, "A").End(xlUp).Offset(1, 0).Resize(sht3Rows, outputCols).Value = WorksheetFunction.Transpose(sht3Data)
End If
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
MsgBox "Time taken: " & Format(Timer - dStart, "0.000s")
End Sub
Another fast option is to add a dummy sheet (if possible), use autofilter then delete the dummy worksheet. This is very fast and the code is very simple:
Sub Copy_Data_On_Condition2()
Dim dStart As Double: dStart = Timer
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Sheet1.Copy After:=Sheet1
With ActiveSheet
With .Range("A3:U" & .Cells(Rows.Count, 1).End(xlUp).Row)
.Rows(1).Offset(-1, 0).AutoFilter Field:=20, Criteria1:="Yes"
.Copy Destination:=Sheet2.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
.Rows(1).Offset(-1, 0).AutoFilter Field:=20
.Rows(1).Offset(-1, 0).AutoFilter Field:=21, Criteria1:="Yes"
.Copy Destination:=Sheet3.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
.AutoFilter
End With
.Delete
End With
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
MsgBox Format(Timer - dStart, "0.000")
End Sub
Edit: (following comment and file share)
Your worksheet is protected but without password. Therefore, you can actually do autfilter in place without having to add a new dummy sheet. Your autfilter becomes:
Sub Copy_Data_On_Condition2()
Dim dStart As Double: dStart = Timer
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Application.DisplayAlerts = False
' Check first if there's autfilter
If Sheet1.AutoFilterMode Then Sheet1.AutoFilter.ShowAllData
With Sheet2
If .AutoFilterMode Then .AutoFilter.ShowAllData
.Rows("4:" & .Rows.Count).ClearContents
End With
With Sheet3
If .AutoFilterMode Then .AutoFilter.ShowAllData
.Rows("4:" & .Rows.Count).ClearContents
End With
'=========== Super Symmetry Code _ Auto Filter
With Sheet1
.Unprotect
With .Range("A3:U" & .Cells(Rows.Count, 1).End(xlUp).Row)
.Rows(1).Offset(-1, 0).AutoFilter Field:=20, Criteria1:="Yes"
.Copy Destination:=Sheet2.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
.Rows(1).Offset(-1, 0).AutoFilter Field:=20
.Rows(1).Offset(-1, 0).AutoFilter Field:=21, Criteria1:="Yes"
.Copy Destination:=Sheet3.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
End With
.Protect
End With
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
MsgBox Format(Timer - dStart, "0.000")
End Sub
Autofilter is your best friend here if and when your data grows.
Copy Filtered Data
In this solution, it is assumed that you always want to start your resulting data in a given cell (dFirst) removing the previous contents.
Option Explicit
Sub CopyData()
Const sFirst As String = "A3"
Dim sCols As Variant: sCols = Array(20, 21)
Dim sCriteria As Variant: sCriteria = Array("Yes", "Yes")
Dim dFirst As Variant: dFirst = Array("A3", "A3")
Dim AutoFitColumns As Variant: AutoFitColumns = Array(True, True)
Dim sws As Worksheet: Set sws = Sheet1
Dim dws As Variant: dws = Array(Sheet2, Sheet3)
Dim srg As Range: Set srg = RefRange(sws.Range(sFirst))
If srg Is Nothing Then Exit Sub
Dim dData As Variant
Dim n As Long
For n = LBound(dws) To UBound(dws)
dData = GetCriteriaRows(srg, sCriteria(n), sCols(n))
If Not IsEmpty(dData) Then
WriteData dData, dws(n).Range(dFirst(n)), AutoFitColumns(n)
End If
Next n
End Sub
' Creates a reference to the range from a given first cell (range)
' to the cell at the intersection of the last non-empty row
' and the last non-empty column.
Function RefRange( _
ByVal FirstCellRange As Range) _
As Range
If FirstCellRange Is Nothing Then Exit Function
With FirstCellRange.Cells(1)
Dim rg As Range
Set rg = .Resize(.Worksheet.Rows.Count - .Row + 1, _
.Worksheet.Columns.Count - .Column + 1)
Dim lCell As Range
Set lCell = rg.Find("*", , xlFormulas, , xlByRows, xlPrevious)
If lCell Is Nothing Then Exit Function
Dim lRow As Long: lRow = lCell.Row
Set lCell = rg.Find("*", , , , xlByColumns, xlPrevious)
Set RefRange = .Resize(lRow - .Row + 1, lCell.Column - .Column + 1)
End With
End Function
' Returns a 2D one-based array containing the rows with matching criteria
' in a given column.
Function GetCriteriaRows( _
ByVal srg As Range, _
ByVal CriteriaString As String, _
Optional ByVal CriteriaColumn As Long = 1) _
As Variant
If srg Is Nothing Then Exit Function
If Len(CriteriaString) = 0 Then Exit Function
If CriteriaColumn < 0 Then Exit Function
Dim drCount As Long: drCount = Application.CountIf(srg, CriteriaString)
If drCount = 0 Then Exit Function
Dim srCount As Long: srCount = srg.Rows.Count
Dim cCount As Long: cCount = srg.Columns.Count
If CriteriaColumn > cCount Then Exit Function
Dim sData As Variant
If srCount + cCount = 2 Then
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = srg.Value
Else
sData = srg.Value
End If
Dim dData As Variant: ReDim dData(1 To drCount, 1 To cCount)
Dim cValue As Variant
Dim r As Long, c As Long, n As Long
For r = 1 To srCount
cValue = CStr(sData(r, CriteriaColumn))
If cValue = CriteriaString Then
n = n + 1
For c = 1 To cCount
dData(n, c) = sData(r, c)
Next c
End If
Next r
GetCriteriaRows = dData
End Function
' Writes the values from a 2D one-based array to a range.
Sub WriteData( _
ByVal Data As Variant, _
ByVal FirstCellRange As Range, _
Optional ByVal AutoFitColumns As Boolean = False)
If FirstCellRange Is Nothing Then Exit Sub
If IsEmpty(Data) Then Exit Sub
Dim srCount As Long: srCount = UBound(Data, 1)
Dim scCount As Long: scCount = UBound(Data, 2)
Dim DoesFit As Boolean
Dim DoesNotFitExactly As Boolean
With FirstCellRange.Cells(1)
If .Worksheet.Columns.Count - .Column + 1 >= scCount Then
Select Case .Worksheet.Rows.Count - .Row + 1
Case srCount
DoesFit = True
Case Is > srCount
DoesFit = True
DoesNotFitExactly = True
End Select
End If
If DoesFit Then
Dim drg As Range: Set drg = .Resize(srCount, scCount)
drg.Value = Data
If DoesNotFitExactly Then
drg.Resize(.Worksheet.Rows.Count - .Row - srCount + 1) _
.Offset(srCount).ClearContents
End If
If AutoFitColumns Then
drg.EntireColumn.AutoFit
End If
End If
End With
End Sub
' Returns a 2D one-based array containing the values of a range
' (Not used because it is incorporated in 'GetCriteriaRows').
Function GetRange( _
ByVal rg As Range) _
As Variant
If rg Is Nothing Then Exit Function
Dim Data As Variant
If rg.Rows.Count + rg.Columns.Count = 2 Then
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
Else
Data = rg.Value
End If
GetRange = Data
End Function
If you don't want to consider autofilter option.
Option Explicit
Sub Copy_Data_On_Condition()
'_____________________________________________________________
Dim StartTime As Double
Dim SecondsElapsed As Double
StartTime = Timer
'_____________________________________________________________
Dim arr, findT As Range, findU As Range, arrStr As String, i As Long, j As Long
Dim LastRow As Long, ColT As Range, ColU As Range, k As Long, n As Long
LastRow = Sheet1.Cells(Sheet1.Rows.Count, 1).End(xlUp).Row
k = 3000
For j = 2 To LastRow Step WorksheetFunction.Min(LastRow, k)
'_____________________________________________________________
'Evaluate Column T for "Yes" and create range findT
Set ColT = Sheet1.Range("T" & j + 1 & ":T" & WorksheetFunction.Min(j + k, LastRow))
arr = Evaluate("Transpose(IF((" & ColT.Address & "=" & """YES""" & ")," & _
"""A""" & "& ROW(" & ColT.Address & ") &" & _
""":U""" & "& ROW(" & ColT.Address & "),""0""))")
arrStr = Replace(Join(arr, ","), ",0", "")
If Left(arrStr, 2) = "0," Then
arrStr = Right(arrStr, Len(arrStr) - 2)
End If
For n = 15 To Len(arrStr) - Len(Replace(arrStr, ",", "", , , vbTextCompare)) Step 15
arrStr = WorksheetFunction.Substitute(arrStr, ",", "|", n)
Next n
arr = Split(arrStr, "|")
For n = 0 To UBound(arr)
If findT Is Nothing Then
'arr = Split(arrStr, "|")
Set findT = Evaluate(arr(n))
Else
Set findT = Union(Evaluate(arr(n)), findT)
End If
Next n
Debug.Print findT.Cells.Count
'_____________________________________________________________
'Evaluate Column U for "Yes" and create range findU
Set ColU = Sheet1.Range("U" & j + 1 & ":U" & WorksheetFunction.Min(j + k, LastRow))
arr = Evaluate("Transpose(IF((" & ColU.Address & "=" & """YES""" & ")," & _
"""A""" & "& ROW(" & ColU.Address & ") &" & _
""":U""" & "& ROW(" & ColU.Address & "),""0""))")
arrStr = Replace(Join(arr, ","), ",0", "")
If Left(arrStr, 2) = "0," Then
arrStr = Right(arrStr, Len(arrStr) - 2)
End If
For n = 15 To Len(arrStr) - Len(Replace(arrStr, ",", "", , , vbTextCompare)) Step 15
arrStr = WorksheetFunction.Substitute(arrStr, ",", "|")
Next n
arr = Split(arrStr, "|")
For n = 0 To UBound(arr)
If findU Is Nothing Then
'arr = Split(arrStr, "|")
Set findU = Evaluate(arr(n))
Else
Set findU = Union(Evaluate(arr(n)), findU)
End If
Next n
Debug.Print findU.Cells.Count
'_____________________________________________________________
Next j
findT.Copy Sheet2.Range("A" & Sheet2.Rows.Count).End(xlUp).Offset(1)
findU.Copy Sheet3.Range("A" & Sheet3.Rows.Count).End(xlUp).Offset(1)
'_____________________________________________________________
SecondsElapsed = Round(Timer - StartTime, 2)
Debug.Print "This code ran successfully in " & SecondsElapsed & " seconds"
End Sub
How can I autofill the entirety of column B based on column A but with n empty rows in between each letter?
Column A:
a
b
c
Column B:
a
...
...
b
...
...
c
I have tried the VBA code below:
Range("A1:A3").AutoFill Destination:=Range("A1:A10"), Type:=xlFillDefault
The code works with numbers but not when the cell references a formula (in this case, =A1, ...) as the code seems to reference the row the formula is, instead of the list in column A.
For example, the code inserts the formula a row after c in B7, however would insert =A7 instead of =A4 which would be the letter d.
Any help with this would be greatly appreciated.
To insert n row for each value in Column A, I will use offset to solve it, here is the solution and hope you find it useful:
Sub ty()
Dim count As Long, i As Long, nextrow As Long
count = Application.WorksheetFunction.CountA(Sheet1.Range("A:A"))
nextrow = 1
For i = 1 To count
Sheet1.Cells(nextrow, 2).Value = Sheet1.Cells(i, 1).Value
nextrow = Cells(nextrow, 2).Offset(3, 1).Row
Next
End Sub
Expected Output:
In order to preserve the formula into new cells, then you may need copy method` by change this part:
For i = 1 To count
Sheet1.Cells(i, 1).Copy Sheet1.Cells(nextrow, 2)
nextrow = nextrow + 3
Next
AutoFill Every n Rows
You run GetGappedColumnTEST. GetGappedColumn is being called by the GetGappedColumnTEST.
Adjust the values in the constants section and the workbook, and rename the Sub appropriately.
Option Explicit
Sub GetGappedColumnTEST()
Const sName As String = "Sheet1"
Const sFirst As String = "A1"
Const dName As String = "Sheet1"
Const dFirst As String = "B1"
Const dGap As Long = 2
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim Data As Variant
Data = GetGappedColumn(wb.Worksheets(sName).Range(sFirst), dGap)
If IsEmpty(Data) Then Exit Sub
Dim drCount As Long: drCount = UBound(Data, 1)
With wb.Worksheets(dName).Range(dFirst)
.Resize(drCount).Value = Data
.Resize(.Worksheet.Rows.Count - .Row - drCount + 1) _
.Offset(drCount).ClearContents
End With
End Sub
Function GetGappedColumn( _
ByVal FirstCell As Range, _
Optional ByVal Gap As Long = 0) _
As Variant
Const ProcName As String = "GetGappedColumn"
On Error GoTo clearError
If FirstCell Is Nothing Then Exit Function
If Gap < 0 Then Exit Function
Dim srg As Range
With FirstCell.Cells(1)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Function
Set srg = .Resize(lCell.Row - .Row + 1)
End With
Dim rCount As Long: rCount = srg.Rows.Count
Dim sData As Variant
If rCount = 1 Then
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = srg.Value
Else
sData = srg.Value
End If
Dim dData As Variant: ReDim dData(1 To rCount + rCount * Gap - Gap, 1 To 1)
Dim d As Long: d = 1
Dim s As Long
For s = 1 To rCount - 1
dData(d, 1) = sData(s, 1)
d = d + 1 + Gap
Next s
dData(d, 1) = sData(s, 1)
GetGappedColumn = dData
ProcExit:
Exit Function
clearError:
Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
Resume ProcExit
End Function
I have autofiltered a worksheet and am trying to establish the unique values within the filtered data. I feel like I have the correct approach, but the my results only show 2 of the possible 8 unique values.
Private Sub GetAllCampusDomains(DomainCol As Collection)
Dim data(), dict As Object, r As Long, i%, lastrow As Long
Set dict = CreateObject("Scripting.Dictionary")
'Clear the previous filter
shtData.ShowAllData
'Filter the data
shtData.Range("A:Y").AutoFilter Field:=6, Criteria1:=shtSetup.Range("CampusName") 'SchoolName
shtData.Range("A:Y").AutoFilter Field:=9, Criteria1:="DomainPerformance" 'ColI
'Inspect the visible cells in ColP
lastrow = shtData.Cells(shtData.Rows.Count, "P").End(xlUp).row
data = shtData.Range("P2:P" & lastrow).SpecialCells(xlCellTypeVisible)
'Find the unique values
For r = 1 To UBound(data)
dict(data(r, 1)) = Empty
Next
data = WorksheetFunction.Transpose(dict.keys())
'Walk through the unique values
For i = 1 To UBound(data)
Debug.Print data(i, 1)
'DomainCol.Add data(i, 1)
Next i
End Sub
The error seems to have to do with this line:
data = shtData.Range("P2:P" & lastrow).SpecialCells(xlCellTypeVisible)
This call only seems to create a 90x1 sized array, when it should be much bigger.
I greatly appreciate your help!
Josh
Non-Contiguous Column Range to Jagged Array
Instead of...
data = shtData.Range("P2:P" & lastrow).SpecialCells(xlCellTypeVisible)
'Find the unique values
For r = 1 To UBound(data)
dict(data(r, 1)) = Empty
Next
...use the following...
Private Sub GetAllCampusDomains(DomainCol As Collection)
'...
Dim rng As Range
Set rng = shtData.Range("P2:P" & lastrow).SpecialCells(xlCellTypeVisible)
getNonContiguousColumn Data, rng
'Find the unique values
Dim j As Long
For j = 0 To UBound(Data)
For r = 1 To UBound(Data(j))
dict(Data(j)(r, 1)) = Empty
Next r
Next j
'...
End Sub
...backed up by the following:
Sub getNonContiguousColumn(ByRef Data As Variant, _
NonContiguousColumnRange As Range, _
Optional FirstIndex As Long = 0)
Dim j As Long
j = FirstIndex - 1
ReDim Data(FirstIndex To NonContiguousColumnRange.Areas.Count + j)
Dim ar As Range
Dim OneCell As Variant
ReDim OneCell(1 To 1, 1 To 1)
For Each ar In NonContiguousColumnRange.Areas
j = j + 1
If ar.Cells.Count > 1 Then
Data(j) = ar.Value
Else
OneCell(1, 1) = ar.Value
Data(j) = OneCell
End If
Next ar
End Sub
Test the previous Sub with something like the following:
Sub testGetNCC()
Const rngAddr As String = "A2:A20"
Dim Data As Variant
Dim rng As Range
Set rng = Range(rngAddr).SpecialCells(xlCellTypeVisible)
getNonContiguousColumn Data, rng
Dim j As Long, i As Long
For j = 0 To UBound(Data)
For i = 1 To UBound(Data(j))
Debug.Print Data(j)(i, 1)
Next i
Next j
End Sub
Please, replace this piece of code:
data = shtData.Range("P2:P" & lastrow).SpecialCells(xlCellTypeVisible)
'Find the unique values
For r = 1 To UBound(data)
dict(data(r, 1)) = Empty
Next
with the next one:
Dim rng As Range, C As Range
Set rng = shtData.Range("P2:P" & lastRow).SpecialCells(xlCellTypeVisible)
'Find the unique values
For Each C In rng.cells
dict(C.Value) = Empty
Next
Your initial code iterates between the first area range cells.
The second one will iterate between all visible range cells...