I am realy new in Vba. I have a long data series in excel. but I want to select and cut some specific data range which are in column A and start with "MIN" till 33 rows after that. In each column there are 60 data series with different lengthen but all start with the row filled "MIN" till 33 rows after that. Between the range I want to select there are some other rows with data which aren't my goal.
I could copy all my ranges by hand but it takes time. I have 200 files that each files has 20000 rows and its really terrible to do by hand. All I could find is this code which i should at first find the row which has cells filled "MIN". Then I fill the range in this code by hand. but it takes time too. I want to write a code which :
1. find all cells filled "MIN"
2. Select the row which has "MIN"``
3.extend selection till 33 rows
4.copy all ranges it finds
thank you in advance.
This code works too but just extract the rows which has cell "min" and copy them in another sheet. but i want to extend the selection from the row it finds (has cell "min") till 33 rows after that.
Public Sub FindAndCopyit()
Dim Rng As Range
Dim rCell As Range
Dim copyRng As Range
Dim LCell As Range
Dim WB As Workbook
Dim srcSH As Worksheet
Dim destSH As Worksheet
Dim sFirst As String
Dim CalcMode As Long
Const strSearch As String = "MIN"
Set WB = ActiveWorkbook
Set srcSH = WB.Sheets("Find")
Set destSH = WB.Sheets("final")
Set Rng = srcSH.Range("A1:G20000")
With Application
CalcMode = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
Set rCell = Rng.Find(strSearch)
If Not rCell Is Nothing Then
Set copyRng = rCell.EntireRow
sFirst = rCell.Address
Do
Set rCell = Rng.FindNext(rCell)
If Not rCell Is Nothing And _
rCell.Address <> sFirst Then
Set copyRng = Union(rCell.EntireRow, copyRng)
End If
Loop Until rCell Is Nothing Or sFirst = rCell.Address
End If
Set LCell = destSH.Cells(Rows.Count, "A").End(xlUp)(2)
If Not copyRng Is Nothing Then
With copyRng
.Copy Destination:=LCell
.Delete
End With
End If
With Application
.CutCopyMode = False
.Calculation = CalcMode
.ScreenUpdating = True
End With
End Sub
would you please help me to fix it?
Try replacing:
Set copyRng = rCell.EntireRow
with
Set copyRng = Range(rCell, rCell(33, 1)).EntireRow
Related
Apologies if this has been answered before, I'm unable to find anything that matches my specific case.
I have a workbook with 18 sheets, and a variable number of columns per sheet starting at B2. Occasionally the program that generates the sheet will create duplicate columns, due to this, I need a macro triggered by button to search each sheet for matching column headers and then delete one of these columns (the whole column, not just the header).
So far I'm pretty stuck, I've been able to delete all matches from any cell in the sheet, which pretty much wipes the entire sheet out. I just need to match headers and then delete the entire column based on that.
Let me know if you need any more information, and thank you for the help!
What I have so far, the code is doing some other stuff too so this needs to continue working.
Sub RemoveExtras()
Dim MyRange As Range
Dim ws As Worksheet
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
BadCharacters = Array(Chr(10), Chr(13))
wsNumber = Sheets.Count
For Each ws In Worksheets
With ws
For Each MyRange In .UsedRange
If 0 < InStr(MyRange, Chr(10)) Then
For Each i In BadCharacters
MyRange = Replace(MyRange, i, vbNullString)
Next i
End If
For t = 1 To wsNumber
Columns(t).RemoveDuplicates Columns:=Array(1), Header:=xlYes
Next t
Next MyRange
End With
Next ws
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Dictionaries are perfect for handling unique values:
Sub RemoveExtras()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim c As Integer, i As Integer, ws As Worksheet
Dim dict As Object
For Each ws In Worksheets
Set dict = CreateObject("Scripting.Dictionary")
'Find Last column
c = ws.UsedRange.Columns.Count
'Loop backwards
For i = c To 2 Step -1
'If column does not exist in dictionary, then add it
If Not dict.Exists(ws.Cells(2, i).Value) Then
dict.Add ws.Cells(2, i).Value, 1
Else
'Otherwise delete column
ws.Columns(i).Delete Shift:=xlToLeft
End If
Next i
Set dict = Nothing
Next
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Here you do not compare every pair of column headers in the sheet. Also this compares headers across all the worksheets, not just duplicates inside one individual sheet.
See if this helps you
Sub test()
Dim book As Workbook, sheet As Worksheet, text As String
For Each sheet In Worksheets
Set MR = Range("B2:Z2") 'change Z2 as per your requirement
For Each cell In MR
Set BR = Range("B2:Z2") 'change Z2 as per your requirement
For Each cell2 In BR
If cell.Value = cell2.Value Then cell.EntireColumn.Delete
Next
Next
Next sheet
End Sub
I wrote a VBA script to compare fields in excel. Excel freezes the second I click the button. It never displays any error messages. Every time I try to run it I have to use control alt delete to close excel.
one of my variables is commented out because after I get this to work I plan on copying the data to a different sheet instead of changing the font. just an FYI
Private Sub CommandButton4_Click()
Dim rng1, rng2, cell1, cell2 As Range
Set rng1 = Worksheets("Main").Range("B:B")
Set rng2 = Worksheets("CSV Transfer").Range("D:D")
'Set rng3 = Worksheets("Data").Range("A:A")
For Each cell1 In rng1
For Each cell2 In rng2
If IsEmpty(cell2.Value) Then Exit For
If cell1.Value = cell2.Value Then
cell1.Font.Bold = True
cell1.Font.ColorIndex = 2
cell1.Interior.ColorIndex = 3
cell1.Interior.Pattern = xlSolid
cell2.Font.Bold = True
cell2.Font.ColorIndex = 2
cell2.Interior.ColorIndex = 3
cell2.Interior.Pattern = xlSolid
End If
Next cell2
Next cell1
End Sub
Edit: Entire post has been changed to reflect my actual issue.
Your macro isn't freezing, you just aren't giving it enough time to complete - which is a lonnnngggg time. Excel has a row limit of 1,048,576 rows, and you are comparing every single cell in each row to every single cell in the other row. That's a total of 1,099,511,627,776 cell comparisons. Assuming you can do 100,000 comparisons per second (which is probably a stretch even without the formatting), this will eventually complete in just over 127 days.
I'd suggest doing a couple of things. First, when you assign a range to a column like this...
Set rng1 = Worksheets("Main").Range("B:B")
...you get every possible cell - not just the used ones. Find the last non-empty cell in each column, and set your ranges based on that:
Dim LastRow As Long
Dim ColumnB As Range
With Worksheets("Main")
LastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
Set ColumnB = .Range("B1:B" + LastRow)
End With
This might get your run times onto the order of minutes or seconds instead of days unless you have a huge data set. To improve them further, stop requesting individual values from the worksheet one at a time and pull them into arrays:
Dim BValues As Variant
BValues = ColumnB.Value
Then, just loop through and compare values in memory. It might look something more like this (I pulled the formatting out into a Sub):
Private Sub CommandButton4_Click()
Dim LastRow As Long, MainSheet As Worksheet, CsvSheet As Worksheet
Set MainSheet = Worksheets("Main")
Set CsvSheet = Worksheets("CSV Transfer")
Dim MainValues As Variant
With MainSheet
LastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
MainValues = .Range("B1:B" & LastRow).Value
End With
Dim CsvValues As Variant
With CsvSheet
LastRow = .Cells(.Rows.Count, "D").End(xlUp).Row
CsvValues = .Range("D1:D" & LastRow).Value
End With
Dim MainRow As Long, CsvRow As Long
For MainRow = LBound(MainValues) To UBound(MainValues)
For CsvRow = LBound(CsvValues) To UBound(CsvValues)
If MainValues(MainRow) = CsvValues(CsvRow) Then
FormatCell MainSheet, MainRow, 2
FormatCell CsvValues, CsvRow, 4
End If
Next
Next
End Sub
Private Sub FormatCell(sheet As Worksheet, formatRow As Long, formatCol As Long)
With sheet.Cells(formatRow, formatCol)
With .Font
.Bold = True
.ColorIndex = 2
End With
With .Interior
.ColorIndex = 3
.Pattern = xlSolid
End With
End With
End Sub
I'd also turn off ScreenUpdates at very least if your performance is still too low.
Long-time user of this forum, first request for VBA help. Still consider myself a very beginner in VBA.
I need to make a daily batch file more meaningful by breaking up the rows in a single worksheet- "Main" (between 13,000 - 1,000,000 rows) into new worksheets. As this file gets processed daily, my requirement is that we can move rows based on the "Record Type" cell in column A.
The "Record Type" e.g. "25" or "41" or "ZA" could each have 3 populated columns, whilst Record Type "26" could have 30 populated... hence important to have entire row moved.
I am limited in my abilities and knowledge here, and have researched many examples on how to move rows (or a range of cells within a row) but these are limited to static options such as YES/NO, PAID/NOT PAID.
So in summary I need to:
1. Create a new worksheet for each distinct record in column A ("Record Type" in "Main")
2. Move entire row from "Main" to subsequently created worksheet in row 2.
Here is my attempt that somewhat creates the new worksheets (though I have to disable the error-handling and can't run as a script- have to step-through)
Sub breakout1()
Workbooks(1).Activate
Dim lastCol As Integer
Dim LastRow As Long
Dim x As Long
Dim rng As Range
Dim Rng1 As Range
Dim Rng2 As Range
Dim Rng3 As Range
Dim SheetNameArray
Dim fn As WorksheetFunction
Dim CalcSetting As Integer
Dim newsht As Worksheet
Set fn = Application.WorksheetFunction
With Application
CalcSetting = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
With ActiveSheet
Set rng = .UsedRange
Set Rng1 = Intersect(rng, .Range("A:A"))
lastCol = rng.Column + rng.Columns.Count - 1
.Range("A:A").AdvancedFilter Action:=xlFilterCopy, _
CopyToRange:=.Cells(1, lastCol + 2), Unique:=True
Set Rng2 = Intersect(.Columns(lastCol + 2).CurrentRegion, _
.Rows("2:" & Rows.Count))
ReDim SheetNameArray(1 To Rng2.Cells.Count)
SheetNameArray = fn.Transpose(Rng2)
.Columns(lastCol + 2).Clear
For x = LBound(SheetNameArray) To UBound(SheetNameArray)
On Error Resume Next
Set newsht = ThisWorkbook.Sheets(CStr(SheetNameArray(x)))
If Err <> 0 Then
Worksheets.Add
ActiveSheet.Name = CStr(SheetNameArray(x))
Err.Clear
End If
'On Error GoTo 0
'rng.AutoFilter Field:=1, Criteria1:=SheetNameArray(x)
'Set Rng3 = Intersect(rng, .Cells.SpecialCells(xlCellTypeVisible))
'Rng3.Copy Workbooks(1).Sheets(CStr(SheetNameArray(x))).Range("A1")
'rng.AutoFilter
Next x
End With
Range("A1").Select
Application.Calculation = CalcSetting
End Sub
I didn't focus on your true goal which I couldn't grasp out of your description
but here's a refactoring of your code that works for creating and/or populating sheets named after what found in unique values in "base" sheet (se code to set it properly) column "A
Option Explicit
Sub breakout2()
Dim x As Long
Dim rng As Range
Dim SheetNameArray As Variant
Dim CalcSetting As Integer
Dim newsht As Worksheet, BaseSht As Worksheet
With Application
CalcSetting = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
Set BaseSht = ThisWorkbook.Sheets("breakout") '<== choose "base" sheet
'Set BaseSht= Workbooks(1).ActiveSheet '<== this would activate the first workbook opend in current excel session. is it the one you actually want?
With BaseSht
Set rng = .UsedRange
SheetNameArray = GetSheetNames(rng, 1, 2)
For x = LBound(SheetNameArray) To UBound(SheetNameArray)
Set newsht = SetSheet(CStr(SheetNameArray(x)))
rng.AutoFilter Field:=1, Criteria1:=SheetNameArray(x)
Intersect(rng, .Cells.SpecialCells(xlCellTypeVisible)).Copy Parent.Sheets(CStr(SheetNameArray(x))).Range("A1")
rng.AutoFilter
Next x
End With
Range("A1").Select '<=== what for? Selection is rarely a good programming habit. set and use 'range' type variables instead
With Application
.Calculation = CalcSetting
.ScreenUpdating = True
End With
End Sub
Function SetSheet(shtName As String) As Worksheet
On Error Resume Next
ThisWorkbook.Sheets(shtName).Activate
If Err <> 0 Then
On Error GoTo 0
ThisWorkbook.Worksheets.Add
ActiveSheet.Name = shtName
End If
Set SetSheet = ActiveSheet
End Function
Function GetSheetNames(usedRng As Range, colWithSheetNames As Long, colShift As Long) As Variant
Dim sht As Worksheet
Dim rangeToScan As Range, rangeWithNames As Range, rngToCopyTo As Range
With usedRng
Set sht = .Parent
Set rngToCopyTo = sht.Columns(.Columns(.Columns.Count).column + 2)
End With
With sht
Set rangeToScan = Intersect(usedRng, .Columns(colWithSheetNames))
rangeToScan.AdvancedFilter Action:=xlFilterCopy, CopyToRange:=rngToCopyTo, Unique:=True
Set rangeWithNames = .Range(rngToCopyTo.Cells(1, 1).Offset(1), .Cells(.Rows.Count, rngToCopyTo.column).End(xlUp))
End With
GetSheetNames = Application.WorksheetFunction.Transpose(rangeWithNames)
rngToCopyTo.Clear
End Function
The object is to remove all the rows in sheet1 column A if they exist in the list in sheet2 column A.
Both columns only contain numbers.
Sheet one column A may contain duplicates which is fine if they are not on the list in sheet2.
One option that I'm not familiar with and might be missing out on is Autofilter.
The code executes on a small data range 100 to 1000 but I have many books with over 1,000,000 records to clean up and anything over 10,000 brings Excel to not responding and freezes up indefinitely.
Sub remDupesfromTwoWs()
With Application
.EnableEvents = False
CalcMode = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
' set range to be searched
Dim masterRecordRange As Range ' declare an unallocated array.
Set masterRecordRange = Range("Sheet1!A2:A316730") ' masterRecordRange is now an allocated array
' store sheet2 column A as searchfor array
Dim unwantedRecords() As Variant ' declare an unallocated array.
unwantedRecords = Range("Sheet2!A1:A282393") ' unwantedRecords is now an allocated array
' foreach masterRecord loop to search masterRecordRange for match in unwantedRecords
Dim i As Double
Dim delRange As Range
Set delRange = Range("A" & ActiveSheet.Rows.Count)
'go through all rows starting at last row
For i = masterRecordRange.Rows.Count To 1 Step -1
' loop through unwantedRecords check each offset
For Each findMe In unwantedRecords
'If StrComp(cell, findMe, 1) = 0 Then not as fast
' unwantedRecord found
If Cells(i, 1).Value = findMe Then
Set delRange = Union(delRange, Range("A" & i))
'MsgBox i
Exit For
End If
Next findMe
Next i
'remove them all in one shot
delRange.EntireRow.Delete
With Application
.EnableEvents = True
CalcMode = .Calculation
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
'possibly count and display quantity found
MsgBox "finally done!"
End Sub
It is very slow to walk through a range one cell at a time because there is a large overhead on each call to Cells. So you should get both ranges into variant arrays, then compare them to build up another array of matches which you would then write back to the worksheet and use Autofilter to select the rows to delete.
Here is a blog post on various methods of comparing lists:
VBA Comparing lists shootout
The fastest method is to use either a Dictionary or a collection. You should be able to adapt the code to do what you want.
Have you ever tried Range.Find:
Sub TestIt()
Dim ws1 As Worksheet, ws2 As Worksheet
Dim LastRow As Long, DestLast As Long, CurRow As Long
Set ws1 = Sheets("Sheet1")
Set ws2 = Sheets("Sheet2")
LastRow = ws1.Range("A" & Rows.Count).End(xlUp).Row
DestLast = ws2.Range("A" & Rows.Count).End(xlUp).Row
For CurRow = LastRow to 2 Step -1 'Must go backwards because you are deleting rows
If Not ws2.Range("A2:A" & DestLast).Find(ws1.Range("A" & CurRow).Value, LookIn:=xlValues, LookAt:=xlWhole) is Nothing Then
Range("A" & CurRow).EntireRow.Delete xlShiftUp
End If
Next CurRow
End Sub
Created a macro below thanks to help from another that works.
Basically, it takes the value of the cell in column A and, if a sheet doesn't exist with that cells name, creates it. Then it pastes all rows of data that have the corresponding cell value to that sheet. Ie. if a cell contains the following:
column a column b
dc00025 data value
If dc00025 doesn't exist, it'll make the sheet. And paste all rows with dc00025 in A.
This works perfectly. However, I noticed when you run this macro after a sheet has already been created, for some reason it adds thousands of columns dramatically slowing down excel.
To fix this, would it be possible to modify the script to only copy columns b:o rather tahnt he entire row? Pasting them starting at A3 would be preferable but I'm not sure how to fix that.
Thanks in advance.
Sub CopyCodes()
Application.ScreenUpdating = False
Dim rCell As Range
Dim lastrow As Long
lastrow = Sheets("Data").UsedRange.Rows.Count
For Each rCell In Worksheets("Data").Range("A2:A" & lastrow).SpecialCells(xlCellTypeConstants)
If Not SheetExists(rCell.Value) Then
With Worksheets.Add(, Worksheets(Worksheets.Count))
.Name = rCell.Value
End With
End If
Worksheets("Data").Rows(1).EntireRow.Copy Worksheets(rCell.Value).Rows(1)
Worksheets(rCell.Value).Range("A" & Rows.Count).End(xlUp)(2).EntireRow.Value = _
rCell.EntireRow.Value
Next rCell
Application.ScreenUpdating = True
End Sub
Function SheetExists(wsName As String)
On Error Resume Next
SheetExists = Worksheets(wsName).Name = wsName
End Function
Suggested fix:
Sub CopyCodes()
Application.ScreenUpdating = False
Dim rCell As Range
Dim lastrow As Long
Dim shtData as worksheet, shtDest as worksheet
Dim sheetName as string
set shtData=worksheets("Data")
lastrow = shtData.cells(rows.count,1).end(xlup).row
For Each rCell In shtData.Range("A2:A" & lastrow).SpecialCells(xlCellTypeConstants)
sheetName = rCell.Value
If Not SheetExists(sheetName) Then
set shtDest = Worksheets.Add(, Worksheets(Worksheets.Count))
shtDest.Name = sheetName
shtData.Rows(1).EntireRow.Copy shtDest.Rows(1)
Else
set shtDest = Worksheets(sheetName)
End If
shtDest.Range("A" & Rows.Count).End(xlUp).offset(1,0).EntireRow.Value = _
rCell.EntireRow.Value
Next rCell
Application.ScreenUpdating = True
End Sub