Inserting value in 2nd cell based on value in 1st cell - excel

I am trying to write a script where as it reads down an entire column starting with E2 and if a cell in that column has a particular value (for this example, let's say A, E, I, O, or U) then it enters a value of "Y" in cell F2, however it continues this pattern until it runs out of filled cells in column E.
I understand the logic of
Dim ColE As String
For ColE = 2 To Rows.Count
Next i
If E1 = "A" Or "E" Or "I" Or "O" Or "U" Then F2 = "Y"
but how do i repeat that say all the way down the entire column of E until it runs out of filled cells in column E

Here is an easy way to implement a list of OR's:
Sub marine()
Dim s1 As String, s2 As String
s1 = "AEIOU"
For i = 2 To 25
If Range("E" & i).Value <> "" Then
If InStr(s1, Range("E" & i).Value) > 0 Then
Range("F" & i).Value = "Y"
End If
End If
Next i
End Sub

Suitable option here is using the select case command with an if-loop
for i = 2 to Cells(Rows.Count, 5).End(xlUp).Row '5 = Column E
Select Case Range("E"&i).value
Case "A", "E", "I", "O", "U"
Range("F"&i).value
End Select
next
Using Select Case allows you to also give different commands for other inputs in column E and is way easier to handle than if-conditions for your specific requirements.
Cells(Rows.Count, 5).End(xlUp).Row '5
This will return the row number of the last entry in the fifth column (column E). You can use it in the for-loop to iterate until the very last row.

Search Multiple Criteria
Copy the code into a standard module (e.g. Module1).
Carefully adjust the values in the constants section.
The Code
Option Explicit
Sub searchMultipleCriteria()
' Handle Errors
Const Proc = "searchMultipleCriteria"
On Error GoTo cleanError
' Define constants.
Const SheetName As String = "Sheet1"
Const FirstRow As Long = 2
Const CriteriaCol As Variant = "E" ' 1 or "A"
Dim CriteriaVals As Variant: CriteriaVals = Array("A", "E", "I", "O", "U")
Const ResultCol As Variant = "F" ' 1 or "A"
Const ResultVal As String = "Y"
Dim wb As Workbook: Set wb = ThisWorkbook
' Write values from Criteria Column Range to Criteria Array.
Dim ws As Worksheet: Set ws = wb.Worksheets(SheetName)
Dim rng As Range
Set rng = ws.Columns(CriteriaCol).Find("*", , xlFormulas, , , xlPrevious)
If rng Is Nothing Then GoTo EmptyColumn
If rng.Row < FirstRow Then GoTo NoRange
Set rng = ws.Range(ws.Cells(FirstRow, CriteriaCol), rng)
Dim Criteria As Variant: Criteria = rng.Value
' Write values from Result Column Range to Result Array.
Set rng = rng.Offset(, ws.Columns(ResultCol).Column - rng.Column)
Dim Result As Variant: Result = rng.Value
' Modify values in Result Array.
Dim i As Long, Curr As Variant
For i = 1 To UBound(Criteria)
' Note: 'Match' is not case-sensitive i.e. A=a...
Curr = Application.Match(Criteria(i, 1), CriteriaVals, 0)
If Not IsError(Curr) Then
Result(i, 1) = ResultVal
Else ' Maybe you wanna do something here...
'Result(i, 1) = "N"
End If
Next i
' Write values from Result Array to Result Range.
rng.Value = Result
' Inform user.
MsgBox "Data transferred.", vbInformation, "Success"
' Revert Settings (not utilized in this Sub)
CleanExit:
Exit Sub
' Not As Planned
EmptyColumn:
MsgBox "Looking in an empty column to define a range with values!?", _
vbExclamation, "'" & Proc & "': Empty Column"
GoTo CleanExit
NoRange:
MsgBox "Trying to define a range with an ending row lower than " _
& "the starting row!?", _
vbExclamation, "'" & Proc & "': No Range"
GoTo CleanExit
cleanError:
MsgBox "An unexpected error occurred in '" & Proc & "'!" & vbCr _
& "Run-time error '" & Err.Number & "':" & vbCr & Err.Description, _
vbCritical, "'" & Proc & "': Unexpected Error"
On Error GoTo 0
GoTo CleanExit
End Sub

Related

VBA: Keep first and last rows of duplicate column values of an Excel sheet

I have an Excel worksheet with 20K rows like this:
header1
header2
1
P
2
P
3
P
4
Q
5
R
6
R
7
R
8
R
9
S
10
S
I want a VBA code to delete the rows containing duplicates, but keep the first and last row of the duplicates. The result should be like this:
header1
header2
1
P
3
P
4
Q
5
R
8
R
9
S
10
S
I have modified the following code found here to do just that, but every time I have to manually select the range containing the duplicates in column header2.
Sub Delete_Dups_Keep_Last_v2()
Dim SelRng As Range
Dim Cell_in_Rng As Range
Dim RngToDelete As Range
Dim SelLastRow As Long
Application.DisplayAlerts = False
Set SelRng = Application.InputBox("Select cells", Type:=8)
On Error GoTo 0
Application.DisplayAlerts = True
SelLastRow = SelRng.Rows.Count + SelRng.Row - 1
For Each Cell_in_Rng In SelRng
If Cell_in_Rng.Row < SelLastRow Then
If Cell_in_Rng.Row > SelRng.Row Then
If Not Cell_in_Rng.Offset(1, 0).Resize(SelLastRow - Cell_in_Rng.Row).Find(What:=Cell_in_Rng.Value, Lookat:=xlWhole) Is Nothing Then
'this value exists again in the range
If RngToDelete Is Nothing Then
Set RngToDelete = Cell_in_Rng
Else
Set RngToDelete = Application.Union(RngToDelete, Cell_in_Rng)
End If
End If
End If
End If
Next Cell_in_Rng
If Not RngToDelete Is Nothing Then RngToDelete.EntireRow.Delete
End Sub
Another code found here by user A.S.H. automates the manual selection and speed by using Dictionary, but fails to produce the wanted result.
Sub keepFirstAndLast()
Dim toDelete As Range: Set toDelete = Sheet1.Rows(999999) '(to not start with a null range)
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Dim a As Range
For Each a In Sheet1.Range("B2", Sheet1.Range("B999999").End(xlUp))
If Not dict.Exists(a.Value2) Then
dict(a.Value2) = 0 ' first appearence, dont save the row
Else
' if last observed occurrence was a duplicate, add it to deleted range
If dict(a.Value2) > 0 Then Set toDelete = Union(toDelete, Sheet1.Rows(dict(a.Value2)))
dict(a.Value2) = a.row ' not first appearence, save the row for eventual deletion
End If
Next
toDelete.Delete
End Sub
Simple solution:
Sub KeepFirstLast()
Application.ScreenUpdating = False
Dim lastRow As Long
lastRow = Sheets(1).Cells(Rows.Count, 1).End(xlUp).Row
Dim i As Long
Dim x As Long
Dim currentValue As String
For i = lastRow To 2 Step -1
If i = 2 Then
Application.ScreenUpdating = True
Exit For
End If
currentValue = Sheets(1).Cells(i, 2).Value
x = i - 1
Do While Sheets(1).Cells(x, 2).Value = currentValue And Sheets(1).Cells(x - 1, 2).Value = currentValue
Sheets(1).Rows(x).Delete
x = x - 1
Loop
i = x + 1
Next i
Application.ScreenUpdating = True
End Sub
You may benefit from SpecialCells to select those rows based on formula:
Sub test()
Dim LR As Long 'last row
Dim LC As Long 'last column
Dim SR As Long 'starting row
Dim rng As Range
Set rng = Range("A1") 'change this to TOP LEFT CELL OF YOUR DATA
SR = rng.Row
LR = rng.CurrentRegion.Cells(rng.CurrentRegion.Rows.Count, 1).Row
LC = Cells(1, Columns.Count).End(xlToLeft).Column 'last column used
'we add new column with formula to delete
With Range(Cells(SR + 1, LC + 1), Cells(LR, LC + 1))
.FormulaR1C1 = "=IF(OR(RC[-1]<>R[-1]C[-1],RC[-1]<>R[1]C[-1]),""x"",0)"
.SpecialCells(xlCellTypeFormulas, 1).EntireRow.Delete
End With
'clear formula
LR = rng.CurrentRegion.Cells(rng.CurrentRegion.Rows.Count, 1).Row
Range(Cells(SR + 1, LC + 1), Cells(LR, LC + 1)).Clear
Set rng = Nothing
End Sub
[![enter image description here][1]][1]
The tricky part is here:
.FormulaR1C1 = "=IF(OR(RC[-1]<>R[-1]C[-1],RC[-1]<>R[1]C[-1]),""x"",0)"
.SpecialCells(xlCellTypeFormulas, 1).EntireRow.Delete
First line will create and IF(OR) formula to check if the row must be deleted or not. It will return x if not, else 0
Second line will delete entire rows only if it contains a number (zero)
[1]: https://i.stack.imgur.com/UlhtI.gif
This can also be accomplished using Power Query, available in Windows Excel 2010+ and Excel 365 (Windows or Mac)
To use Power Query
Select some cell in your Data Table
Data => Get&Transform => from Table/Range or from within sheet
When the PQ Editor opens: Home => Advanced Editor
Make note of the Table Name in Line 2
Paste the M Code below in place of what you see
Change the Table name in line 2 back to what was generated originally.
Read the comments and explore the Applied Steps to understand the algorithm
M Code
let
//change next line to your actual table name in your worksheet
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Changed Type" = Table.TransformColumnTypes(Source,{{"header1", Int64.Type}, {"header2", type text}}),
//Group by header2
// then return the first and last table rows if there is more than a single row
#"Grouped Rows" = Table.Group(#"Changed Type", {"header2"}, {
{"header1", each if Table.RowCount(_) = 1 then _
else Table.FromRecords({Table.First(_),Table.Last(_)}),
type table[header1=Int64.Type, header2=text]}
}),
//expand the subtables and set the column order
#"Expanded header1" = Table.ExpandTableColumn(#"Grouped Rows", "header1", {"header1"}),
#"Reordered Columns" = Table.ReorderColumns(#"Expanded header1",{"header1", "header2"})
in
#"Reordered Columns"
Keep First and Last In Sorted Range
Option Explicit
Sub DeleteNotFirstNorLast()
Const ProcName As String = "DeleteNotFirstNorLast"
Dim RowsDeleted As Boolean ' to inform
On Error GoTo ClearError ' enable error trapping
' Constants (adjust!)
Const FirstCellAddress As String = "A1"
Const CriteriaColumnIndex As Long = 2
Const Criteria As String = "#$%"
' Reference the worksheet.
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
Application.ScreenUpdating = False
' Turn off AutoFilter.
If ws.AutoFilterMode Then ws.AutoFilterMode = False
' Reference the table range.
Dim trg As Range: Set trg = RefCurrentRegion(ws.Range(FirstCellAddress))
' Write an ascending integer sequence adjacent to the right
' of the table range.
AppendColumnOfAscendingIntegers trg
' Include this helper column to the table range.
Set trg = trg.Resize(, trg.Columns.Count + 1)
' Reference the criteria column range.
Dim crg As Range: Set crg = trg.Columns(CriteriaColumnIndex)
' It is assumed that the criteria column is already sorted favorably.
' If not, you could do something like the following:
' Sort the table range by the criteria column ascending.
'trg.Sort crg, xlAscending, , , , , , xlYes
' Write the data rows (no headers) count to a variable.
Dim drCount As Long: drCount = trg.Rows.Count - 1
' Reference the criteria column data range (headers excluded).
Dim cdrg As Range: Set cdrg = crg.Resize(drCount).Offset(1)
' Write the values from the criteria column data range to an array.
Dim cData As Variant: cData = GetRange(cdrg)
' Replace the unwanted values in the array with the criteria.
KeepFirstAndLastInColumn cData
' Write the (modified) values from the array back to the range.
cdrg.Value = cData
' Reference the table data range (no headers).
Dim tdrg As Range: Set tdrg = trg.Resize(drCount).Offset(1)
' Filter the table range in the criteria column by the criteria.
trg.AutoFilter CriteriaColumnIndex, Criteria
' Attempt to reference the table data visible (filtered) range.
Dim tdvrg As Range
On Error Resume Next ' defer error trapping
Set tdvrg = tdrg.SpecialCells(xlCellTypeVisible)
On Error GoTo ClearError ' re-enable error trapping
' Remove the filter.
ws.AutoFilterMode = False
' Attempt to delete the table data visible range.
If Not tdvrg Is Nothing Then
tdvrg.Delete xlShiftUp
RowsDeleted = True
End If
' Reference the helper column.
Dim hrg As Range: Set hrg = trg.Columns(trg.Columns.Count)
' Sort the table range by the helper column ascending.
trg.Sort hrg, xlAscending, , , , , , xlYes
' Clear the helper column.
hrg.Clear
SafeExit:
Application.ScreenUpdating = True ' to see any changes while reading message
' Inform.
If RowsDeleted Then
MsgBox "Rows deleted.", vbInformation, ProcName
Else
MsgBox "Nothing deleted.", vbExclamation, ProcName
End If
Exit Sub
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume SafeExit
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns a reference to the range starting with the first cell
' of a range and ending with the last cell of the first cell's
' Current Region.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefCurrentRegion( _
ByVal FirstCell As Range) _
As Range
Const ProcName As String = "RefCurrentRegion"
On Error GoTo ClearError
If FirstCell Is Nothing Then Exit Function
With FirstCell.Cells(1).CurrentRegion
Set RefCurrentRegion = FirstCell.Resize(.Row + .Rows.Count _
- FirstCell.Row, .Column + .Columns.Count - FirstCell.Column)
End With
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Writes an ascending integer sequence adjacent to the right
' of a range.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub AppendColumnOfAscendingIntegers( _
ByVal trg As Range, _
Optional ByVal FirstInteger As Long = 1)
Const ProcName As String = "AppendColumnOfAscendingIntegers"
On Error GoTo ClearError
With trg
With .Resize(, 1).Offset(, .Columns.Count)
.Value = .Worksheet.Evaluate("ROW(" & CStr(FirstInteger) & ":" _
& CStr(FirstInteger + .Rows.Count - 1) & ")")
End With
End With
ProcExit:
Exit Sub
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a range ('trg') in a 2D one-based array.
' Remarks: If ˙rg` refers to a multi-range, only its first area
' is considered.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRange( _
ByVal trg As Range) _
As Variant
Const ProcName As String = "GetRange"
On Error GoTo ClearError
If trg.Rows.Count + trg.Columns.Count = 2 Then ' one cell
Dim Data As Variant: ReDim Data(1 To 1, 1 To 1): Data(1, 1) = trg.Value
GetRange = Data
Else ' multiple cells
GetRange = trg.Value
End If
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: In the first column of a 2D one-based array of sorted values,
' keeps the first and last occurrence of each value and replaces
' the remaining occurrences with a string.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub KeepFirstAndLastInColumn( _
ByRef cData As Variant, _
Optional ByVal Criteria As String = "#$%")
Const ProcName As String = "KeepFirstAndLastInColumn"
On Error GoTo ClearError
Dim OldString As String: OldString = CStr(cData(1, 1))
Dim r As Long
Dim cr As Long
Dim FirstRow As Long
Dim NewString As String
For r = 2 To UBound(cData, 1)
NewString = CStr(cData(r, 1))
If NewString = OldString Then
If FirstRow = 0 Then
FirstRow = r
End If
Else
If FirstRow > 0 Then
For cr = FirstRow To r - 2
cData(cr, 1) = Criteria
Next cr
FirstRow = 0
End If
OldString = NewString
End If
Next r
ProcExit:
Exit Sub
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Sub

How to use the ISNULL/empty Function (VBA)

Trying something like this. My code is find the value of cell B7 of Sheet1 in column A of Sheet15. If it is not found, do nothing. If it is found, write the value in the cell next to the found value to the next available cell in column F of Sheet4.
I have this number in Sheet1
Code will find that Sheet1 value in Sheet2 Col"A" then copy the Col"B" value that is "156".
After that code will paste that "156" VALUE IN Sheet3.Range("C2") to till where Col"B" used range end.
Looking forward to your help.
Dim lastR4 As Long
lastR4 = Sheet4.Range("E" & Rows.Count).End(xlUp).Row
If Sheet15.Range("A2:B2") Is Empty Then
Exit Sub
Else
Sheet4.Range("F11:F" & lastR4).Value = Sheet15.Range("A" & _
WorksheetFunction.Match(Sheet1.Range("B7").Value, Sheet15.Range("A:A"), 0)).Offset(0, 1)
End If
Try the next slightly adapted code, please:
Sub testFillDat()
Dim lastR4 As Long
lastR4 = Sheet4.Range("E" & rows.count).End(xlUp).row
If WorksheetFunction.CountA(Sheet15.Range("A2:B2")) = 0 Then
Exit Sub
Else
If Sheet15.Range("B2") = "" Then
Sheet4.Range("F11:F" & lastR4).Value = 0
Else
Sheet4.Range("F11:F" & lastR4).Value = Sheet15.Range("A" & _
WorksheetFunction.match(Sheet1.Range("B7").Value, Sheet15.Range("A:A"), 0)).Offset(0, 1)
End If
End If
End Sub
If there is no match of "B7" (Sheet1) in "A:A" (Sheet15) the code will raise an error. It can be adapted to catch it, but I only tried to show you how checking if a range with multiple cells is empty... In rest, your code should work.
Lookup Using Application.Match
Using Application.Match is easier and cleaner then using WorksheetFunction.Match because you can test the result with IsError or IsNumeric.
The Code
Option Explicit
Sub test()
' The cell containing the lookup value.
Dim cel1 As Range
Set cel1 = Sheet1.Range("B7")
' First 'available' cell in column `E` column, but in column `F`.
Dim cel4 As Range
Set cel4 = Sheet4.Range("E" & Sheet4.Rows.Count).End(xlUp).Offset(1, 1)
' Range from cell `A1` to the last non-empty cell.
Dim rng15 As Range
Set rng15 = Sheet15.Range("A1", Sheet15.Range("A" & Sheet15.Rows.Count).End(xlUp))
' The index of the found value. If not found, returns error value.
' Therefore we use 'Variant'.
Dim cMatch As Variant
cMatch = Application.Match(cel1.Value, rng15, 0)
' Test if found with 'IsNumeric()'. You can also use 'Not IsError()'
' instead. Or even 'IsError()', but then switch the statements.
If IsNumeric(cMatch) Then
cel4.Value = rng15.Cells(cMatch).Offset(, 1).Value
MsgBox "Value '" & cel1.Value & "' copied.", vbInformation, "Success"
Else
MsgBox "Value '" & cel1.Value & "' not found.", vbCritical, "Fail"
End If
End Sub
EDIT
Adjust the sheets and cells appropriately.
Fill Range with Found Value
Sub test2()
' The cell containing the lookup value.
Dim cel1 As Range
Set cel1 = Sheet1.Range("B7")
' Range from cell 'E2' to the last non-empty cell, but column `F`.
Dim rng4 As Range
Set rng4 = Sheet4.Range("E2", Sheet4.Range("E" & Sheet4.Rows.Count) _
.End(xlUp)).Offset(, 1)
' Range from cell `A1` to the last non-empty cell.
Dim rng15 As Range
Set rng15 = Sheet15.Range("A1", Sheet15.Range("A" & Sheet15.Rows.Count) _
.End(xlUp))
' The index of the found value. If not found, returns error value.
' Therefore we use 'Variant'.
Dim cMatch As Variant
cMatch = Application.Match(cel1.Value, rng15, 0)
' Test if found with 'IsNumeric()'. You can also use 'Not IsError()'
' instead. Or even 'IsError()', but then switch the statements.
If IsNumeric(cMatch) Then
rng4.Value = rng15.Cells(cMatch).Offset(, 1).Value
MsgBox "Value '" & cel1.Value & "' copied.", vbInformation, "Success"
Else
MsgBox "Value '" & cel1.Value & "' not found.", vbCritical, "Fail"
End If
End Sub

Excel VBA: How do I add text to a blank cell in a specific column then loop to the next blank cell and add text?

I need a macro to add text to blank cells in Column A. The macro needs to skip cells that have text. The macro needs to stop looping at the end of the data set.
I am trying to use an If Else statement, but I think I'm on the wrong track. My current, non-working code is below. Thank you so much - I'm still new to VBA
Sub ElseIfi()
For i = 2 To 100
If Worksheets("RawPayrollDump").Cells(2, 1).Value = "" Then
Worksheets("RawPayrollDump").Cells(2, 1).Value = "Administration"
Else if(not(worksheets("RawPayrollDump").cells(2,1).value="")) then 'go to next cell
End If
Next
End Sub
To find the last row of data, use the End(xlUp) function.
Try this code. It replaces all empty cells in column A with Administration.
Sub ElseIfi()
Set ws = Worksheets("RawPayrollDump")
lastrow = ws.Cells(Rows.Count, 1).End(xlUp).Row ' last data row
For i = 2 To lastrow ' all rows until last data row
If ws.Cells(i, 1).Value = "" Then ' column A, check if blank
ws.Cells(i, 1).Value = "Administration" ' set text
End If
Next
End Sub
There is no need to loop. Please try this code.
Sub FillBlanks()
Dim Rng As Range
With Worksheets("RawPayrollDump")
Set Rng = Range(.Cells(2, "A"), .Cells(.Rows.Count, "A").End(xlUp))
End With
On Error Resume Next
Set Rng = Rng.SpecialCells(xlCellTypeBlanks)
If Err Then
MsgBox "There are no blank cells" & vbCr & _
"in the specified range.", _
vbInformation, "Range " & Rng.Address(0, 0)
Else
Rng.Value = "Administration"
End If
End Sub
Replace Blanks feat. CurrentRegion
Range.CurrentRegion
Since OP asked for "... stop looping at the end of the data set. ",
I've written this CurrentRegion version.
As I understand it, the end of the data set doesn't mean that there
cannot be blank cells below the last cell containing data in column
A.
Use the 1st Sub to test the 2nd, the main Sub (replaceBlanks).
Adjust the constants including the workbook (in the 1st Sub) to fit your needs.
Criteria is declared as Variant to allow other data types not just strings.
The Code
Option Explicit
Sub testReplaceBlanks()
Const wsName As String = "RawPayrollDump"
Const FirstCellAddress As String = "A2"
Const Criteria As Variant = "Administration"
Dim wb As Workbook: Set wb = ThisWorkbook
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
replaceBlanks ws, FirstCellAddress, Criteria
End Sub
Sub replaceBlanks(Sheet As Worksheet, _
FirstCellAddress As String, _
Criteria As Variant)
' Define column range.
Dim ColumnRange As Range
Set ColumnRange = Intersect(Sheet.Range(FirstCellAddress).CurrentRegion, _
Sheet.Columns(Sheet.Range(FirstCellAddress) _
.Column))
' To remove the possibly included cells above the first cell:
Set ColumnRange = Sheet.Range(Range(FirstCellAddress), _
ColumnRange.Cells(ColumnRange.Cells.Count))
' Note that you can also use the addresses instead of the cell range
' objects in the previous line...
'Set ColumnRange = sheet.Range(FirstCellAddress, _
ColumnRange.Cells(ColumnRange.Cells.Count) _
.Address)
' or a mixture of them.
' Write values from column range to array.
Dim Data As Variant
If ColumnRange.Cells.Count > 1 Then
Data = ColumnRange.Value
Else
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = ColumnRange.Value
End If
' Modify array.
Dim i As Long, k As Long
For i = 1 To UBound(Data)
If IsEmpty(Data(i, 1)) Then Data(i, 1) = Criteria: k = k + 1
Next i
' Write modified array to column range.
' The following line is used when only the first cell is known...
'Sheet.Range(FirstCellAddress).Resize(UBound(Data)).Value = Data
' ...but since the range is known and is the same size as the array,
' the following will do:
ColumnRange.Value = Data
' Inform user.
If k > 0 Then GoSub Success Else GoSub Fail
Exit Sub
' Subroutines
Success:
MsgBox "Wrote '" & Criteria & "' to " & k & " previously " _
& "empty cell(s) in range '" & ColumnRange.Address & "'.", _
vbInformation, "Success"
Return
Fail:
MsgBox "No empty cells in range '" & ColumnRange.Address & "'.", _
vbExclamation, "Nothing Written"
Return
End Sub

Change the values in a column depending upon different criteria

I want the values in Column D to change depending upon the value in Column A. Some values do not need to be amended at all if the conditions aren't met
Sub Test()
Application.ScreenUpdating = False
Dim startrow As Integer
Dim row As Integer
Dim c As Range
Dim Lastrow As Long
Application.ScreenUpdating = False
Lastrow = Cells(Rows.Count, "D").End(xlUp).row
For Each c In Range("D2:D" & Lastrow)
If Cells(row, 1) = "Bol" Then
c.Value = c.Value * 1.19
End If
If Cells(row, 1) = "Amazon" Then
c.Value = c.Value * 1.2
End If
Next
Application.ScreenUpdating = True
End Sub
I think I have an error in the lines beginning with c.Value = c.Value * .....
I'm new to VBA and just trying to make sense of it
I just provide this variant. It is working with array, so theoretically it is very quick. Probably no need to turn off the screen updating.
Sub test()
Dim lastRow As Long, i As Long
With Sheet1
lastRow = .Cells(Rows.Count, "D").End(xlUp).row
Dim vA As Variant 'Represents A2-A lastrow
vA = .Range("A2").Resize(lastRow - 1).Value
Dim vb As Variant 'Represents D2-D lastrow
vb = .Range("D2").Resize(lastRow - 1).Value
i = 0
Dim v As Variant
For Each v In vA
i = i + 1
If v = "Bol" Then
vb(i, 1) = vb(i, 1) * 1.19
ElseIf v = "Amazon" Then
vb(i, 1) = vb(i, 1) * 1.2
End If
Next v
.Range("D2").Resize(lastRow - 1).Value = vb ' Writing the values to the D column
End With
End Sub
You have to forecast and handle all possible conditions. Use this code please:
Sub Test()
Application.ScreenUpdating = False
Dim row As Integer
Dim Lastrow As Long
'I've assumed that you are working on sheet1
Lastrow = Sheets(1).Cells(Rows.Count, "D").End(xlUp).row
If Lastrow > 1 Then
For row = 2 To Lastrow
If Sheets(1).Cells(row, 1).Value = "Bol" Then
Sheets(1).Cells(row, 4).Value = Sheets(1).Cells(row, 4).Value * 1.19
End If
If Sheets(1).Cells(row, 1).Value = "Amazon" Then
Sheets(1).Cells(row, 4).Value = Sheets(1).Cells(row, 4).Value * 1.2
End If
Next
Else
MsgBox ("There is no data at column D")
End If
Application.ScreenUpdating = True
End Sub
There are quite a few ways to go about what you're trying to do. For what it's worth, this is how I would go about it. You had a few additional variables you didn't need, and your 'row' variable wasn't assigned a value at all.
Sub test2()
Dim lastRow As Long, _
i As Long
Application.ScreenUpdating = False
With Sheet1
lastRow = .Cells(Rows.Count, "D").End(xlUp).row
For i = 2 To lastRow
If .Cells(i, 1).Value = "Bol" Then
.Cells(i, 4).Value = .Cells(i, 4).Value * 1.19
End If
If .Cells(i, 1).Value = "Amazon" Then
.Cells(i, 4).Value = .Cells(i, 4).Value * 1.2
End If
Next i
End With
Application.ScreenUpdating = True
End Sub
I kept is relatively simple, so hopefully you can follow what's going on. If you have a lot of "If" statements, it may be cleaner to use VBAs "Select Case".
Also the text strings as you have them set up are case sensitive. "Bol" does not equal "bol" maybe that doesn't matter, but something to be aware of. If the string you pass it is "amazon" it will not pass the 'If' test.
Another assumption I made was that your data is on Sheet1. You should get in the habit of fully qualifying your ranges, it will make your life a lot easier as your code gets more complicated.
Last bit, I'm assuming the values in column D are all numbers. If there is text in there, you may run in to problems multiplying it.
Good luck!
You can simplify your code, and make it easier to read, by looping trough column A instead of column D and using the If/ElseIf statement to test each cell for either of the two conditions. By setting your range and defining c as a range variable for each cell in the range, you only have to loop through each cell and test for the two conditions. If the cell contains Bol use the Offset property to multiple the current value in column D by 1.19; ElseIf the cell contains Amazon use the Offset property to multiple the current value in column D by 1.2. Comments provide in the code.
Application.ScreenUpdating = False
'use the With statement to define your workbook and sheet, change as needed
'Note: "ThisWorkbook" identifies the workbook which contains this code
With ThisWorkbook.Sheets("Sheet1")
'Define the range you want to loop through, using the column you want to test
Dim rng As Range: Set rng = .Range("A2", .Cells(.Rows.Count, "A").End(xlUp))
'Define the variable for each cell-range
Dim c As Range
'loop through each "c" in the range and if one of the conditions are met
For Each c In rng
If c = "Bol" Then
'then use the "Offset property" to modify the value in column D
c.Offset(, 3) = c.Offset(, 3).Value * 1.19
ElseIf c = "Amazon" Then
c.Offset(, 3) = c.Offset(, 3).Value * 1.2
End If
Next c
End With
Application.ScreenUpdating = True
In-Place Modification
All the solutions have one common issue: you can use them only once. If you need to change the values after adding new records (rows) you should consider adding another column with the initial values so the code could be written to identify what has already been changed and what not. But that's for another question.
Your Sub Solution
You actually had only one serious mistake in two-three places.
Instead of row in the If statements you should have used c.Row and you could have removed Dim row As Integer:
Sub Test_Almost_Fixed()
Application.ScreenUpdating = False
Dim startrow As Integer
Dim c As Range
Dim Lastrow As Long
Application.ScreenUpdating = False
Lastrow = Cells(Rows.Count, "D").End(xlUp).row
For Each c In Range("D2:D" & Lastrow)
If Cells(c.Row, 1) = "Bol" Then
c.Value = c.Value * 1.19
End If
If Cells(c.Row, 1) = "Amazon" Then
c.Value = c.Value * 1.2
End If
Next
Application.ScreenUpdating = True
End Sub
Additionally after getting rid of the extra Application.ScreenUpdating = False and the Dim startrow As Integer and some further cosmetics, you could have had something like this:
Sub Test_Fixed()
Dim c As Range
Dim Lastrow As Long
Lastrow = Cells(Rows.Count, "D").End(xlUp).row
Application.ScreenUpdating = False
For Each c In Range("D2:D" & Lastrow)
If Cells(c.Row, 1) = "Bol" Then
c.Value = c.Value * 1.19
End If
If Cells(c.Row, 1) = "Amazon" Then
c.Value = c.Value * 1.2
End If
Next
Application.ScreenUpdating = True
End Sub
A More Complex Sub Solution
Use the following for the ActiveSheet in a standard module (e.g. Module1). For a particular sheet you can place it in a sheet module (e.g. Sheet1) or create a button on the sheet.
Tip: When you have such a simple (short, fast) code and especially when you're using a Button to run it (in a 'one-time operation code'), it is good practice to use a MsgBox at the end of the code to actually know that the code has run and to prevent accidentally pressing the Button more than once.
Option Explicit
Sub Test()
Const Proc As String = "Test"
On Error GoTo cleanError
' Define Constants.
Const FirstRow As Long = 2
Const SourceColumn As Variant = 1 ' e.g. 1 or "A"
Const TargetColumn As Variant = 4 ' e.g. 4 or "D"
Dim Criteria As Variant ' Add more values.
Criteria = Array("Bol", "Amazon")
Dim Multiplier As Variant ' Add more values.
Multiplier = Array(1.19, 1.2)
' Check if Criteria and Multiplier Arrays have the same number
' of elements (columns).
Dim ubCM As Long: ubCM = UBound(Criteria)
If UBound(Multiplier) <> ubCM Then Exit Sub
' Write Source and Target Ranges to Source and Target Arrays.
Dim rng As Range
' Define Last Non-Empty Cell.
Set rng = Columns(TargetColumn).Find("*", , xlValues, , , xlPrevious)
' Check if Target Column is empty.
If rng Is Nothing Then Exit Sub
' Check if the row of Last Non-Empty Cell is above FirstRow.
If rng.Row < FirstRow Then Exit Sub
Dim Target As Variant
' Write Target Range to Target Array.
Target = Range(Cells(FirstRow, TargetColumn), rng).Value
Set rng = Nothing
Dim ubST As Long: ubST = UBound(Target)
Dim Source As Variant
' Write Source Range to Source Array.
Source = Cells(FirstRow, SourceColumn).Resize(ubST).Value
' Modify Target Array.
Dim i As Long, j As Long
' Loop through elements (rows) of Source and Target Arrays.
For i = 1 To ubST
' Loop through elements (columns) of Criteria and Multiplier Arrays.
For j = 0 To ubCM
' Check if the value in current element (row) of Source Array
' matches the value of current element (column) in Criteria Array.
If Source(i, 1) = Criteria(j) Then
' Modify value in current element (row) of Target Array
' by multiplying it with the value of current element (column)
' of Multiplier Array.
Target(i, 1) = Target(i, 1) * Multiplier(j)
' Since a match is found, there is no need to loop anymore.
Exit For
End If
Next j
Next i
Erase Source
' Write values of Target Array to Target Range.
Cells(FirstRow, TargetColumn).Resize(ubST).Value = Target
Erase Target
' Inform user.
MsgBox "Data copied.", vbInformation, "Success"
Exit Sub
cleanError:
MsgBox "An unexpected error occurred in '" & Proc & "'." & vbCr _
& "Run-time error '" & Err.Number & "':" & vbCr & Err.Description _
, vbCritical, Proc & " Error"
End Sub
An Event Solution
To make it automatically change the values in column D for each change of a value in column A you can place the following code into the sheet module (e.g. Sheet1):
Option Explicit
Private Const SOURCE_COLUMN As Variant = 1 ' e.g. 1 or "A"
Private Const TARGET_COLUMN As Variant = 4 ' e.g. 4 or "D"
Private Sub sdfWorksheet_Change(ByVal Target As Range)
Const Proc As String = "Worksheet_Change"
On Error GoTo cleanError
If Intersect(Columns(SOURCE_COLUMN), Target) Is Nothing Then Exit Sub
Const FirstRow As Long = 2
Dim rng As Range
Set rng = Columns(TARGET_COLUMN).Find("*", , xlValues, , , xlPrevious)
If rng Is Nothing Then Exit Sub
If rng.Row < FirstRow Then Exit Sub
Set rng = Cells(FirstRow, SOURCE_COLUMN).Resize(rng.row - FirstRow + 1)
If Intersect(rng, Target) Is Nothing Then Exit Sub
Dim cel As Range
Application.Calculation = xlCalculationManual ' -4135
For Each cel In Target.Cells
TestChange cel
Next cel
CleanExit:
Application.Calculation = xlCalculationAutomatic ' -4105
Exit Sub
cleanError:
MsgBox "An unexpected error occurred in '" & Proc & "'." & vbCr _
& "Run-time error '" & Err.Number & "':" & vbCr & Err.Description _
, vbCritical, Proc & " Error"
On Error GoTo 0
Resume CleanExit
End Sub
Private Sub TestChange(SourceCell As Range)
Const Proc As String = "TestChange"
On Error GoTo cleanError
Dim Criteria As Variant
Criteria = Array("Bol", "Amazon")
Dim Multiplier As Variant
Multiplier = Array(1.19, 1.2)
Dim ubCM As Long: ubCM = UBound(Criteria)
If UBound(Multiplier) <> ubCM Then Exit Sub
Application.ScreenUpdating = False
Dim TargetCell As Range, j As Long
For j = 0 To ubCM
If SourceCell.Value = Criteria(j) Then
Set TargetCell = Cells(SourceCell.row, TARGET_COLUMN)
TargetCell.Value = TargetCell.Value * Multiplier(j)
Exit For
End If
Next j
CleanExit:
Application.ScreenUpdating = True
Exit Sub
cleanError:
MsgBox "An unexpected error occurred in '" & Proc & "'." & vbCr _
& "Run-time error '" & Err.Number & "':" & vbCr & Err.Description _
, vbCritical, Proc & " Error"
On Error GoTo 0
Resume CleanExit
End Sub

how to iterate over all rows of a excel sheet in VBA

I have this code (This code is in Access VBA which tries to read an excel file and after checking, possibly import it):
Set ExcelApp = CreateObject("Excel.application")
Set Workbook = ExcelApp.Workbooks.Open(FileName)
Set Worksheet = Workbook.Worksheets(1)
now I want to iterate over all rows of the excel worksheet. I want something such as this:
for each row in Worksheet.rows
ProcessARow(row)
next row
where
function ProcessARow(row as ???? )
' process a row
' how Should I define the function
' how can I access each cell in the row
' Is there any way that I can understand how many cell with data exist in the row
end function
My questions:
How to define the for each code that it iterate correctly on all
rows that has data?
How to define ProcessARow properly
How to get the value of each cell in the row.
How to find how many cell with data exist in the row?
Is there any way that I detect what is the data type of each cell?
edit 1
The link solves on problem :
How to define the for each code that it iterate correctly on all rows that has data?
but what about other questions?
For example, how to define ProcessARow correctly?
If you need the values in the Row, you need use the 'Value' Property and after do an cycle to get each value
for each row in Worksheet.rows
Values=row.Value
For each cell in Values
ValueCell=cell
next cell
next row
Unfortunately you questions are very broad however I believe the below sub routine can show you a few ways of achieving what you are after. In regards to what datatype each cell is more involved as it depends what data type you wish to compare it to however I have included some stuff to hopefully help.
sub hopefullyuseful()
dim ws as worksheet
dim rng as Range
dim strlc as string
dim rc as long, i as long
dim lc as long, j as long
dim celltoprocess as range
set ws = activeworkbook.sheets(activesheet.name)
strlc = ws.cells.specialcells(xlcelltypeLastCell).address
set rng = ws.range("A1:" & lc)
rc = rng.rows.count()
debug.print "Number of rows: " & rc
lc = rng.columns.count()
debug.print "Number of columns: " & lc
'
'method 1 looping through the cells'
for i = 1 to rc
for j = 1 to lc
set celltoprocess = ws.cells(i,j)
'this gives you a cell object at the coordinates of (i,j)'
'[PROCESS HERE]'
debug.print celltoprocess.address & " is celltype: " & CellType(celltoprocess)
'here you can do any processing you would like on the individual cell if needed however this is not the best method'
set celltoprocess = nothing
next j
next i
'method 2 looping through the cells using a for each loop'
for each celltoprocess in rng.cells
debug.print celltoprocess.address & " is " & CellType(celltoprocess)
next celltoprocess
'if you just need the data in the cells and not the actual cell objects'
arrOfCellData = rng.value
'to access the data'
for i = lbound(arrOfCellData,1) to ubound(arrOfCellData,1)
'i = row'
for j = lbound(arrOfCellData,2) to ubound(arrOfCellData,2)
'j = columns'
debug.print "TYPE: " & typename(arrOfCellData(i,j)) & " character count:" & len(arrOfCellData(i,j))
next j
next i
set rng=nothing
set celltoprocess = nothing
set ws = nothing
end sub
Function CellType(byref Rng as range) as string
Select Case True
Case IsEmpty(Rng)
CellType = "Blank"
Case WorksheetFunction.IsText(Rng)
CellType = "Text"
Case WorksheetFunction.IsLogical(Rng)
CellType = "Logical"
Case WorksheetFunction.IsErr(Rng)
CellType = "Error"
Case IsDate(Rng)
CellType = "Date"
Case InStr(1, Rng.Text, ":") <> 0
CellType = "Time"
Case IsNumeric(Rng)
CellType = "Value"
End Select
end function
sub processRow(byref rngRow as range)
dim c as range
'it is unclear what you want to do with the row however... if you want
'to do something to cells in the row this is how you access them
'individually
for each c in rngRow.cells
debug.print "Cell " & c.address & " is in Column " & c.column & " and Row " & c.row & " has the value of " & c.value
next c
set c = nothing
set rngRow = nothing
exit sub
if you want your other questions answered you will have to be more specific as to what you are trying to accomplish
While I like the solution offered by #krazynhazy I believe that the following solution might be slightly shorter and closer to what you asked for. Still, I'd use the CellType function offered by Krazynhazy rather than all the Iif I currently have in the below code.
Option Explicit
Sub AllNonEmptyCells()
Dim rngRow As Range
Dim rngCell As Range
Dim wksItem As Worksheet
Set wksItem = ThisWorkbook.Worksheets(1)
On Error GoTo EmptySheet
For Each rngRow In wksItem.Cells.SpecialCells(xlCellTypeConstants).EntireRow.Rows
Call ProcessARow(wksItem, rngRow.Row)
Next rngRow
Exit Sub
EmptySheet:
MsgBox "Sheet is empty." & Chr(10) & "Aborting!"
Exit Sub
End Sub
Sub ProcessARow(wksItem As Worksheet, lngRow As Long)
Dim rngCell As Range
Debug.Print "Cells to process in row " & lngRow & ": " & wksItem.Range(wksItem.Cells(lngRow, 1), wksItem.Cells(lngRow, wksItem.Columns.Count)).SpecialCells(xlCellTypeConstants).Count
For Each rngCell In wksItem.Range(wksItem.Cells(lngRow, 1), wksItem.Cells(lngRow, wksItem.Columns.Count)).SpecialCells(xlCellTypeConstants)
Debug.Print "Row: " & lngRow, _
"Column: " & rngCell.Column, _
"Value: " & rngCell.Value2, _
IIf(Left(rngCell.Formula, 1) = "=", "Formula", IIf(IsDate(rngCell.Value), "Date", IIf(IsNumeric(rngCell.Value2), "Number", "Text")))
Next rngCell
End Sub
Note, that you have to call the sub to call a row must also include the sheet on which a row should be processed.

Resources