Excel crashes when comparing two columns VBA macro - vba

I have two columns which I am comparing for identical entries, and pushing the matches to another column through Offset. When I run the macro I've built (off of some Microsoft canned code) it essentially freezes and crashes, since it is a nested for each loop based on cells that are used, I figured it would end upon reaching an empty cell, but I fear I may be in a infinite loop. Any help will be much appreciated.
Dim myRng As Range
Dim lastCell As Long
Dim lastRow As Long
lastRow = ActiveSheet.UsedRange.Rows.Count
Dim c As Range
Dim d As Range
For Each c In Worksheets("Sheet1").Range("AT2:AT" & lastRow).Cells
For Each d In Worksheets("Sheet1").Range("AU2:AU" & lastRow).Cells
If c = d Then c.Offset(0, 1) = c
Next d
Next c

Try this:
Dim lastRow, currentRow, compareRow As Long
Dim found As Boolean
lastRow = Range("AT2").End(xlDown).Row
For currentRow = 2 To lastRow
compareRow = 2
found = False
Do While compareRow <= lastRow And Not found
If Range("AT" & currentRow).Value = Range("AU" & compareRow).Value Then
found = True
Range("AV" & currentRow).Value = Range("AT" & currentRow).Value
End If
compareRow = compareRow + 1
DoEvents
Loop
Next currentRow
Rather than selecting ranges and then cycling through them, this does the same thing without needing to .Select anything. It also breaks out of the inner loop early if it finds a match.

I believe that there are multiple issues here:
Efficiency of the search method
Loss of responsiveness of Excel
You can dramatically improve the efficiency of the code if you can pull all values into arrays. This prevents the time spent by VBA in accessing the Excel Object model and back. Loss of responsiveness can be handled by using DoEvents. Try the code below. It may look longish but should be easy to understand.
'Find last row
Dim lastRow As Variant
lastRow = ActiveSheet.UsedRange.Rows(ActiveSheet.UsedRange.Rows.Count).Row
'Create dynamic arrays
Dim AT() As Variant: Dim AU() As Variant: Dim AV() As Variant
ReDim AT(2 To lastRow): ReDim AU(2 To lastRow): ReDim AV(2 To lastRow)
'Get all contents from Excel
For i = 2 To lastRow
AT(i) = Worksheets("Sheet1").Cells(i, 46)
AU(i) = Worksheets("Sheet1").Cells(i, 47)
Next i
'Do the comparison
For c = 2 To lastRow
For d = 2 To lastRow
If AT(c) = AU(d) Then AV(c) = AT(c)
Next d
'Allow a brief breather to Excel once in a while (don't hang)
If (c / 100) = Int(c / 100) Then DoEvents
Next c
'Place final contents to Excel
For i = 2 To lastRow
Worksheets("Sheet1").Cells(i, 48) = AV(i)
Next i

Try this for your loop:
Dim StartRange As Range, j As Long
Dim CompareRange As Range, i As Range
With Worksheets("Sheet1")
Set StartRange = .Range("AT1", .Range("AT:AT").Find("*", , , , xlByRows, xlPrevious))
Set CompareRange = .Range("AU1", .Range("AU:AU").Find("*", , , , xlByRows, xlPrevious))
For Each i In StartRange
i.Offset(, -8).Value = .Evaluate("IF(COUNTIF(" & CompareRange.Address(0, 0) & "," & i.Address(0, 0) & ")>0," & i.Value & ","""")")
Next i
End With

Dim CompareRange As Variant, To_Be_Compared As Variant, j As Variant, k As Variant
Range("AT2").Select
Selection.End(xlDown).Select
Set To_Be_Compared = Range("AT2:" & Selection.Address)
Range("AU2").Select
Selection.End(xlDown).Select
Set CompareRange = Range("AU2:" & Selection.Address)
To_Be_Compared.Select
For Each j In Selection
DoEvents
For Each k In CompareRange
If j = k Then j.Offset(0, 2) = j
Next k
Next j

I finally got it to work, after taking the suggestions and implementing them into my code, I was able to see where the mistake actually was, I was referencing the wrong column earlier in the code and through this, created no duplicate entries to match, so after fixing this, the matches now appear, I ended up offsetting them, and changing the value to "yes" to reflect the duplication in my chart.
Thank you all for the help.

Related

Excel filter a column by the first letters for more than 2 values

I am very new at vba, and now fighting with one macro which will filter a Column by the first exact letters (for instance, I have a Column N - “City” and as a result I have to have all entries , starts for instance- “Vancouver”, “Vancouver. BC”, “Vancouver Canada” – so I want to sort this column by the first letters – VANCOU - to be sure, that I will not miss any info.
The code below does not work at all for 3 values – probably I choose a wrong way ., can you please advise – which function or operator will work at this case? All I find - work for 2 values (at that case I can use at list "begins with"). I have 5-6 values, and they might vary (I don't know which format of City name I will have next time) .
Thanks in advance!
Dim rng01 As Range
Set rng01 = [A1:Z5048]
rng01.Parent.AutoFilterMode = False
rng01.Columns(14).AutoFilter Field:=1, Criteria1:=Array("Vancou*", "Brampt*", "Halifa*"), Operator:= _
xlFilterValues
Upd:
Here is an adapted code , which is not working
Option Explicit
Sub AutoFilterWorkaround()
Dim sht As Worksheet
Dim filterarr As Variant, tofindarr As Variant
Dim lastrow As Long, i As Long, j As Long, k As Long
Set sht = ThisWorkbook.Worksheets("Sheet1")
lastrow = sht.Cells(sht.Rows.Count, "N").End(xlUp).Row
'List the parts of the words you need to find here
tofindarr = Array("Vancou", "Brampt", "Halifa")
ReDim filterarr(0 To 0)
j = 0
For k = 0 To UBound(tofindarr)
For i = 2 To lastrow
If InStr(sht.Cells(i, 14).Value, tofindarr(k)) > 0 Then
filterarr(j) = sht.Cells(i, 14).Value
j = j + 1
ReDim Preserve filterarr(0 To j)
End If
Next i
Next k
'Filter on array
sht.Range("$N$1:$N$" & lastrow).AutoFilter Field:=14, Criteria1:=Array(filterarr), Operator:=xlFilterValues
End Sub
Okay, so I rewrote the workaround - basically we avoid using wildcards by just finding each individual match case, loading that into an array, then filter on the entire array at the end.
This example works for column A - just change the A in lastrow to N, as well as changing the As to Ns in the last line. Also specify your sheet name on the Set sht line. Also Field:=1 needs to be changed to Field:=14 for column N in your case.
Option Explicit
Sub AutoFilterWorkaround()
Dim sht As Worksheet
Dim filterarr As Variant, tofindarr As Variant
Dim lastrow As Long, i As Long, j As Long, k As Long
Set sht = ThisWorkbook.Worksheets("Sheet1")
lastrow = sht.Cells(sht.Rows.Count, "A").End(xlUp).Row
'List the parts of the words you need to find here
tofindarr = Array("Vancou", "Brampt", "Halifa")
ReDim filterarr(0 To 0)
j = 0
For k = 0 To UBound(tofindarr)
For i = 2 To lastrow
If InStr(sht.Cells(i, 1).Value, tofindarr(k)) > 0 Then
filterarr(j) = sht.Cells(i, 1).Value
j = j + 1
ReDim Preserve filterarr(0 To j)
End If
Next i
Next k
'Filter on array
sht.Range("$A$1:$A$" & lastrow).AutoFilter Field:=1, Criteria1:=Array(filterarr), Operator:=xlFilterValues
End Sub

Why Does Range.Value = Left(....) not work?

I am very new to VBA, so this probably has a very simple answer!
I am trying to use the Left function to produce a string of the first digit of the cells in a column, but for reasons which I don't understand, when I use Range.Value = Left(...) it does not put the values that I have got from Left(...) into the cell.
I am left with a blank column A when the code has finished running.
Can anyone explain why this is happening and suggest how to fix it.
The code I wrote is:
Dim r As Integer
Dim var As Variant
r = Range("B1").CurrentRegion.Rows.Count
For var = 2 To r
Range("A" & var).Value = Left(Cells(var, 1).Text, 1)
Next var
Thanks so much.
Most probably you are not declaring something completely - either the worksheet, or the rows in the current region. Start with something as simple as this one to see how it works:
Public Sub HereComesTheCode()
Dim r As Long: r = 10
Dim i As Long
Dim wks As Worksheet: Set wks = Worksheets(1)
For i = 2 To r
With wks
.Range("A" & i) = Left(.Cells(i, 1), 1)
End With
Next
End Sub
This would be an alternative method, scanning the cells in B and writing the first letter of each cell into the cell to the left of it:
Dim c As Range
For Each c In Range("B1", "B" & Cells(Rows.Count, "B").End(xlUp).Row).Cells
c.Offset(0, -1).Value = Left(c.Text, 1)
Next
Note: Strictly speaking, it would be good practice to qualify your ranges though:
Dim c As Range
With Worksheets("YOUR_SHEET_NAME_HERE")
For Each c In .Range("B1", "B" & .Cells(.Rows.Count, "B").End(xlUp).Row).Cells
c.Offset(0, -1).Value = Left(c.Text, 1)
Next
End With

VBA Looping through single row selections and executing concat code

So, I've been scratching my head for a couple of hours now trying to figure this out. No matter where I look and what I do, I can't seem to make it work.
I have an excel document with ~20 columns and a completely variable number of rows. I want to concatenate each adjacent cell within the defined width (columns A:V)into the first cell (A1 for the first row), and then move to the next row and do the same until I get to the bottom. Snippet below:
Example before and after I'm trying to make
I have the code that does the concatenation. To make it work I have to select the cells I want to concatenate (A1:V1), and then execute the code. Even though some cells are blank, I need the code to treat them this way and leave semicolons there. The code works exactly as I need it to, so I've been trying to wrap it in some sort of Range select, offset, loop:
Dim c As Range
Dim txt As String
For Each c In Selection
txt = txt & c.Value & ";"
Next c
Selection.ClearContents
txt = Left(txt, Len(txt) - 2)
Selection(1).Value = txt
What I am struggling with is making the selection A1:V1, running the code, and then looping this down to A2:V1, A3:V3, etc. I think this can be done with a loops and an offset, but I cannot for the life of me work out how.
Any help at all would be much appreciated :)
This uses variant Arrays and will be very quick
Dim rng As Range
With Worksheets("Sheet4") 'change to your sheet
'set the range to the extents of the data
Set rng = .Range(.Cells(1, 1), .Cells(.Rows.Count, 22).End(xlUp))
'Load data into an array
Dim rngArr As Variant
rngArr = rng.Value
'create Out Bound array
Dim OArr() As Variant
ReDim OArr(1 To UBound(rngArr, 1), 1 To 1)
'Loop array
Dim i As Long
For i = LBound(rngArr, 1) To UBound(rngArr, 1)
'Combine Each Line in the array and load result into out bound array
OArr(i, 1) = Join(Application.Index(rngArr, i, 0), ";")
Next i
'clear and load results
rng.Clear
rng.Cells(1, 1).Resize(UBound(OArr, 1)).Value = OArr
End With
Here's a quick little script I made up to do this - the main thing to note is that I don't use selection, I used a defined range instead.
Sub test()
Dim i As Long
Dim target As Range
Dim c As Range
Dim txt As String
For i = 3 To 8
Set target = Range("A" & i & ":C" & i)
For Each c In target
txt = txt & c.Value & ";"
Next c
Cells(i + 8, "A").Value2 = Left$(txt, Len(txt) - 1)
txt = ""
Next i
End Sub
Just change the range on the below to your requirements:
Sub concat_build()
Dim buildline As String
Dim rw As Range, c As Range
With ActiveSheet
For Each rw In .Range("A2:V" & .Cells(.Rows.Count, "B").End(xlUp).Row + 1).Rows
buildline = ""
For Each c In rw.Cells
If buildline <> "" Then buildline = buildline & ";"
buildline = buildline & c.Value2
Next
rw.EntireRow.ClearContents
rw.EntireRow.Cells(1, 1) = buildline
Next
End With
End Sub

How to combine multiple macros and excel functions into a single macro that executes on button click?

I need to combine multiple macros to a single macro that executes on button click. Kindly excuse me if I write anything wrong since I am completely new to excel macros and vb.
Following is the scenario.
Steps:
Calculate total
Extract reference
Compare total field value for matching reference and mark that as "Complete" if sum of total for matching references calculates to ).
(Explained...)
First i calculate the debit and credit amount to a new column called total, for this, initially I used the SUM function. after that I tried the same using the macro that executes on button click
(old macro)
Private Sub getTotal_Click()
With ActiveSheet
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
For i = 5 To lastRow
Range("K" & i).Value = Range("F" & i).Value + Range("G" & i).Value
Next i
End Sub
This was so much time consuming (took around 2 hrs when executed on 75k records) than when using the formula (which finished in minutes). I am still not able to understand the reason for this. However modifiying to Dy.Lee's answer below, it took only seconds to calculate the total.
(modified based on Dy.Lee's answer)
Private Sub getTotal_Click()
Dim vDB As Variant, vR() As Variant
Dim i As Long, n As Long, lastRow As Long
With ActiveSheet
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
vDB = .Range("R5", "S" & lastRow)
n = UBound(vDB, 1)
ReDim vR(1 To n, 1 To 1)
For i = 1 To n
vR(i, 1) = vDB(i, 1) + vDB(i, 2)
Next i
.Range("AL5").Resize(n) = vR
End With
End Sub
Now moving on to the second macro which I used to extract a pattern from strings in a column D and E.
Function extractReference(cid_No As String, pm_source As String)
Dim regExp As Object, findMatches As Object, match As Object
Dim init_result As String: init_result = ""
Set regExp = CreateObject("vbscript.regexp")
With regExp
.Global = True
.MultiLine = False
.Pattern = "(?:^|\D)(\d{5,6})(?!\d)"
End With
Set findMatches = regExp.Execute(pm_source)
For Each match In findMatches
init_result = init_result + match.SubMatches.Item(0)
Next
If init_result <> "" Then
extractReference = cid_No & " | " & init_result
Else
extractReference = ""
End If
End Function
This macro was working fine.
Finally I used the following function after copying both the extracted reference and total to a new sheet and creating a datatable for that
=IF(ISBLANK([#Reference]), "", (IF((ROUND(SUMIFS([Total],[Reference],[#Reference]),2)=0), "complete", "")))
This also worked fine.
Now what I actually want is I need to avoid creating any new data tables or sheets and preform all this within current sheet on a single button click. Is there anyway that can be done without making the macro a time consuming process? Your help is higly appreciated!
Thanks in Advance
for the first part try:
Private Sub getTotal_Click()
Dim lastRow As Long
Dim sumRange As Range
With ActiveSheet
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
Set sumRange = Range(Range("K5"), Range("K" & lastRow))
sumRange.FormulaR1C1 = "=RC[-5]+RC[-4]"
sumRange.Copy
sumRange.PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
End Sub
also, if you still want to loop notice that calling cell like .Cells(1, 1) is faster than Range("A1")
You need using Variant Array. It is faster.
Private Sub getTotal_Click()
Dim vDB As Variant, vR() As Variant
Dim i As Long, n As Long, lastRow As Long
With ActiveSheet
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
vDB = .Range("f5", "g" & lastRow)
n = UBound(vDB, 1)
ReDim vR(1 To n, 1 To 1)
For i = 1 To n
vR(i, 1) = vDB(i, 1) + vDB(i, 2)
Next i
.Range("k5").Resize(n) = vR
End With
End Sub

How to copy a range of cells to another column in VBA?

Working Environment: Excel 2013
Target: Copy C1:C9 to B11:B19. D1:D9 to B21:B29. E1:E9 to B31:B39.....
After copying all the range to column B, copy A1:A9 to A11:A19(A21:A29....)
My idea is that:
1. select a range by using something like
range.end()
because in some of my sheets, there are only 4 test steps. so I need a syntax which can self inspect the used cells in a column.
do a range copy to column B.
leave 1 row in between considering about the page layout.
My piece of code is:
Worksheets("Master").Columns(3).UsedRange.Copy
Worksheets("Master").Range("B11").PasteSpecial
but seems like the Columns(i).UsedRange.Copy doesn't work. the pastespecial works.
My question is:
How to select the used range in columns? The number of columns are not fixed which means some of the sheets have 40 columns, but some of the other have maybe 30.
Thanks!
I attached one screenshot of the sheet for your reference.
Assuming you do not have more data in the columns to be copied, this should work
Sub copyToOneColumn()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Master")
Dim startCol As Integer
startCol = 3
Dim endCol As Integer
endCol = 10
Dim startRange As Range
Dim ra As Range
For i = startCol To endCol
Set startRange = ws.Range("A1").Offset(0, i - 1)
Set ra = ws.Range(startRange, ws.Cells(Rows.Count, startRange.Column).End(xlUp))
ra.Copy Destination:=ws.Range("B" & Rows.Count).End(xlUp).Offset(2, 0)
Next i
End Sub
You can do a copy (not technically a copy as it doesn't use the clipboard) directly like so:
Range("B1").Resize(Range("A1:A" & range("A" & Rows.Count).End(xlUp).Row).Rows.Count,1) = Range("A1:A" & range("A" & Rows.Count).End(xlUp).Row).Value
Effectively you are looking at B1 then resizing that to a range to be the number of columns in column A that are used with this: Range("A1:A" & range("A" & Rows.Count).End(xlUp).Row).Rows.Count
Then you are making this new range in column B = to the values of the same range in column A.
Note, this can be shortened if you are always starting at row 1 but the code I have given you will suffice if you start at a different row.
You may try something like this...
Sub CopyData()
Dim wsMaster As Worksheet
Dim lr As Long, lc As Long, r As Long, c As Long
Application.ScreenUpdating = False
Set wsMaster = Sheets("Master")
lr = wsMaster.Cells(Rows.Count, 1).End(xlUp).Row
lc = wsMaster.Cells(1, Columns.Count).End(xlToLeft).Column
r = lr + 2
If lr <= 9 Then
For c = 3 To lc
wsMaster.Range(wsMaster.Cells(1, c), wsMaster.Cells(lr, c)).Copy wsMaster.Range("B" & r)
wsMaster.Range("A1:A" & lr).Copy wsMaster.Range("A" & r)
r = wsMaster.Cells(Rows.Count, 2).End(xlUp).Row + 2
Next c
End If
Application.ScreenUpdating = True
End Sub