I'm making a VBA macro to concatenate a number given by the user with a previous column value. Once the loop gets to the value given by the user (the top value), the loop would start again since number one. By now my loop gets to the top value and starts from one, but every time this happened the code skip to the next cell, could you tell me why is this happening guys? (down value by default).
Sorry i'm a little bit new on VBA, here is my try:
Sub Balance()
Dim myValue As Integer
myValue = InputBox("Please, give me a number")
Range("A2").Select
Range("A:A,C:C,F:F,H:J,L:L,T:T,W:X").Delete
Range("A2").Select
firstrow = ActiveCell.Row
Selection.End(xlDown).Select
lastrow = ActiveCell.Row
For i = 1 To lastrow
If Range("M" & i) = "AB" Then
For j = 1 To myValue
watcher = j
Range("N" & i) = "TV" & " " & Range("M" & i) & " " & watcher
i = i + 1
Next j
End If
Next i
End Sub
This the output with the number 10 as input (Column N):
I would like to reach this goal:
You already have your answer by Vandear explaining why the row is getting skipped. However here is an alternative way using only one loop to achieve what you want. But before that couple of suggestions
Suggestions:
Use Option Explicit and declare your variables. You may want to see Optimizing the VBA Code and improve the performance
Avoid the use of Select/Activate You may want to see How to avoid using Select in Excel VBA
Avoid the use of xlDown to get the last row. You may want to see Finding Last Row
When accepting number from users, use Application.InputBox with a Type:=1 instead of just InputBox. This way you will only restrict numbers.
Is this what you are trying?
Code:
Option Explicit
Sub Balance()
Dim ws As Worksheet
Dim lRow As Long
Dim i As Long, counter As Long
Dim myValue As Long
myValue = Application.InputBox(Prompt:="Please, give me a number", Type:=1)
If myValue < 1 Then Exit Sub
'~~> Change this to the relevant sheet
Set ws = Sheet1
With ws
'~~> For demonstration below. I did not use this line below. But
'~~> If you need to delete those columns then use it this way
.Range("A:A,C:C,F:F,H:J,L:L,T:T,W:X").Delete
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Set your counter
counter = 1
For i = 1 To lRow
If .Range("M" & i) = "AB" Then
Range("N" & i) = "TV" & " " & .Range("M" & i).Value2 & " " & counter
counter = counter + 1
'~~> Reset your counter if it crosses user input
If counter > myValue Then counter = 1
End If
Next i
End With
End Sub
In action:
Couple of things before I answer your problem:
It's advisable to declare your variables for better structure:
Dim i As Integer, j As Integer, MyValue As Integer, firstrow As Integer, lastrow As Integer
You can skip the selection of a cell and directly reference it:
firstrow = Range("A2").Row
lastrow = Range("A2").End(xlDown).Row
The Answer to your Problem:
When the code exits the for-next loop for j, i is increased by 1 (i=i+1), and then it will be increased again by 1 when it proceeds to the next i line. This is the reason why it skips to the next row. So after the for-next loop for j, you need to decrease i by 1 before proceeding to the for-next loop for i.
For i = 1 To lastrow
If Range("M" & i) = "AB" Then
For j = 1 To myValue
watcher = j
Range("N" & i) = "TV" & " " & Range("M" & i) & " " & watcher
i = i + 1
Next j
i=i-1 <-------
End If
Next i
Related
I have a Macro that loops through one column that hold dates that are not random (they go vertically from old to more recent ),then finds every date that is greater than Now () then goes to its adjacent cells and clears them
it works fine , except that the looping takes a small bit of time, which i believe could me much faster if the Macro wouldn't loop against all values in the column(500 rows) but instead just stops at the first cell match , then goes to its adjacent cells and clear all contents from that row till the bottom of the table ( ie if the match is at row 15 , then it clear contents from row 15 all the way down to 500 ) without having to check every single row
The code below as described is designed to loop through every value , i need it to stop at the first match and clear contents of adjacent cells underneath in the range the macro covers
can somone please help me acheive this
Dim R As Long
For R = 1 To 500
If Cells(R, "A").Value >= Now Then Cells(R, "B").Value = ""
If Cells(R, "A").Value >= Now Then Cells(R, "C").Value = ""
Next
End Sub
Using your methodology, if you want to know how to drop out of a loop once the criteria are matched - you would use an Exit For. You also can clear column B and C at the same time, like so:
Dim R As Long
With ActiveWorkbook.Sheets("Sheet1")
For R = 1 To 500
If .Cells(R, "A").Value >= Now Then
.Range("B" & R & ":C500").ClearContents
Exit For
End If
Next
End With
In the above, I've also added reference to the sheet. This is always good practice to prevent possible errors once more than one sheet is available.
Here's an example with a binary search.
Option Explicit
Public Sub ClearByDates(Optional targetDate As Variant = Null)
Dim thisSheet As Worksheet
Dim activeRange As Range
Dim lowerRow As Long, higherRow As Long, thisRow As Long
Dim thisDate As Date
' If no date is provided, assume today is the cutoff
If IsNull(targetDate) Then targetDate = Date
' Assuming that we're putting this code in the worksheet that you want to _
clear. If you decide to put it in a separate module you can either: _
(1) Pass in the worksheet you want to modify as an argument, or _
(2) Explicitly set the worksheet you want to modify using ActiveWorkbook.Worksheets(Name)
Set thisSheet = Me
Set activeRange = thisSheet.UsedRange
With activeRange
' Not only is it cleaner if you use With, you get better performance in VBA _
because it's a hint that the object should be kept loaded and ready.
' Getting the range of rows with values
lowerRow = .Rows(1).Row
higherRow = lowerRow + activeRange.Rows.Count - 1
' Don't start processing until you're in the date range; _
this should handle most common worksheet header situations.
Do Until IsDate(activeRange.Cells(lowerRow, 1))
lowerRow = lowerRow + 1
If lowerRow > higherRow Then Exit Sub
Loop
' Use a binary search to find the first row where the date is _
greater than or equal to the target date.
Do Until lowerRow >= higherRow - 1
thisRow = (higherRow + lowerRow) / 2
thisDate = .Cells(thisRow, 1)
Debug.Print "Row " & Right(" " & thisRow, 7) & ": " & Format(thisDate, "YYYY-MM-DD") & " ";
If thisDate >= targetDate Then
Debug.Print ">= " & Format(targetDate, "YYYY-MM-DD");
higherRow = thisRow
Else
Debug.Print "< " & Format(targetDate, "YYYY-MM-DD");
lowerRow = thisRow
End If
Debug.Print " (lowerRow = " & lowerRow & ", higherRow = " & higherRow & ")"
Loop
' Assuming we just want to clear columns B & C. This can easily be adjusted _
to clear all columns to the right by using .Columns(.Columns.Count) in the _
ending range.
thisSheet.Range("B" & thisRow & ":C" & .Rows(.Rows.Count).Row).ClearContents
End With
End Sub
I have a table where I need to find a final score based on time used (Column A and B) and points scored(row 2). e.g. If the person used 43 seconds and scored 11 points, the final score would be 10.
My first thought was too loop through the time to get the row number like this:
Do Until (.Range("A" & i).Value <= time) And _
(.Range("B" & i).Value >= time)
i = i + 1
Loop
cellN = i
And then loop through the points to get the column letter and then add them together with something like this:
finalScore = .Range(cellL & cellN).Value
But how do I loop through the columns? And is there an easier way of storing the final score in a variable?
Try the next function, please:
Function FinalScore(lTime As Long, points As Long) As Long
Dim sh As Worksheet, lastRow As Long, lastCol As Long
Dim colP As Long, i As Long
Set sh = ActiveSheet 'use here your specific worksheet
lastRow = sh.Range("A" & Rows.count).End(xlUp).Row
lastCol = sh.cells(1, Columns.count).End(xlToLeft).Column
For i = 2 To lastRow
If lTime >= sh.Range("A" & i).Value And _
lTime <= sh.Range("B" & i).Value Then
colP = WorksheetFunction.Match(points, sh.Rows(2), 0)
FinalScore = sh.cells(i, colP).Value: Exit Function
End If
Next i
End Function
It can be called/checked in this way:
Sub testFinalScore()
MsgBox FinalScore(43, 11)
End Sub
This is the basic. It must be improved with some error handling part. To warn in case of not using Long variables as parameters, if they exceed the existing range limits etc...
maxRow = ws.UsedRange.EntireRow.Count
For n = 1 To maxRow
Do While Not IsEmpty(ws.Range("D" & n).Value)
ws.Range("D" & n & ":" & "E" & n).Delete shift:=xlShiftToLeft
Rows(n + 1).Insert shift:=xlShiftDown
maxRow = maxRow + 1
Loop
Next n
I was just wondering why my code always exit when n = 19 (number of original maxRow count). I added maxRow = maxRow +1 because I inserted one row so that it doesn't exit until it is finished.
What I am trying to do, is to remove the two cells (D&E) and inserting a row underneath until D&E is empty.
Thanks
You did not answer my question and I prepared an answer based on the assumption that the necessary code must replace the cells in D:E with their right neighbors (F:G) and insert a row after such a processed range. If this assumption is correct, please test the next code:
Sub testDeleteRangeInsertRow()
Dim ws As Worksheet, n As Long
Set ws = ActiveSheet
n = 2
Do While Not IsEmpty(ws.Range("D" & n).value)
ws.Range("D" & n & ":" & "E" & n).Delete Shift:=xlShiftToLeft
Rows(n + 1).Insert Shift:=xlShiftDown
n = n + 2
Loop
End Sub
If my assumption is wrong, please better explain (in words...) what do you need to accomplish...
I have gone crazy with vba projects around the house and helping my wife upping her reports to the next level. I’m having troubles putting down to VBA what I’m thinking. If anyone has felt my pain please shed some light on the actual script that would help me over this hump. Summary might be comparing cell value for certain text using InStr and if doesn't exist the appending characters to the right end. I can append and run thru one cycle of the loop but get confused with trying to write the logic I'm thinking.
Alittle background on the report: One row equals one reservation. Within that row there is a column labeled “Nights”. This column is filtered for any reservation with more than “1” Night. Example: could be 3 nights, 6 nights, and 10 nights doesn’t matter. I have a macro that sorts these reservations and splits the one reservation into multiple rows totaling the number value in the “Nights” column. Basically, copying and inserting the rows next to each other. While this filtered is still applied (SpecialVisibleCells Only). Now I have another column labeled “ResNumber”. With 3, 6, or 10 rows split out the “ResNumber” column is the same number. I’m tasked with walking down this ‘ResNumber” column and appending a “-1” for the first row. A “-2” for the second reservation “-3” for the third and possibly a forth “-4” Until the last row of the copied for that one reservation group. Then the cycle (loop) starts again on the next group or block of rows. Same procedure.
Dim lnrow As Integer
Dim llrow As String
Dim rownuml As Integer 'row checker
Dim colnuml As String 'column checker
Dim count As Integer
Dim total As String 'Value of reservation's "Nights" column Offset(,17)
Dim startnum As Integer 'Start number for counter
Dim actcell As String 'Activecell
startnum = 1
With sh
llrow = .Cells(.Rows.count, 2).End(xlUp).row
If llrow = "" Then Exit Sub
.Cells(2, 2).Resize(llrow - 1).SpecialCells(xlCellTypeVisible).Select
For lnrow = 2 To llrow
rownuml = ActiveCell.row
colnuml = ActiveCell.Column
total = ActiveCell.offset(, 17).Value
For count = 1 To total
rownuml = ActiveCell.row
colnuml = ActiveCell.Column
actcell = ActiveCell.Value
'Compares row 1 and checks resNumber value for "-1" if none exist it appends.
If InStr(ActiveCell.Value, "-1") = 0 Then
ActiveCell.Value = ActiveCell.Value & "-1"
Else
GoTo nexrow
End If
'Compares row 2 and checks resNumber value of above cell.
If InStr(ActiveCell.offset(-1, 0).Value, "-1") = 0 Then
Resume Next
If InStr(ActiveCell.Value, "-2") = 0 Then
ActiveCell.Value = ActiveCell.Value & "-2"
GoTo nexrow
End If
'to jump out of loop nexrow
'ActiveCell moves one row down.
ActiveCell.offset(1, 0).SpecialCells(xlCellTypeVisible).Select
rownuml = ActiveCell.row 'just checking row number
colnuml = ActiveCell.Column 'just checking column number
'since 1st reservation is already in the DB startnum starts at # 1. The counter
startnum = startnum + count
Next count
Next
End With
Try:
Option Explicit
Sub test()
Dim LastRow As Long, Times As Long, Counter As Long, i As Long, y As Long
Dim strNumber As String
With ThisWorkbook.Worksheets("Sheet1")
LastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
For i = 2 To LastRow
strNumber = .Range("B" & i).Value
Times = Application.WorksheetFunction.CountIf(.Range("B2:B" & LastRow), strNumber)
If Times > 1 Then
Counter = 1
For y = 2 To LastRow
If strNumber = .Range("B" & y).Value Then
.Range("B" & y).Value = strNumber & " - " & Counter
.Range("D" & y).Value = 1
Counter = Counter + 1
End If
Next y
End If
Next i
End With
End Sub
Results:
I'm creating a Macro to do almost exactly what is outlined here:
Excel/Categorization Macro(formula) based on string of text containing keywords
My question is that in the code from the example above Like "" is used to check to see if the Description matches a keyword and if it does then it pulls the corresponding category name. In my case, I don't currently have keywords for every possible category (but will eventually have them as I collect more transaction data), meaning some of the cells in my keyword column are blank and the way the above code is written it considers patternfound = true when it encounters an empty cell. How do I alter the If statement with "Like" or something similar so that it skips over a cell if it's completely blank and only provides a match when there are some characters (that match) in the cell?
I've found a work around by putting "N/A" in the empty cells but I'd rather not do that. Here is my code:
Sub Categorize()
Dim lastrow As Long, lastrow2 As Long
Dim i As Integer, j As Integer
Dim PatternFound As Boolean
Call speedup
lastrow = Sheets("Categorization").Range("B" & Rows.Count).End(xlUp).Row
lastrow2 = Sheets("Cleaned Spend Data").Range("C" & Rows.Count).End(xlUp).Row
For i = 4 To lastrow2
PatternFound = False
j = 1
Do While PatternFound = False And j < lastrow
j = j + 1
If UCase(Sheets("Cleaned Spend Data").Range("B" & i).Value) Like "*" & UCase(Sheets("Categorization").Range("B" & j).Value) & "*" Then
Sheets("Cleaned Spend Data").Range("D" & i).Value = Sheets("Categorization").Range("A" & j).Value
PatternFound = True
End If
Loop
Next i
Call normal
End Sub
Thanks!
You can test for an empty cell...
Also - your code could be cleaner using a couple of variables for your worksheets.
Sub Categorize()
Dim lastrow As Long, lastrow2 As Long
Dim i As Integer, j As Integer
Dim PatternFound As Boolean, shtCat As Worksheet, shtCleaned As Worksheet
Dim v, t
Set shtCat = Sheets("Categorization")
Set shtCleaned = Sheets("Cleaned Spend Data")
Call speedup
lastrow = shtCat.Range("B" & Rows.Count).End(xlUp).Row
lastrow2 = shtCleaned.Range("C" & Rows.Count).End(xlUp).Row
For i = 4 To lastrow2
v = UCase(UCase(shtCleaned.Range("B" & i).Value))
For j = 1 To lastrow
t = UCase(Sheets("Categorization").Range("B" & j).Value)
If Len(t) > 0 And v Like "*" & t & "*" Then
shtCleaned.Range("D" & i).Value = shtCat.Range("A" & j).Value
Exit For
End If
Next j
Next i
Call normal
End Sub