Add new row when cell value changes - vba

I need to add a new row whenever a cell values changes in a defined column. I then need it to do the same for another column, then another column after that.
I used the same code three times, with different columns referenced, but I think it is not working due to the new (blank) rows entered from the first run. I've written it as three separate Subs.
Sub LineTestCODE()
Dim lRow As Long
For lRow = Cells(Cells.Rows.Count, "C").End(xlUp).Row To 2 Step -1
If Cells(lRow, "C") <> Cells(lRow - 1, "C") Then Rows(lRow).EntireRow.Insert
Next lRow
End Sub
Sub LineTestENHANCEMENT()
Dim lRow2 As Long
For lRow2 = Cells(Cells.Rows.Count, "D").End(xlUp).Row To 2 Step -1
If Cells(lRow2, "D") <> Cells(lRow2 - 1, "D") Then Rows(lRow2).EntireRow.Insert
Next lRow2
End Sub
Sub LineTestZONE()
Dim lRow3 As Long
For lRow3 = Cells(Cells.Rows.Count, "G").End(xlUp).Row To 2 Step -1
If Cells(lRow3, "G") <> Cells(lRow3 - 1, "G") Then Rows(lRow3).EntireRow.Insert
Next lRow3
End Sub

I'm not exactly sure how you want to add your rows. It looks as if you want to test the changed cell and if it doesn't match the cell above add a row. I guess it's also possible that you want to add one row per unmatching cell pair in your columns. You'll see both in the code below - take your pick.
I put this code in the Sheet_Change event but you could put it in a module and call it from this event if you wished. You'll see I've disabled events, this could be the problem with your code.
This routine doesn't test if someone pasted values (ie Target.Cells.Count > 1). You might want to handle the possibility of Target being multiple cells.
For Each item in Target.Cells
..//..
Next
could work for you.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim MyColumns As Range
' Define the value columns we're interested in
If MyColumns Is Nothing Then
Set MyColumns = Union(Columns("C"), _
Columns("D"), _
Columns("G"))
End If
' If you just want to add one row for a non-matching change in one of the three columns changes
If Not Intersect(Target, MyColumns) Is Nothing Then
If Target.Row > 1 Then
If Target.Offset(-1).Value <> Target.Value Then
Application.EnableEvents = False
Target.Offset(1).EntireRow.Insert
Application.EnableEvents = True
End If
End If
End If
' If you want to add one row for each non-matching cell value in the three columns
Dim cell As Range
If Not Intersect(Target, MyColumns) Is Nothing Then
If Target.Row > 1 Then
For Each cell In Intersect(MyColumns, Target.EntireRow).Cells
If cell.Offset(-1).Value <> cell.Value Then
Application.EnableEvents = False
cell.Offset(1).EntireRow.Insert
Application.EnableEvents = True
End If
Next
End If
End If
End Sub

Related

Excel vba to select next option in autofilter drop down menu

I have several column with a few hundred rows of data. One of my roles is to look through the data (most commonly in column 2), So what I do is click the little drop down arrow on the column header to open the auto filter list, deselects the first value, then select the next value. Then, likewise, open menu, deselect second value and select third.
There's no fixed number of values either. Different data sheets have varying amounts of data. The data usually goes like 0,10,40,50,60,.... Again it isn't fixed. It is an array however. All the data is in increasing order already.
What I need:
Preferably a button to click (for column 2) that deselects the currently selected value, selects the next value and filters that out
The converse. I.e. Deselects the current value, selects the previous value
Essentially I need a Forward and Back button for my data.
This is what I get when I tried to record my actions.
Sub a()
ActiveSheet.Range("$A$2:$V$609").AutoFilter Field:=2, Criteria1:
="750385/000"
ActiveSheet.Range("$A$2:$V$609").AutoFilter Field:=2, Criteria1:
="750385/010"
ActiveSheet.Range("$A$2:$V$609").AutoFilter Field:=2, Criteria1:
="750385/017"
End Sub
Appreciate any help!!
There is a method to read out the curent filter, from which on you can loop through the column untill you find that value. here you just need to jump to the value in the next row, which now you can put into the filter.
So in conclusion this method would be your "forward"-button
Sub test()
Dim startRow As Integer
startRow = 2
Dim rangeString As String
rangeString = "$A$2:$V$609"
Dim rng As Range
Set rng = Range(rangeString)
Dim currentCrit As String
currentCrit = rng.Parent.AutoFilter.Filters(2).Criteria1
currentCrit = Right(currentCrit, Len(currentCrit) - 1)
Dim i As Integer
For i = startRow To startRow + rng.Rows.Count
If Cells(i, 2).Value = currentCrit Then
i = i + 1
Exit For
End If
Next
If i > rng.Rows.Count + startRow Then
Exit Sub
End If
ActiveSheet.Range(rangeString).AutoFilter Field:=2, Criteria1:=Cells(i, 2).Value
End Sub
Note: This won´t work if there are duplicates in you column B, if this is so replace the part with the For-Loop with the following:
Dim i As Integer
Dim bool As Boolean
bool = False
For i = startRow To startRow + rng.Rows.Count
If Cells(i, 2).Value = currentCrit Then
bool = True
End If
If bool And Cells(i, 2).Value <> currentCrit Then
Exit For
End If
Next
Hope I could help.
I would use Spinbuttons on the sheet and link them to the first cell of the column, it want to filter.
(I called it spbFilterChange and linked it to $B$1)
(picture upload doesnt work here, sorry)
Then you can put the following code in the module of your worksheet:
Private Sub spbFilterChange_SpinDown()
Change_Filter Me.Range(Me.spbFilterChange.LinkedCell), False
End Sub
Private Sub spbFilterChange_SpinUp()
Change_Filter Me.Range(Me.spbFilterChange.LinkedCell), True
End Sub
And the following sub in a standard module:
Option Explicit
Sub Change_Filter(SortField As Range, Up As Boolean)
Dim Filter_Values As Collection
Dim Value_Arr, Val, Sort_Value As String
Application.ScreenUpdating = False
' Find Unique Values in relevant Column -> Collection
Set Filter_Values = New Collection
SortField.Offset(2, 0).Areas(1).AutoFilter SortField.Column
Value_Arr = SortField.Parent.Range(SortField.Offset(3, 0), SortField.Parent.Cells(SortField.Parent.Rows.Count, SortField.Column).End(xlUp)).Value2
On Error Resume Next
For Each Val In Value_Arr
Filter_Values.Add Val, CStr(Val)
Next Val
' Check if Value of LinkedCell is in range
If SortField.Value < 1 Or SortField.Value > Filter_Values.Count Then SortField.Value = 1
' set autofilter
Sort_Value = Filter_Values(SortField.Value)
SortField.Offset(2, 0).AutoFilter SortField.Column, Sort_Value
Application.ScreenUpdating = True
End Sub
This should solve your problem and could be used on different columns and sheets (you have to add another copy of the event-procedures in the worksheet-module).
I would do something like this.
First: Get Help column X where you copy all the Unique data from column B for example.
Option Explicit
Sub CreateUniqueList()
Dim lastrow As Long
lastrow = Cells(Rows.Count, "B").End(xlUp).Row
ActiveSheet.Range("B1:B" & lastrow).AdvancedFilter _
Action:=xlFilterCopy, _
CopyToRange:=ActiveSheet.Range("X1"), _
Unique:=True
ActiveSheet.Range("Y1").Value = "x"
End Sub
Your list could lokk after that like this:
After that, you would need a loop for the buttons:
Something like this.
//The Code is not Testet//
Sub butNextValue()
Dim lastrow As Long
lastrow = Cells(Rows.Count, "B").End(xlUp).Row
For i = 2 To lastrow
If ActiveSheet.Cells(i, 25).Value = "x" Then
If Not ActiveSheet.Cells(i+1, 24)-value = "" Then 'check if next value is there
ActiveSheet.Range("$A$2:$V$609").AutoFilter Field:=2, Criteria1:=ActiveSheet.Cells(i+1, 24)-value
Else
MsgBox "No more Next Values"
End If
Exit For
End If
Next i
End Sub
Sub butPriValue()
Dim lastrow As Long
lastrow = Cells(Rows.Count, "B").End(xlUp).Row
For i = 2 To lastrow
If ActiveSheet.Cells(i, 25).Value = "x" Then
If Not ActiveSheet.Cells(i-1, 24)-value = "Set" OR Not ActiveSheet.Cells(i-1, 24)-value = "" Then 'check if next value is there
ActiveSheet.Range("$A$2:$V$609").AutoFilter Field:=2, Criteria1:=ActiveSheet.Cells(i-1, 24)
Else
MsgBox "No more Pri Values"
End If
Exit For
End If
Next i
End Sub

Excel - VBA fill in cells between 1st and Last value

I am attempting to use VBA to fill all blank cells in rows with the value to the left, with the exception that I only want to fill the blank cells between the first and last value in the row (not including row 1 and column A, which are identifiers).
I've struggled with getting the loop to stop once the last column with a value has been reached (as this changes with each row), rather than running all the way through the last column on the sheet.
Originally this was marked as duplicate (Autofill when there are blank values), but this does not solve the mentioned problem. This continues until the sheet ends. As seen in the picture below, the fill should stop when the last value is reached.
I am searching for a solution that will allow me to do this for an entire sheet at once, even though the data ends in different columns throughout the sheet. There are 1000+ rows, so running for each row could be quite tedious.
I've been using this code to fill the data (excluding the 1st row and column). But this is where I am not sure how to get it to stop at the last value in the row.
Sub test()
With ThisWorkbook.Sheets("Sheet1").Range("A:A")
With Range(.Cells(2, 2), .Cells(.Rows.Count, 36).End(xlUp))
With .Offset(0, 1)
.Value = .Value
On Error Resume Next
.SpecialCells(xlCellTypeBlanks).FormulaR1C1 = "=RC[-1]&"""""
On Error GoTo 0
.Value = .Value
End With
End With
End With
End Sub
If my explanation was not clear, This is a sample and the output I am trying to create
Thank you all so much in advance for all your help!
You may try something like this...
Sub FillBlanks()
Dim r As Long, lr As Long, lc As Long
Dim cell As Range, FirstCell As Range, LastCell As Range
lr = Cells(Rows.Count, 1).End(xlUp).Row
lc = Cells(2, Columns.Count).End(xlToLeft).Column
For r = 3 To lr
Set FirstCell = Range(Cells(r, 1), Cells(r, lc)).Find(what:="*", after:=Cells(r, 1))
If Not FirstCell Is Nothing And FirstCell.Column > 1 Then
Set LastCell = Cells(r, Columns.Count).End(xlToLeft)
Range(FirstCell, LastCell).SpecialCells(xlCellTypeBlanks).FormulaR1C1 = "=RC[-1]"
Range(FirstCell, LastCell).Value = Range(FirstCell, LastCell).Value
End If
Next r
End Sub
And here is yet another solution (just to give you some variety):
Option Explicit
Sub fillInTheBlanks()
Dim lngRow As Long
Dim ws As Worksheet
Dim lngColumn As Long
Dim bolStart As Boolean
Dim lngLastColumn As Long
Dim dblTempValue As Double
Dim arrSheetCopy As Variant
Set ws = ThisWorkbook.Worksheets("Sheet1")
arrSheetCopy = ws.Range(ws.Cells(3, 1), ws.Cells(ws.Cells(ws.Rows.Count, "A").End(xlUp).Row, ws.UsedRange.Columns.Count)).Value2
For lngRow = LBound(arrSheetCopy, 1) To UBound(arrSheetCopy, 1)
bolStart = False
lngLastColumn = 0
For lngColumn = LBound(arrSheetCopy, 2) To UBound(arrSheetCopy, 2)
If Not arrSheetCopy(lngRow, lngColumn) = vbEmpty Then lngLastColumn = lngColumn
Next lngColumn
For lngColumn = LBound(arrSheetCopy, 2) To lngLastColumn
If arrSheetCopy(lngRow, lngColumn) = vbEmpty And bolStart Then
arrSheetCopy(lngRow, lngColumn) = dblTempValue
Else
If Not arrSheetCopy(lngRow, lngColumn) = vbEmpty And IsNumeric(arrSheetCopy(lngRow, lngColumn)) Then
bolStart = True
dblTempValue = CDbl(arrSheetCopy(lngRow, lngColumn))
End If
End If
Next lngColumn
Next lngRow
ws.Range("A3").Resize(UBound(arrSheetCopy, 1), UBound(arrSheetCopy, 2)).Value2 = arrSheetCopy
End Sub
This one is probably the fastest solution (even though it seems a bit bulky with much more lines of code when compared to the other solutions). That's due to the fact that this solution is doing most of the work in memory and not on the sheet. The entire sheet is loaded into a variable and then the work is done on the variable before the result (the variable) is written back to the sheet. So, if you have a speed problem then you might want to consider using this solution.
Here is one possible that meets your sample data's expectations.
Sub wqewqwew()
Dim i As Long, fc As Variant, lc As Long
'necessary if you do not want to confirm numbers and blanks in any row
On Error Resume Next
With ThisWorkbook.Worksheets("Sheet6")
For i = 3 To .Cells(.Rows.Count, "A").End(xlUp).Row
If CBool(Application.Count(Rows(i))) Then
fc = Intersect(.Rows(i), .UsedRange).Offset(0, 1).SpecialCells(xlCellTypeConstants, xlNumbers).Cells(1).Column
If Not IsError(fc) Then
lc = Application.Match(9 ^ 99, .Rows(i))
On Error Resume Next
With .Range(.Cells(i, fc), .Cells(i, lc))
.SpecialCells(xlCellTypeBlanks).Cells.FormulaR1C1 = "=RC[-1]"
.Value = .Value2
End With
End If
End If
Next i
End With
End Sub
Just another solution:
The following code can help is there you need to auto-fill the previous values between 1st and last cells depending on value of 1st cell as mentioned in question Excel - VBA fill in cells between 1st and Last value
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim i As Long
For i = 2 To Target.Column
If Cells(Target.Row, i) = "" Then
If Cells(Target.Row, i - 1) <> "" Then
Range(Cells(Target.Row, i), Cells(Target.Row, i)).Value = Range(Cells(Target.Row, i - 1), Cells(Target.Row, i - 1)).Value
End If
End If
Next i
End Sub
This sub is activated by clicking on any cell. Same cell marks the end of the loop i.e. to stop the loop just click the cell till which you want to fill the blank cells.

Update cell, automatically copy row to a separate sheet

I have a worksheet comprising of two columns (A and B) ... the first of which is just a name, the second is a number.
If I make an edit to a number in column B, I want Excel to automatically copy that entire row to a second worksheet in order to create a list of edits that I have made.
The second worksheet would then be a continually updated list of changes that I have made to the first sheet, with the latest change (a copy of the two updated columns) added to the next unused row.
I hope that a bit of VBA trickery might be able to make this happen, but require some help to make it happen.
Try this in the sheet where you have data (under the Excel Objects), e.g Sheet1
Option Explicit
Dim PrevVal As Variant
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 2 Then
Application.ScreenUpdating = False
Dim rng As Range
Dim copyVal As String
Set rng = Nothing
Set rng = Range("A" & Target.Row & ":B" & Target.Row)
'copy the values
With Worksheets("Sheet2").Cells(Rows.Count, 1).End(xlUp)
.Offset(1, 0).Resize(1, rng.Cells.Count).Value = rng.Value
With Worksheets("Sheet1")
Range("A" & Target.Row).Copy
copyVal = CStr(PrevVal)
End With
.Offset(1, 0).PasteSpecial xlPasteFormats
.Offset(1, 1) = copyVal
Application.CutCopyMode = False
End With
End If
Application.ScreenUpdating = True
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Rows.Count > 1 Then Exit Sub
If Target.Columns.Count > 1 Then Exit Sub
PrevVal = Target.Value
End Sub
Here is some additional code to check the row number, as per the above answer and comments.
Dim row_num As Long
row_num = Cells(Rows.Count, "B").End(xlUp).Row
If row_num > 1 then row_num = row_num + 1 'Add 1 only when row number doesn't equal to 1, otherwise - 1.

Deleting All Rows That Have an Empty "B" Column Using VBA

I'm looking to create a macro that deletes all rows that don't contain any data in Column B. Any help would be appreciated. This is all I got for now.
Sub DeleteAllEmptyBRows()
Dim lr As Long
lr = Cells(Rows.Count, "B").End(xlUp).Row
For Each cell In Range("B1:B" & lr)
If cell.Value = "" Then
cell.Row.Delete
Exit Sub
End If
Next cell
End Sub
You can use SpecialCells to do this in one quick line:
Range("B:B").SpecialCells(xlCellTypeBlanks).EntireRow.Delete
I'd use the above, but also for your own knowledge, here's how you could do it following your code:
Sub DeleteAllEmptyBRows()
Dim lr As Long, i&
lr = Cells(Rows.Count, "B").End(xlUp).Row
For i = lr To 1 Step -1 'Since you're deleting rows, start at the end, and work upwards
If Cells(i, 2).Value = "" Then
Cells(i, 2).EntireRow.Delete
End If
Next i
End Sub
Note that you have an Exit Sub in yours, after the first time a row is deleted. I removed that, since you want to loop through all cells in the range. Again, this is a loop so will take longer, and has more room for errors, than the simple one liner above.
You are missing some parameters:
Cells(cell.Row, 2).Delete Shift:=xlUp
If you need the entire row, just change to:
cell.Row.EntireRow.Delete

Macro - delete rows based on date

I am very new to VBA and macros in Excel. I have a very large excel spreadsheet in which column A holds dates. I am trying to delete the rows which have a value smaller than a certain date and this is what I have come up with till now..
Sub DELETEDATE()
Dim x As Long
For x = 1 To Cells.SpecialCells(xlCellTypeLastCell).Row
Debug.Print Cells(x, "A").Value
If CDate(Cells(x, "A")) < CDate("01/01/2013") Then
Cells(i, "A").EntireRow.Delete
End If
Next x
Next i
End Sub
I am receiving a Next without For error... can somebody help please?
This lends itself well to using the .AutoFilter property of a Range. The script below contains a comment for each step taken:
Option Explicit
Sub DeleteDateWithAutoFilter()
Dim MySheet As Worksheet, MyRange As Range
Dim LastRow As Long, LastCol As Long
'turn off alerts
Application.DisplayAlerts = False
'set references up-front
Set MySheet = ThisWorkbook.Worksheets("Sheet1")
'identify the last row in column A and the last col in row 1
'then assign a range to contain the full data "block"
With MySheet
LastRow = .Range("A" & .Rows.Count).End(xlUp).Row
LastCol = .Range("A" & .Columns.Count).End(xlToLeft).Column
Set MyRange = .Range(.Cells(1, 1), .Cells(LastRow, LastCol))
End With
'apply autofilter to the range showing only dates
'older than january 1st, 2013, then deleting
'all the visible rows except the header
With MyRange
.AutoFilter Field:=1, Criteria1:="<1/1/2013"
.SpecialCells(xlCellTypeVisible).Offset(1, 0).Resize(.Rows.Count).Rows.Delete
End With
'turn off autofilter safely
With MySheet
.AutoFilterMode = False
If .FilterMode = True Then
.ShowAllData
End If
End With
'turn alerts back on
Application.DisplayAlerts = True
End Sub
Running this code on a simple example (on "Sheet1" in this picture) that looks like this:
Will delete all rows with a date older than 1/1/2013, giving you this result:
To answer your question
I am receiving a Next without For error
The problem is you are trying to loop on i but you haven't opened a For i loop. When you indent the code below any code that invokes a Loop or condition (i.e. If) it becomes obvious
Sub DELETEDATE()
Dim x As Long
For x = 1 To Cells.SpecialCells(xlCellTypeLastCell).Row
Debug.Print Cells(x, "A").Value
If CDate(Cells(x, "A")) < CDate("01/01/2013") Then
Cells(i, "A").EntireRow.Delete 'i has no value so Cells(0, "A") is ??
End If
Next x
Next i 'where is the For i = ... in this code?
End Sub
When writing code I try to:
Enter the end command immediately if it's needed. So type If...Then, hit [ENTER], type End If, hit [HOME], hit [ENTER], hit [UP ARROW] then [TAB] to the right place to write the conditional code so that anyone will be able to read and understand it easily.
Always use Option Explicit at the top of every module to force variable declarations.
a tip about deleting rows based on a condition
If you start at the top and work down, every time you delete a row your counter will effectively move to the cell two rows below the row you deleted because the row immediately below the deleted row moves up (i.e. it is not tested at all).
The most efficient way is to loop up from the bottom or your rows:
Sub DELETEDATE()
Dim x As Long
For x = [a1].SpecialCells(xlCellTypeLastCell).Row To 1 Step -1
Debug.Print Cells(x, "A").Value
If CDate(Cells(x, "A")) < CDate("01/01/2013") Then
Cells(x, "A").EntireRow.Delete 'changed i to x
End If
Next x
End Sub
This way, the next row you want to test has been preserved - you've only moved the row below up by 1 and you've tested that row earlier.
Please try with this
Sub DELETEDATE()
Dim x As Long
last = Range("A65536").End(xlUp).Row
For x = 1 To last
Debug.Print Cells(x, "A").Value
check:
If x <= last Then
If Trim(CDate(Cells(x, "A"))) <= Trim(CDate("7/29/2013")) Then
last = last - 1
Cells(x, "A").EntireRow.Delete
GoTo check
End If
End If
Next x
End Sub
You have an additional Next i for some reason in your code as highlighted by the debugger. Try the below:
Sub DELETEDATE()
Dim x As Long
For x = 1 To Cells.SpecialCells(xlCellTypeLastCell).Row
Debug.Print Cells(x, "A").Value
If CDate(Cells(x, "A")) < CDate("01/01/2013") Then
Cells(i, "A").EntireRow.Delete
End If
Next x
End Sub