The following code checks to see if any of my rows on the main page contain a date from a year ago or older. If it does, it copies it to the "Archive" worksheet and deletes it from the main page. However, what it's doing now is just copying from the main page and overriding what already exists on the archive page instead of adding to the last row. I've tried subbing in LastRow from a function, but I was getting an error with how I was using it. Anyone have a better solution?
Sub TestDateTransfer()
With Application
PrevCalc = .Calculation
.Calculation = xlCalculationManual
.Cursor = xlWait
.Calculate
.EnableEvents = False
.ScreenUpdating = False
End With
Application.DisplayAlerts = False
Worksheets("Archive").Activate
Range("A3:I1000").Select
Selection.ClearContents
Worksheets("Main Page").Activate
Dim MyDate As Date
MyDate = "03/27/2017"
Set i = Sheets("Main Page")
Set E = Sheets("Archive")
Dim d
Dim j
d = 2
j = 2
Do Until IsEmpty(i.Range("C" & j))
If i.Range("C" & j) <= MyDate - 365 Then
d = d + 1
E.rows(d).Value = i.rows(j).Value
End If
j = j + 1
Loop
Worksheets("Archive").Activate
ActiveSheet.Range("H1").Select 'To unselect the page
Worksheets("Main Page").Activate
MyDate = "03/27/2017"
Dim y
Dim z
y = 2
z = 2
Do Until IsEmpty(i.Range("C" & z))
If i.Range("C" & z) <= MyDate - 365 Then
y = y + 1
i.rows(z).Delete
End If
z = z + 1
Loop
With Application
.Cursor = xlDefault
.Calculate
.Calculation = PrevCalc
'.ScreenUpdating = True 'Not Needed...
.EnableEvents = True
End With
ActiveSheet.Range("H1").Select
End Sub
Worksheets("Archive").Cells(Sheet1.Rows.Count, 1).End(xlUp).Offset(1, 0).Value = "hai"
I have written a small piece of code that shows you how to use this. My code is different than yours b/c it loops through the range and checks each cell if the value of the difference between it and NOW is greater than or equal to 1 (which is how to tell if its from lats year or not). Its not how you approach it but it does seem to be more simplified in the approach. Alsoe I soted a bucnh of dates in a spreadsheet and tested this out. just apply to your needs. I hope this helps more?
Private Sub this()
Dim rng As Range
Dim rcell As Range
Set rng = ThisWorkbook.Sheets("Sheet1").Range("A1:A20")
For Each rcell In rng.Cells
'note that if you dont put a handler in here to deal with blank cells values this code will run forever. most peop-le do a check with "if rcell.valeu <> vbNullString then etc etc
If DateDiff("yyyy", rcell.Value, Now()) >= 1 Then
Worksheets("Sheet2").Cells(Sheet2.Rows.Count, 1).End(xlUp).Offset(1, 0).Value = rcell.Value
rcell.Value = vbNullString
End If
Next rcell
End Sub
You could exploit AutoFilter() method of Range object and copy/paste filtered rows in one shot:
Option Explicit
Sub main()
Dim MyDate As Date
MyDate = "03/27/2017"
Dim E As Worksheet
Set E = Worksheets("Archive")
With Worksheets("Main Page")
With .Range("C1", .Cells(.Rows.Count, "C").End(xlUp))
.AutoFilter field:=1, Criteria1:="<=" & CDbl((MyDate - 365))
If Application.WorksheetFunction.Subtotal(103, .Cells) > 1 Then Intersect(.Parent.UsedRange, .Resize(.Rows.Count - 1).Offset(1).SpecialCells(xlCellTypeVisible).EntireRow).Copy Destination:=E.Cells(Rows.Count, 1).End(xlUp).Offset(1)
End With
.AutoFilterMode = False
End With
End Sub
Related
I need to speed up this macro & to avoid specifying a range as (A2:A2000) for example because my data is dynamic.
My macro checks every cell with the same value in some columns to merge it
Sub Merge_Duplicated_Cells()
'
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Dim ws As Worksheet
Dim Cell As Range
' Merge Duplicated Cells
Application.DisplayAlerts = False
Sheets("1").Select
Set myrange = Range("A2:A2000, B2:B2000, L2:L2000, M2:M2000, N2:N2000, O2:O2000")
CheckAgain:
For Each Cell In myrange
If Cell.Value = Cell.Offset(1, 0).Value And Not IsEmpty(Cell) Then
Range(Cell, Cell.Offset(1, 0)).Merge
Cell.VerticalAlignment = xlCenter
GoTo CheckAgain
End If
Next
Sheets("2").Select
Set myrange = Range("A2:A2000, B2:B2000, L2:L2000, M2:M2000, N2:N2000, O2:O2000")
For Each Cell In myrange
If Cell.Value = Cell.Offset(1, 0).Value And Not IsEmpty(Cell) Then
Range(Cell, Cell.Offset(1, 0)).Merge
Cell.VerticalAlignment = xlCenter
GoTo CheckAgain
End If
Next
ActiveWorkbook.Save
MsgBox "Report is ready"
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
For a quick fix add
Application.Calculation = xlManual
after your code
Application.DisplayAlerts = False
Application.ScreenUpdating = False
and
Application.Calculation = xlAutomatic
after your code
Application.DisplayAlerts = True
Application.ScreenUpdating = True
and to improve the macro not processing blank ranges,
dim ws as worksheet
dim lastrowA, lastrowB, lastrow C as long
'Instead of setting last row to 2000, can use the actual last row by eg:
'find last row of data in column A'
lastrowA = ws.Cells(Rows.Count, 1).End(xlUp).Row
'find last row of data in column B'
lastrowB = ws.Cells(Rows.Count, 2).End(xlUp).Row
'find last row of data in column C'
lastrowC = ws.Cells(Rows.Count, 3).End(xlUp).Row
and insert these into the macro instead of 2000 eg:
Set myrange = Range("A2:A" & lastrowA & ,
The slowdown in your code is primarily due to the presence of the GoTo CheckAgain transition, due to which the cycle of processing the same cells is repeated many times. In addition, multiple calls to the cells of the sheet are used, which is very time consuming. In the code below, unnecessary cycles are excluded, reading data from the sheet, merging and formatting cells are performed immediately for the entire processed subrange.
I ran the code on 2 sheets with 10000 rows each, it took 2.6 sec.
Option Explicit
Sub test1()
'Here we indicate only the starting cells in each column, because
'the size of the non-empty area in these columns is calculated
'automatically in the MergeCells() procedure
MergeCells Sheets("1").Range("A2,B2,L2,M2,N2,O2")
MergeCells Sheets("2").Range("A2,B2,L2,M2,N2,O2")
End Sub
Sub MergeCells(myrange As Range)
Dim v As Variant, col As Range, Cell As Range, toMerge(0 To 1) As Range, k As Long, index As Byte, area As Variant, arr As Variant, skip As Boolean
With Application
.DisplayAlerts = False
.ScreenUpdating = False
.Calculation = xlCalculationManual
For Each col In myrange
' next line reads all the data from sheet's column at once
arr = col.Resize(myrange.Parent.Cells(Rows.Count, col.Column).End(xlUp).Row - col.Row + 1)
For k = LBound(arr, 1) To UBound(arr, 1) - 1 'loop through all rows of an array
If Not skip And arr(k, 1) = arr(k + 1, 1) And Not IsEmpty(arr(k, 1)) Then
'to prevent "gluing" adjacent sub-ranges within the same range,
'two ranges are used in the toMerge array, all odd sub-ranges are collected
'in the element with index 0, all even ranges are collected in the element
'with index 1, and Index switches from 0 to 1 and vice versa after each array subrange
If toMerge(index) Is Nothing Then
Set toMerge(index) = col.Offset(k - col.Row + 1).Resize(2)
Else
Set toMerge(index) = Union(col.Offset(k - col.Row + 1).Resize(2), toMerge(index))
End If
index = 1 - index
skip = True ' if merged, skip next cell
Else
skip = False
End If
Next
' if the ranges for merge are non-empty, we merge and format simultaneously for all subranges
For Each area In toMerge
If Not area Is Nothing Then
area.Merge
area.VerticalAlignment = XlVAlign.xlVAlignCenter
End If
Next
Set toMerge(0) = Nothing
Set toMerge(1) = Nothing
Next
.DisplayAlerts = True
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
End Sub
If I understand you correctly .... besides the already existing answer, another way (which is not meant to be better) maybe something like this :
Before and after running the sub (please ignore the yellow fill and the border, as it is used just to be easier to see the result) like image below :
===>
Sub test()
Dim LR As Integer: Dim cnt As Integer
Dim i As Integer: Dim c As Range
Application.DisplayAlerts = False
With ActiveSheet.UsedRange
LR = .Rows(.Rows.Count).Row
cnt = .Columns.Count
End With
For i = 1 To cnt
Set c = Cells(1, i)
Do
If c.Value <> "" And c.Value = c.Offset(1, 0).Value _
Then Range(c, c.Offset(1, 0)).Merge _
Else Set c = c.Offset(1, 0)
Loop Until c.Row > LR
Next
End Sub
LR is the last row of the used range of the active sheet.
cnt is the column count of the used range of the active sheet.
Then it loop from 1 to as many as the cnt as i variable.
Inside this loop, it create the starting cell as c variable, then do the inner loop, by checking each c within the looped column (the i in cnt) if the row below c has the same value then it merge this c and the c.offset(1,0). The inner loop stop when the c.row is larger than the LR, then it goes to the next i (the next column).
Please note, the data should start from column A ... because the outer loop assume that the column to be in the inner loop will start from column 1 (column A). And also, the code doesn't do any fancy things, such as alignment, font size, border, etc.
I would like to merge similar cells by columns, as of now I am using this macro
Sub MergeSimilarCells()
Set myRange = Range("A1:Z300")
CheckAgain:
For Each cell In myRange
If cell.Value = cell.Offset(0, 1).Value And Not IsEmpty(cell) Then
Range(cell, cell.Offset(0, 1)).Merge
cell.VerticalAlignment = xlCenter
cell.HorizontalAlignment = xlCenter
GoTo CheckAgain
End If
Next
End Sub
My problem is with hundreds of rows and 40-50 columns, it takes forever.
I am pretty sure a For Loop could help me there but I am not skilled enough to figure it out
I know the following code is wrong but I am lost
Sub SimilarCells()
Set myRange = Range("A1:G4")
Dim count As Integer
CheckAgain:
count = 1
For Each cell In myRange
If cell.Value = cell.Offset(0, 1).Value And Not IsEmpty(cell) Then
count = count + 1
ElseIf cell.Value <> cell.Offset(0, 1).Value Then
Range(cell, cell.Offset(0, -count)).Merge
End If
Next
End Sub
Here is what I would like to accomplish
Sub MergeMe()
Dim wks As Worksheet: Set wks = Worksheets(1)
Dim myRange As Range: Set myRange = wks.Range("B2:H5")
Dim myCell As Range
Dim myCell2 As Range
Dim firstColumn As Long: firstColumn = myRange.Columns(1).column + 1
Dim lastColumn As Long: lastColumn = firstColumn + myRange.Columns.Count - 1
Dim firstRow As Long: firstRow = myRange.Rows(1).row
Dim lastRow As Long: lastRow = firstRow + myRange.Rows.Count - 1
Dim column As Long
Dim row As Long
OnStart
For column = lastColumn To firstColumn Step -1
For row = lastRow To firstRow Step -1
Set myCell = wks.Cells(row, column)
Set myCell2 = myCell.Offset(0, -1)
If myCell.Value = myCell2.Value Then
With wks.Range(myCell, myCell2)
.Merge
.VerticalAlignment = xlCenter
.HorizontalAlignment = xlCenter
End With
End If
Next row
Next column
OnEnd
End Sub
There are quite a few tricks in this code:
we need to get the first and last column and row;
then we should be looping from the last cell (bottom right) to the first one (top left);
we should not enter the first column, because we are using .Offset(0,-1) and we compare every cell with its leftmost one;
the reason for the whole operation, is that by default, the value of a merged cells is kept in its left top cell. The other cells of a merged cell are without a value.
This is why we always compare the merged cells with their "left" neighbour;
These are the OnEnd and OnStart, facilitating the operation.
Public Sub OnEnd()
Application.ScreenUpdating = True
Application.EnableEvents = True
Application.AskToUpdateLinks = True
Application.DisplayAlerts = True
Application.Calculation = xlAutomatic
ThisWorkbook.Date1904 = False
Application.StatusBar = False
End Sub
Public Sub OnStart()
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.AskToUpdateLinks = False
Application.DisplayAlerts = False
Application.Calculation = xlAutomatic
ThisWorkbook.Date1904 = False
ActiveWindow.View = xlNormalView
End Sub
Only one merge per group
EDITED to fix - thanks Vityata for the heads-up
Sub MergeEm()
Dim rw As Range, i As Long, s As Long, v
Range("C21:J33").Copy Range("C5:J17") 'for testing purposes: replace previous run
Application.ScreenUpdating = False
For Each rw In Range("C5:J17").Rows 'or wherever
i = 1
s = 1
Do While i < (rw.Cells.Count)
v = rw.Cells(i).Value
'check for a run of same values
Do While Len(v) > 0 And v = rw.Cells(i + s).Value
s = s + 1
If i + s > rw.Cells.Count Then Exit Do
Loop
'if s>1 then had a run: merge those ells
If s > 1 Then
Application.DisplayAlerts = False
rw.Cells(i).Resize(1, s).Merge
rw.Cells(i).HorizontalAlignment = xlCenter
Application.DisplayAlerts = True
i = i + s 'skip over the merged range
s = 1 'reset s
Else
i = i + 1
End If
Loop
Next rw
End Sub
I'm pretty sure what bloats your processing time is the goto causing you to loop through everything yet again every time after every merge
Edit to take column A into account and prevent first column cells to merge with cells outside of myRange:
Sub MergeSimilarCells()
Dim i As Long
Dim myCol As String
Set myRange = Range("K1:L30")
myCol = Left(myRange.Address(True, False), InStr(myRange.Offset(0, 1).Address(True, False), "$") - 1)
If Not Intersect(myRange, Range(myCol & ":" & myCol)).Address = myRange.Address Then
Set myRange = Range(Replace(myRange.Address, Left(myRange.Address(True, False), _
InStr(myRange.Address(True, False), "$")), Left(myRange.Offset(0, 1).Address(True, False), _
InStr(myRange.Offset(0, 1).Address(True, False), "$"))))
For i = myRange.Cells.Count To 1 Step -1
If myRange.Item(i).Value = myRange.Item(i).Offset(0, -1).Value And Not IsEmpty(myRange.Item(i)) Then
Range(myRange.Item(i), myRange.Item(i).Offset(0, -1)).Merge
myRange.Item(i).VerticalAlignment = xlCenter
myRange.Item(i).HorizontalAlignment = xlCenter
End If
Next
End If
End Sub
To clarify why myRange has to start in column B: Offset(0, -1) of any cell in column A will cause an error since there is no column to the left of A.
So I have a code that conducts a loop on Vlookup and the intention of this is that based on the vlookup, it moves specific cells over to another specific cell but in another sheet. The code runs but it does not move the data over for some reason. I've tried another approach within the If comment and doing a range with a copy destination but I begin to get object not identified errors. Hoping someone can see what I may be missing on why the values are not getting moved over.
Dim i, j, lastG, lastD As Long
Dim sht, ws As Worksheet
With Application
.ScreenUpdating = False
.EnableEvents = False
.CutCopyMode = False
End With
' find last row
lastG = Sheets("Log").Cells(Rows.Count, "B").End(xlUp).Row
lastD = Sheets("Slide Layout").Cells(Rows.Count, "B").End(xlUp).Offset(1, 0).Row
Set sht = Sheets("Log")
Set ws = Sheets("Slide Layout")
' loop over values in "Log"
For i = 2 To lastG
currVal = ws.Cells(Rows.Count, "J").End(xlUp)
lookupval = Sheets("Log").Cells(i, "B") ' value to find
If lookupval = currVal Then
sht.Cells(i, "F") = ws.Cells(lastD)
End If
Next i
On Error Resume Next
With Application
.ScreenUpdating = True
.EnableEvents = True
.CutCopyMode = True
End With
End Sub
Not sure if i am following entirely...but it looks like you should have this
sht.Cells(i, "F") = currVal
Update, figured it out! I just replaced the
sht.Cells(i, "F") = ws.Cells(lastD)
with
sht.Cells(i, "F").Copy Destination:=ws.Cells(lastD, "B")
I appreciate the help.
I would like to write a Excel vba to merge cells according to their values and a reference cell in another column. Like the picture attached.
I have over 18000 Lines, with many of variation.
All the values within the line are in order rank.
enter image description here
This is the code that I based my VBA
Sub MergeCells()
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Dim rngMerge As Range, cell As Range
Set rngMerge = Range("B2:C10")
MergeAgain:
For Each cell In rngMerge
If cell.Value = cell.Offset(1, 0).Value And IsEmpty(cell) = False Then
Range(cell, cell.Offset(1, 0)).Merge
GoTo MergeAgain
End If
Next
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
Edit Minor upgrade to allow merged ranges to be extended enabling merge updates.
Merge Vertically Adjacent Cells with Equal Values.
Save in a regular module.
Be sure the constants (Const) come before any other code in the module.
Consider adding a guard to ensure this only runs against the worksheet
it is intended for (see how to after the code).
Run the macro from the Alt-F8 Macro Dialogue.
NB Like most macros, this will wipe the Excel undo buffer.
It cannot be undone with a Ctrl-Z. (The only options are to revert to last saved
or manually edit to the way it was before.)
Copy/Paste
Private Const LastCol = 20
Private Const LastRow = 20
Public Sub Merge_Cells()
Dim r As Range
Dim s As Range
Dim l As Range
Dim c As Long
Dim v As Variant
For c = 1 To LastCol
Set s = Nothing
Set l = Nothing
For Each r In Range(Cells(1, c), Cells(LastRow, c))
v = r.MergeArea(1, 1).Value
If v = vbNullString Then
DoMerge s, l
Set s = Nothing
Set l = Nothing
ElseIf s Is Nothing Then
Set s = r
ElseIf s.Value <> v Then
DoMerge s, l
Set s = r
Set l = Nothing
Else
Set l = r
End If
Next r
DoMerge s, l
Next c
End Sub
Private Sub DoMerge(ByRef s As Range, ByRef l As Range)
If s Is Nothing Then Exit Sub
If l Is Nothing Then Set l = s
Application.DisplayAlerts = False
With Range(s, l)
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.Merge
End With
Application.DisplayAlerts = True
End Sub
Consider finding the last column and last row programmatically.
If the merge should start after row 1:
For Each r In Range(Cells(1, c), Cells(LastRow, c))
^
Change the 1 to the correct row number or replace with an added const variable.
To guard other worksheets, use the tab name (recommend renaming the tab first):
For Each r In Worksheets(TabName).Range(Cells(1, c), Cells(LastRow, c))
^^^^^^^^^^^^^^^^^^^^
Make this edit to the same line as the starting row edit.
And add Private Const TabName = "The Merge Tabs Name" ' Spaces ok
to the top of the Module with the other Const (constants).
Or place the name directly in the code: Worksheets("The Merge Tabs Name").
Add this into a module, select your range of data (excluding headers), run the macro and see if it works for you.
Public Sub MergeRange()
Dim rngData As Range, lngRow As Long, lngCol As Long, strTopCell As String
Dim strBottomCell As String, strThisValue As String, strNextValue As String
Dim strThisMergeArea As String, strNextMergeArea As String
Set rngData = Selection
Application.DisplayAlerts = False
Application.ScreenUpdating = False
With rngData
For lngCol = 1 To .Columns.Count
strTopCell = ""
For lngRow = 1 To .Rows.Count
If strTopCell = "" Then strTopCell = .Cells(lngRow, lngCol).Address
strThisValue = .Cells(lngRow, lngCol)
strNextValue = .Cells(lngRow + 1, lngCol)
If lngCol > 1 Then
strThisMergeArea = .Cells(lngRow, lngCol - 1).MergeArea.Address
strNextMergeArea = .Cells(lngRow + 1, lngCol - 1).MergeArea.Address
If strThisMergeArea <> strNextMergeArea Then strNextValue = strThisValue & "."
End If
If strNextValue <> strThisValue Or lngRow = .Rows.Count Then
strBottomCell = .Cells(lngRow, lngCol).Address
With rngData.Worksheet.Range(strTopCell & ":" & strBottomCell)
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.MergeCells = True
End With
strTopCell = .Cells(lngRow + 1, lngCol).Address
End If
Next
Next
End With
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
There's one trick to this which is able to be changed and that is that it will also group based off the prior column. You can see an example of what I'm talking about in cell C19 ...
... it has worked out that the previous column had a grouping that stopped at that point, therefore, the 1 isn't carried through and grouped to the next lot, it stops and is grouped there. I hope that makes sense and I hope it gives you what you need.
Another thing, this code here will attempt to demerge all of your previously merged data.
Public Sub DeMergeRange()
Dim rngData As Range, lngRow As Long, lngCol As Long, objCell As Range
Dim objMergeArea As Range, strMergeRange As String, strFirstCell As String
Dim strLastCell As String, objDestRange As Range
Set rngData = Selection
Application.DisplayAlerts = False
Application.ScreenUpdating = False
With rngData
For lngCol = 1 To .Columns.Count
For lngRow = 1 To .Rows.Count
Set objCell = .Cells(lngRow, lngCol)
If objCell.Areas(1).MergeArea.Cells.Count > 1 Then
strMergeRange = objCell.Areas(1).MergeArea.Address
objCell.MergeCells = False
strFirstCell = Split(strMergeRange, ":")(0)
strLastCell = Split(strMergeRange, ":")(1)
Set objDestRange = .Worksheet.Range(.Worksheet.Range(strFirstCell).Offset(1, 0).Address & ":" & strLastCell)
.Worksheet.Range(strFirstCell).Copy objDestRange
End If
Next
Next
End With
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
A note, my suggestion is to ensure you have the original source data saved to another workbook/sheet as a backup before running any code over the top of it.
If it stuffs with your data then it will be a right royal pain to undo manually.
I have a table in which I want to create a VBA code to write me the dates In ascending order in horizontally method but the problem when I activate the code, it gives me the date with the last criteria. my code is as follows:
Private Sub Worksheet_Activate()
Dim a As Range, ab As Range, b As Range, bc As Range
Set ab = Range("B3:E3")
Set bc = Range("A2:D2")
For Each a In ab
For Each b In bc
If Cells(3, 1) <> "" Then
Cells(3, a.Column) = Range("A3") + Cells(2, b.Column)
Else
Cells(3, a.Column) = ""
End If
Next
Next
End Sub
yes, I changed the Ranges identification names, but the most important part is the solution.
Private Sub Worksheet_Activate()
On Error Resume Next
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim cng As Range, rng As Range
Set rng = Range("B3:E3")
For Each cng In rng
If Cells(3, 1) <> "" Then
cng.Offset(0, 0).Value = cng.Offset(0, -1) + 1
ElseIf Cells(3, 1) = "" Then
cell.Offset(0, 5).Value = ""
End If
Next
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub