trouble in obtaining sum of values after checking rows and columns - vba

I have some trouble trying to get the values of the first column with EG and 0 adding up the values found at column H, EG and 0. The sum will be placed at Column O row 3.
I have tried to write out the code, to find the Name, EG and also the temp, 0. Unfortunately, the code looks for the last 0 seen at column O. If you have any idea, do share yours, will appreciate your help! THanks!
Sub Macro1()
Dim LastRow1 As Long, RowG As Range, RowCheck As Long, Rowtosave As Long, LastCol1 As Long
Dim EGCheck As Long, ColEG As Range, firstEG As Long, IGCheck As Long, ColIG As Range
Dim findtemp As Range, tempRow As Long, tempRow1 As Long, lastEG As Long
Dim totalvalue As Long, valuestoadd As Long, tempCheck As Long
Dim emptycol As Long, empty1 As Range, Colempty As Long, tempcol As Long
LastRow1 = 50
LastCol1 = 50
For RowCheck = 1 To LastRow1
With Worksheets("Sheet1").Cells(RowCheck, 1)
If .Value = "Name" Then
Rowtosave = RowCheck
For EGCheck = 1 To LastCol1
With Worksheets("Sheet1").Cells(Rowtosave, EGCheck)
If .Value = "EG" Then
firstEG = EGCheck
End If
End With
Next EGCheck
For IGCheck = 1 To LastCol1
With Worksheets("Sheet1").Cells(Rowtosave, IGCheck)
If .Value = "IG" Then
lastEG = IGCheck - 1
End If
End With
Next IGCheck
End If
End With
Next RowCheck
'Look for temp
totalvalue = 0
For RowCheck = 1 To LastRow1
With Worksheets("Sheet1").Cells(RowCheck, 1)
If .Value = "temp" Then
tempRow1 = RowCheck
For tempCheck = 1 To lastEG
With Worksheets("Sheet1").Cells(tempRow1, tempCheck)
If .Value = "0" Then
tempcol = tempCheck
valuestoadd = Worksheets("Sheet1").Cells(tempRow1, tempcol).Select
totalvalue = totalvalue + valuestoadd
End If
End With
Next tempCheck
End If
End With
Next RowCheck
'Look for empty column
emptycol = 1
Do
Set empty1 = Worksheets("Sheet1").Cells(tempRow1, emptycol)
If empty1 = "" Then
emptycol = emptycol + 1
Colempty = emptycol
empty1 = totalvalue
End If
Exit Do
emptycol = emptycol + 1
Loop
End Sub
Follow up:
What my code needs to do is as follows:
Check Row for "Name".
After finding "Name" will look for "EG" and set it to first "EG"
Then it will try to look for first "IG" so that we can have the last of the first"EG" (note: "EG" groups Column B-D, IG group Column E to G, 2nd EG group H-J and so on.
After that, look for temp.
Then find 0 or 100 or overall which will be within the column from for example "EG"'s category, "First EG" TO "Last of the first EG".
Then add 1 to Row so that we can tabulate the sum of test1.
Get the sum of 0 from "First EG(Column B)" and "Second EG(Column H)" and place it at an empty column say Column O. For this example, the resultant will be 2+ 0 = 2
I realized the confusion caused when i use lastEG. What i meant for lastEG was last of first EG
I hope this clear up the doubts.
Discussion with Tony and follow up on improving the question
There is a row with "Name" in column A. Call this the Name row. It may not necessary be on the first row.
Within the Name row, there are cells containing "EG". A few cells after a cell containg "EG" is a cell containing "IG". Call an range starting at a cell containing "EG" and continuing to (but not including) the next cell containing "IG", an EG range. In the example, the EG ranges are B:D and H:J.
There is a row with "temp" in column A. Call this the Temp row. In the example, the Temp row is immediately under the Name row but apparently this is not a requirement. The Name and Temp rows can appear anywhere within the Sheet and Name will always be above temp but that does not means they are paired together, some times we can have name, month, temp.
There is a row with "test1" in column A. Call this the Test1 row.
In the example, the Test1 Row is immediately under the Temp row. The code does not access the Test1 row. The explanation says that the row under the Temp row is the Test1 row but this is not checked.
Within each EG range within the Temp row, there is a cell containing '0', '100' and 'Overall'. The cell below the zero will be empty or contain a number. The value of those numbers for all EG ranges is to be totalled. This total is to be saved in the cell after the first empty cell below the temp row, for example, O3 - Q3 where values will be placed at.
I hope this gets better and Tony, I have used some of your points and edited it.

I have corrected the most obvious errors in your code but it makes little sense to me so I cannot tell if it does what you want.
' Option Explicit ensures that misspelt names are
' not taken as an implicit declaration
Option Explicit
Sub Macro1()
' This is legal syntax but I find it confusing. I only have two or more
' variables in a Dim if they are closely related.
Dim LastRow1 As Long, RowG As Range, RowCheck As Long, Rowtosave As Long, LastCol1 As Long
Dim EGCheck As Long, ColEG As Range, firstEG As Long, IGCheck As Long, ColIG As Range
Dim findtemp As Range, tempRow As Long, tempRow1 As Long, lastEG As Long
Dim totalvalue As Long, valuestoadd As Long, tempCheck As Long
Dim emptycol As Long, empty1 As Range, Colempty As Long, tempcol As Long
' You can nest Withs.
' You only use one worksheet so let us declare that at the top.
With Worksheets("Sheet1")
' These statements will have to be replaced when the number of rows and
' columns exceed 50.
'LastRow1 = 50
'LastCol1 = 50
LastRow1 = .Cells.SpecialCells(xlCellTypeLastCell).Row
LastCol1 = .Cells.SpecialCells(xlCellTypeLastCell).Column
Rowtosave = 0 ' Initialise in case not found
For RowCheck = 1 To LastRow1
' I use: With .Cells(R, C)
' .Value = 5
' .NumberFormat = "0.00"
' .Font.Bold = True
' End With
' because I want to refer to .Cells(R, C) in several ways.
' I do not see the benefit of:
' With .Cells(R, C)
' .Value = 5
' End With
If .Cells(RowCheck, 1).Value = "Name" Then
Rowtosave = RowCheck
firstEG = 0 ' Initialise so you can test for
lastEG = 0 ' EG and IG not being found.
For EGCheck = 1 To LastCol1
If .Cells(Rowtosave, EGCheck).Value = "EG" Then
firstEG = EGCheck
' Without the Exit For, the search will continue and
' firstEG will identify the last EG not the first.
Exit For
End If
Next EGCheck
For IGCheck = 1 To LastCol1
If .Cells(Rowtosave, IGCheck).Value = "IG" Then
' I find the name lastEG confusing
lastEG = IGCheck - 1
End If
Next IGCheck
' If you really want the last IG on the row
' better to search backwards.
For IGCheck = LastCol1 To 1 Step -1
If .Cells(Rowtosave, IGCheck).Value = "IG" Then
lastEG = IGCheck - 1
Exit For
End If
Next IGCheck
End If
' I assume there is only one row with Name in column A
' so there is no point in searching further.
Exit For
Next RowCheck
If Rowtosave = 0 Then
Call MsgBox("Name row not found", vbOKOnly)
Exit Sub
End If
If lastEG = 0 Then
Call MsgBox("EG not found on Name row", vbOKOnly)
Exit Sub
End If
'Look for temp
totalvalue = 0
tempRow1 = 0 ' Initialise in case not found
tempcol = 0
For RowCheck = 1 To LastRow1
If .Cells(RowCheck, 1).Value = "temp" Then
For tempCheck = 1 To lastEG
tempRow1 = RowCheck
' I assume .Cells(tempRow1, tempCheck).Value is numeric.
' If so "0" has to be converted to numeric for every test.
' Better: If .Cells(tempRow1, tempCheck).Value = 0 Then
' I might use "With .Cells(tempRow1, tempCheck)" because you
' test its value then use the value. However, this code
' makes no sense to me.
If .Cells(tempRow1, tempCheck).Value = "0" Then
' Why are you saving tempcol?
tempcol = tempCheck
' Why are you selecting valuestoadd?
' You have not activated the sheet. You cannot select a
' a cell except within the active worksheet.
'valuestoadd = .Cells(tempRow1, tempcol).Select
' Why are you totalling all the cells with a value of zero.
totalvalue = totalvalue + .Cells(tempRow1, tempcol).Value
End If
Next tempCheck
' I assume there is only one temp row
Exit For
End If
Next RowCheck
' This loop appears to be searching for a empty cell
' in which to place totalvalue.
'Look for empty column
emptycol = 1
Do
' Why are you selecting the cell?
Set empty1 = .Cells(tempRow1, emptycol)
If empty1 = "" Then
' Why are you place the value in the cell after the empty one?
emptycol = emptycol + 1
' How will you use Colempty?
Colempty = emptycol
' You cannot set a range to a numeric.
'empty1 = totalvalue
' "empty1.Value = totalvalue" would be OK
.Cells(tempRow1, emptycol).Value = totalvalue
' I have moved the Exit Do to inside the If.
' Before "emptycol = emptycol + 1" could not be reached
Exit Do
End If
emptycol = emptycol + 1
Loop
End With
End Sub
New section in response to user1204868's questions and new explanation
I started answering your questions but I am unable to reconcile all the information you have given. The code, the explanation and the questions are not consistent. The following is my attempt to create a consistent description.
There is a row with "Name" in column A. Call this the Name row.
Within the Name row, there are cells containing "EG". A few cells after a cell containg "EG" is a cell containing "IG". Call an range starting at a cell containing "EG" and continuing to (but not including) the next cell containing "IG", an EG range. In the example, the EG ranges are B:D and H:J.
There is a row with "temp" in column A. Call this the Temp row. In the example, the Temp row is immediately under the Name row but apparently this is not a requirement. The Name and Temp rows can appear anywhere within the Sheet and in either sequence.
There is a row with "test1" in column A. Call this the Test1 row.
In the example, the Test1 Row is immediately under the Temp row. The code does not access the Test1 row. The explanation says that the row under the Temp row is the Test1 row but this is not checked.
Within each EG range within the Temp row, there is a cell containing zero. The cell below the zero will be empty or contain a number. The value of those numbers for all EG ranges is to be totalled. This total is to be saved in the cell after the first empty cell within the Temp row overwritten any existing value. In the example, the first empty cell in the Temp row is in column N so the total will be saved in column O overwriting the existing zero.
I doubt this explanation is completely correct. You should copy this explanation to your question and correct it so that Tim or I or someone else can understand what you really want.
Your question about the Name row not being row 1 suggests that you are concerned that as you develop the worksheet, the Name row may move down. You need to decide what about the worksheet is fixed and what may change. For example, must the Test1 row be immediately under the Temp row?
What are the rows with "test2" and "test3" in column A. Are they related to the Test1 row?
Comments on code from testing1.xlsm
Issue 1
You have removed my Option Explicit so you have undeclared variables.
Add:
Dim totalvalue1 As Long
Dim totalvalue2 As Long
Issue 2
.Cells(LastRow1, 1).Select
You can only select a cell within the active worksheet. We are accessing worksheet Sheet1 using With so it does not have to be active.
There is no need to select this cell so delete this statement.
Issue 3
End If
' I assume there is only one row with Name in column A
' so there is no point in searching further.
Exit For
Next RowCheck
As explained in my comment, this block must be replaced with:
Exit For
End If
Next RowCheck
Issue 4
For IGCheck = 1 To LastCol1
If .Cells(Rowtosave, IGCheck).Value = "IG" Then
lastEG = IGCheck - 1
Exit For
ElseIf .Cells(Rowtosave, IGCheck).Value = "EG" Then
lastEG = IGCheck - 1
'Exit For
ElseIf .Cells(Rowtosave, IGCheck).Value = "CG" Then
lastEG = IGCheck - 1
'Exit For
End If
Next IGCheck
I guess from this code and you explanations that you are searching the Name row for a range of columns which start with a cell containing "EG" and end one column before:
the next column with a cell containing "CG" or
the next column with a cell containing "EG" or
the next column with a cell containing "IG"
I also guess that you have commented out the Exit For statements because this code does not work.
Your problem is For IGCheck = 1 To LastCol1. You are starting the search at column 1 so you find the first "EG" again.
Replace this block of code with:
If firstEG = 0 Then
Call MsgBox("EG not found on Name row", vbOKOnly)
Exit Sub
End If
For IGCheck = firstEG + 1 To LastCol1
If .Cells(Rowtosave, IGCheck).Value = "IG" Or _
.Cells(Rowtosave, IGCheck).Value = "EG" Or _
.Cells(Rowtosave, IGCheck).Value = "CG" Then
lastEG = IGCheck - 1
Exit For
End If
Next IGCheck
Issue 5
If lastEG = 0 Then
Call MsgBox("EG not found on Name row", vbOKOnly)
Exit Sub
End If
The code discussed under the previous issue reveals that an EG range can be ended by the next EG. This suggests that the final EG range could be ended by the end of the used range.
Replace this block of code with:
If lastEG = 0 Then
lastEG = LastCol1 - 1
End If
Issue 6
For n = 1 To LastRow1
:
Next
The whole of the second block of code is surrounded by this For Loop. With your example data, this is the equivalent of For n = 1 to 5. Within this block of code, you look for the row (RowCheck=3) with column A containing the value "temp". You then access data from row RowCheck+n. That is, you acess rows 4 to 8. Is this really what you want to do?
Issue 7
For tempCheck = 1 To lastEG
I think this should be:
For tempCheck = firstEG To lastEG
Issue 8
The second block of code is a muddle. I can see what the code does but I do not undertand why so it is difficult for me to comment. But I will try.
The loop looking for the Temp row needs to be on the outside so something like:
For RowCheck = 1 To LastRow1
If .Cells(RowCheck, 1).Value = "temp" Then
' Set up new headers on Name row
' Process all the test rows
Exit For
End If
Next
The code places an "EG" on the row above the Temp row. This assumes the row above the Temp row is the Name row. Either:
The Temp row is always going to be the row below the Name row in which case you do not need to search for the Temp row or
you should place the "EG" on row Rowtosave.
Whichever, you only need to do it once.
You should not attempt to process the rows with "testN" in column A with For n = 1 To LastRow1. Choices include:
You have made LastRow1 two less than the true last row. Remove the - 2 from the statement initialising LastRow1. Replace For n = 1 To LastRow1 with For n = 1 To LastRow1 - tempRow1
Replace For n = 1 To LastRow1 ... Next with a Do Loop that continues until column A is empty or does not contain "testN" or whatever terminates this table.

Related

Excel / VBA - get first normal form (1NF) [duplicate]

I have values in column B separated by commas. I need to split them into new rows and keep the other data the same.
I have a variable number of rows.
I don't know how many values will be in the cells in Column B, so I need to loop over the array dynamically.
Example:
ColA ColB ColC ColD
Monday A,B,C Red Email
Output:
ColA ColB ColC ColD
Monday A Red Email
Monday B Red Email
Monday C Red Email
Have tried something like:
colArray = Split(ws.Cells(i, 2).Value, ", ")
For i = LBound(colArray) To UBound(colArray)
Rows.Insert(i)
Next i
Try this, you can easily adjust it to your actual sheet name and column to split.
Sub splitByColB()
Dim r As Range, i As Long, ar
Set r = Worksheets("Sheet1").Range("B999999").End(xlUp)
Do While r.row > 1
ar = Split(r.value, ",")
If UBound(ar) >= 0 Then r.value = ar(0)
For i = UBound(ar) To 1 Step -1
r.EntireRow.Copy
r.Offset(1).EntireRow.Insert
r.Offset(1).value = ar(i)
Next
Set r = r.Offset(-1)
Loop
End Sub
You can also just do it in place by using a Do loop instead of a For loop. The only real trick is to just manually update your row counter every time you insert a new row. The "static" columns that get copied are just a simple matter of caching the values and then writing them to the inserted rows:
Dim workingRow As Long
workingRow = 2
With ActiveSheet
Do While Not IsEmpty(.Cells(workingRow, 2).Value)
Dim values() As String
values = Split(.Cells(workingRow, 2).Value, ",")
If UBound(values) > 0 Then
Dim colA As Variant, colC As Variant, colD As Variant
colA = .Cells(workingRow, 1).Value
colC = .Cells(workingRow, 3).Value
colD = .Cells(workingRow, 4).Value
For i = LBound(values) To UBound(values)
If i > 0 Then
.Rows(workingRow).Insert xlDown
End If
.Cells(workingRow, 1).Value = colA
.Cells(workingRow, 2).Value = values(i)
.Cells(workingRow, 3).Value = colC
.Cells(workingRow, 4).Value = colD
workingRow = workingRow + 1
Next
Else
workingRow = workingRow + 1
End If
Loop
End With
This will do what you want.
Option Explicit
Const ANALYSIS_ROW As String = "B"
Const DATA_START_ROW As Long = 1
Sub ReplicateData()
Dim iRow As Long
Dim lastrow As Long
Dim ws As Worksheet
Dim iSplit() As String
Dim iIndex As Long
Dim iSize As Long
'Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
With ThisWorkbook
.Worksheets("Sheet4").Copy After:=.Worksheets("Sheet4")
Set ws = ActiveSheet
End With
With ws
lastrow = .Cells(.Rows.Count, ANALYSIS_ROW).End(xlUp).Row
End With
For iRow = lastrow To DATA_START_ROW Step -1
iSplit = Split(ws.Cells(iRow, ANALYSIS_ROW).Value2, ",")
iSize = UBound(iSplit) - LBound(iSplit) + 1
If iSize = 1 Then GoTo Continue
ws.Rows(iRow).Copy
ws.Rows(iRow).Resize(iSize - 1).Insert
For iIndex = LBound(iSplit) To UBound(iSplit)
ws.Cells(iRow, ANALYSIS_ROW).Offset(iIndex).Value2 = iSplit(iIndex)
Next iIndex
Continue:
Next iRow
Application.CutCopyMode = False
Application.Calculation = xlCalculationAutomatic
'Application.ScreenUpdating = True
End Sub
A formula solution is close to your requirement.
Cell G1 is the delimiter. In this case a comma.
Helper E1:=SUM(E1,LEN(B1)-LEN(SUBSTITUTE(B1,$H$1,"")))+1
You must fill the above formula one row more.
A8:=a1
Fill this formula to the right.
A9:=LOOKUP(ROW(1:1),$E:$E,A:A)&""
Fill this formula to the right and then down.
B9:=MID($H$1&LOOKUP(ROW(A1),E:E,B:B)&$H$1,FIND("艹",SUBSTITUTE($H$1&LOOKUP(ROW(A1),E:E,B:B)&$H$1,$H$1,"艹",ROW(A2)-LOOKUP(ROW(A1),E:E)))+1,FIND("艹",SUBSTITUTE($H$1&LOOKUP(ROW(A1),E:E,B:B)&$H$1,$H$1,"艹",ROW(A2)-LOOKUP(ROW(A1),E:E)+1))-FIND("艹",SUBSTITUTE($H$1&LOOKUP(ROW(A1),E:E,B:B)&$H$1,$H$1,"艹",ROW(A2)-LOOKUP(ROW(A1),E:E)))-1)&""
Fill down.
Bug:
Numbers will be converted to Text. Of course you can remove the &"" at the end of the formula, but blank cells will be filled with 0.
Given #A.S.H.'s excellent and brief answer, the VBA function below might be a bit of an overkill, but it will hopefully be of some help to someone looking for a more "generic" solution. This method makes sure not to modify the cells to the left, to the right, or above the table of data, in case the table does not start in A1 or in case there is other data on the sheet besides the table. It also avoids copying and inserting entire rows, and it allows you to specify a separator other than a comma.
This function happens to have similarities to #ryguy72's procedure, but it does not rely on the clipboard.
Function SplitRows(ByRef dataRng As Range, ByVal splitCol As Long, ByVal splitSep As String, _
Optional ByVal idCol As Long = 0) As Boolean
SplitRows = True
Dim oldUpd As Variant: oldUpd = Application.ScreenUpdating
Dim oldCal As Variant: oldCal = Application.Calculation
On Error GoTo err_sub
'Modify application settings for the sake of speed
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
'Get the current number of data rows
Dim rowCount As Long: rowCount = dataRng.Rows.Count
'If an ID column is specified, use it to determine where the table ends by finding the first row
' with no data in that column
If idCol > 0 Then
With dataRng
rowCount = .Offset(, idCol - 1).Resize(, 1).End(xlDown).Row - .Row + 1
End With
End If
Dim splitArr() As String
Dim splitLb As Long, splitUb As Long, splitI As Long
Dim editedRowRng As Range
'Loop through the data rows to split them as needed
Dim r As Long: r = 0
Do While r < rowCount
r = r + 1
'Split the string in the specified column
splitArr = Split(dataRng.Cells(r, splitCol).Value & "", splitSep)
splitLb = LBound(splitArr)
splitUb = UBound(splitArr)
'If the string was not split into more than 1 item, skip this row
If splitUb <= splitLb Then GoTo splitRows_Continue
'Replace the unsplit string with the first item from the split
Set editedRowRng = dataRng.Resize(1).Offset(r - 1)
editedRowRng.Cells(1, splitCol).Value = splitArr(splitLb)
'Create the new rows
For splitI = splitLb + 1 To splitUb
editedRowRng.Offset(1).Insert 'Add a new blank row
Set editedRowRng = editedRowRng.Offset(1) 'Move down to the next row
editedRowRng.Offset(-1).Copy Destination:=editedRowRng 'Copy the preceding row to the new row
editedRowRng.Cells(1, splitCol).Value = splitArr(splitI) 'Place the next item from the split string
'Account for the new row in the counters
r = r + 1
rowCount = rowCount + 1
Next
splitRows_Continue:
Loop
exit_sub:
On Error Resume Next
'Resize the original data range to reflect the new, full data range
If rowCount <> dataRng.Rows.Count Then Set dataRng = dataRng.Resize(rowCount)
'Restore the application settings
If Application.ScreenUpdating <> oldUpd Then Application.ScreenUpdating = oldUpd
If Application.Calculation <> oldCal Then Application.Calculation = oldCal
Exit Function
err_sub:
SplitRows = False
Resume exit_sub
End Function
Function input and output
To use the above function, you would specify
the range containing the rows of data (excluding the header)
the (relative) number of the column within the range with the string to split
the separator in the string to split
the optional (relative) number of the "ID" column within the range (if a number >=1 is provided, the first row with no data in this column will be taken as the last row of data)
The range object passed in the first argument will be modified by the function to reflect the range of all the new data rows (including all inserted rows). The function returns True if no errors were encountered, and False otherwise.
Examples
For the range illustrated in the original question, the call would look like this:
SplitRows Range("A2:C2"), 2, ","
If the same table started in F5 instead of A1, and if the data in column G (i.e. the data that would fall in column B if the table started in A1) was separated by Alt-Enters instead of commas, the call would look like this:
SplitRows Range("F6:H6"), 2, vbLf
If the table contained the row header plus 10 rows of data (instead of 1), and if it started in F5 again, the call would look like this:
SplitRows Range("F6:H15"), 2, vbLf
If there was no certainty about the number of rows, but we knew that all the valid rows are contiguous and always have a value in column H (i.e. the 3rd column in the range), the call could look something like this:
SplitRows Range("F6:H1048576"), 2, vbLf, 3
In Excel 95 or lower, you would have to change "1048576" to "16384", and in Excel 97-2003, to "65536".

Delete missing data from a set of 3 columns in Excel

I have a dataset that includes 9 columns. I want to check each row to see if the last 3 columns are empty. If all 3 are empty, I want to delete the row. I'm currently trying to do this in VBA, but I'm a programming newb and find myself completely overwhelmed.
The pseudocode that I've written is as follows:
For Row i
If(Col 1 & Col 2 & Col 3) = blank
Then delete Row i
Move on to next Row
I'd go like follows
Dim iArea As Long
With Range("E:G") '<--| change "E:G" to your actual last three columns indexes
If WorksheetFunction.CountBlank(.Cells) < 3 Then Exit Sub
With .SpecialCells(xlCellTypeBlanks)
For iArea = .Areas.Count To 1 Step -1
If .Areas(iArea).Count Mod 3 = 0 Then .Areas(iArea).EntireRow.Delete
Next
End With
End With
Assuming you have at least one row that is always filled out, you can use the following:
Dim LR as Long
Dim i as Integer
LR = Cells(Sheets("REF").Rows.Count,1).End(xlUp).Row
For i = 1 to 9
If Range(Cells(LR-3,i),(Cells(LR,i)).Value="" Then
Columns(i).Delete
Else:
End If
Next i
This works by defining the last row as LR, and defining a variable as i. You will check column "i" to determine if the last 3 rows of the column are "", aka it's blank; one might try to use ISBLANK(), but that cannot work for an array. If this is true, then you will delete the column, i. The code will then move to the next i. The FOR LOOP using i starts at 1 and goes to 9, which corresponds to starting at column 1 (A) and ending at column 9 (I).
Edit:
I appear to have misread which was supposed to be empty and which is supposed to be deleted, in terms of columns/rows... this code would be re-written as:
Dim LR as Long
Dim i as Integer
LR = Cells(Sheets("REF").Rows.Count,1).End(xlUp).Row
For i = LR to 2 Step -1 'Assumes you have headers in Row1
If AND(ISBLANK(Cells(i,7)),ISBLANK(Cells(i,8)),ISBLANK(Cells(i,9)) Then
Rows(i).Delete
End If
Next i
Significant changes are checking is each of the 3 last columns in the row are empty, ISBLANK(), changing that a row gets deleted if the condition is met, and changing what to loop through.
Here's another answer, assuming your last three column starts on "G","H","I".
Sub DeleteRowWithLastThreeColumnsBlank()
Dim N As Long, i As Long
N = Cells(Rows.Count, "A").End(xlUp).Row
For i = 1 To N
If Cells(i, "G").Value = "" And Cells(i, "H").Value = "" And Cells(i, "I").Value = "" Then
Rows(i).EntireRow.Delete
N = Cells(Rows.Count, "A").End(xlUp).Row
End If
Next i
End Sub

Loop through Column restarting when value changes

I've found a couple threads with similar titles but weren't really what I am looking to do. What I'm trying to do is go through the list of numbers in Col A, and calculate the time difference using NetworkDays for the first instance the number appears in Col B ' Received On ' and the last instance the number appears in Col C ' Processed On '. After the NetworkDays calculation is done I'd like to put that value repeating in Col D on every respective row. The number of times a value will appear in Col A constantly varies, and Col A itself is several thousand lines long and constantly growing. Once that is done I need to loop through all the other different sets of numbers in Col A and repeat the process. As an example, ***39430 first appears on Row 2 and last appears on Row 7. Using Networkdays(B2,C7) gives 11 days, and so forth. After that move onto ***39383. Sample below.
Sample data
Below is the code I have so far. From the sample above I have to put a blank row under ***39430 in order to get the code to work, otherwise it just continues on to the bottom of the list and calculates that difference (not what I want obviously). What I'm stumped on is how to tell the loop to restart whenever the value changes in Col A and then continue on. I suspect it might be something close to Do Until ActiveCell.Value <> Activecell.Offset(-1,0).Value but I can't quite figure it out. Also how to get the Networkdays value to repeat on every respective row.
Dim counter As Integer
Dim CycleTime As Long
counter = 0
Do Until ActiveCell.Value = ""
counter = counter + 1
ActiveCell.Offset(1, 0).Select
Loop
'Gives the number of rows to offset
MsgBox counter
'Shows the correct number of days difference
MsgBox WorksheetFunction.NetworkDays(Range("B2"), Range("B2").Offset(counter - 1, 1))
CycleTime = WorksheetFunction.NetworkDays(Range("B2"), Range("B2").Offset(counter - 1, 1))
Range("D2").Value = CycleTime
Any help would be greatly appreciated. Thanks in advance.
Update
After using the code provided for a couple of weeks I've noticed a complication that I had not thought of before. Previously, I had thought that there was always only one output doc for each input doc (not considered in scope of original question), however as shown in Sample-New image in the top box there can be more than one output doc per input doc. For the new screenshot below I've included two additional columns, Col. C 'Output Doc #' and Col. D 'Output Doc Created On'. What I'd like to be able to do, amending the code that #YowE3K provided below, is to nest another loop that goes through Col. D 'Output Doc #' and uses NetworkDays to calculate the difference from B1 and D1 for the first group, and then B1 and D8 for the second group. As it is now, the code isn't written to handle the change and calculates everything as shown in Column F, with the ideal code resulting in Column G. The second box (in dark blue) shows a typical example where the code performs perfectly. Loops are something I'm struggling with to understand and not really sure how to even take a stab at this. Any comments to the code in a response would be very helpful. Thanks in advance.
Sample - New
The following code loops using endRow as the loop "counter".
startRow is set to the row containing the start of the current "Doc Number", and endRow is incremented until it is pointing at the last row for that "Doc Number".
Once endRow is pointing at the correct place, CycleTime is calculated and written to column D of each row from startRow to endRow. startRow is then set to point to the beginning of the next "Doc Number".
The loop ends when a blank cell is found in column A.
Sub Calc()
Dim startRow As Long
Dim endRow As Long
Dim CycleTime As Long
startRow = 2
endRow = 2
Do
If Cells(startRow, "A").Value <> Cells(endRow + 1, "A").Value Then
CycleTime = WorksheetFunction.NetworkDays(Cells(startRow, "B"), Cells(endRow, "C"))
Range(Cells(startRow, "D"), Cells(endRow, "D")).Value = CycleTime
startRow = endRow + 1
End If
endRow = endRow + 1
If Cells(endRow, "A").Value = "" Then
Exit Do
End If
Loop
End Sub
Edited to keep track of the first and last "Approved" record, and only update column D if one is found:
Sub Calc()
Dim startRow As Long 'Start of the Doc Number
Dim firstRow As Long 'First "approved" row
Dim lastRow As Long 'Last "approved" row
Dim endRow As Long 'End of the Doc Number
Dim CycleTime As Long
startRow = 2
endRow = 2
firstRow = -1
lastRow = -1
Do
If Cells(endRow, "Q").Value = "Approved" Then
'Found an "Approved" record
'Set the first row if not already set
If firstRow = -1 Then
firstRow = endRow
End If
'Set the last row (will replace this if we find another record)
lastRow = endRow
End If
If Cells(startRow, "A").Value <> Cells(endRow + 1, "A").Value Then
If firstRow > 0 Then ' (If it is -1 then we never found an "Approved" record)
CycleTime = WorksheetFunction.NetworkDays(Cells(firstRow, "B"), Cells(lastRow, "C"))
Range(Cells(startRow, "D"), Cells(endRow, "D")).Value = CycleTime
End If
'Set up for next Doc Number
startRow = endRow + 1
firstRow = -1
lastRow = -1
End If
'Go to next row
endRow = endRow + 1
'Exit when we hit a blank Doc Number
If Cells(currentRow, "A").Value = "" Then
Exit Do
End If
Loop
End Sub

Referencing a particular cell value when there are two string matches in VBA

I am trying to create a predictive algorithm in VBA that would search for strings in a particular row from a data source, and return a value based on the row number. This is the first step in the workflow, and in its simplest form, there are 2 tables as shown below:
Source Table:
Output Table:
This is what I'm trying to do:
Pick up the strings in Row 1 of Output Table (Blue,Black) and search for them in Rows 1,2,3,4 of Source Table.
If both strings match in a single row, the 'Input' cell from that particular row is copied to Row 1 in Output Table in the 'Output' column.
Example (2nd iteration):
From Output Table Row 2, strings Ivory,Green,Grey are picked up and queried in all rows of Source Table. If any 2 out of 3 strings match in a single row on Source Table, the Input cell of that row is copied.
In this case, Ivory and Green match in Row 1, and also in Row 4. Either input cell would work, but for the sake of having a rule, lets take the last match (Row 4). So '1,8' would be copied to Row 2 on Output Table.
This the flow I am currently using, but I'm getting an incorrect output:
For i = 2 To 5
For j = 1 To 4
For k = 2 To 5
For l = 1 To 5
If Cells(i, j).Value = Worksheets("SourceTable").Cells(k, l).Value And Cells(i,j).Value <> "" Then
For a = 1 To 5
For b = 1 To 4
If Cells(i, b).Value = Worksheets("SourceTable").Cells(k, a).Value And Cells(i, b).Value <> "" Then
Cells(i, 15).Value = Worksheets("SourceTable").Cells(k, 5).Value
GoTo iLoop
End If
Next b
Next a
End If
Next l
Next k
Next j
iLoop:
Next i
Both tables would have around half a million rows, and I am trying to figure out how to reduce the number of loops and make it work at the same time. Any suggestions would be appreciated, this would help me save a lot of man-hours and automate a major chunk of the process. Thanks!
Sub macro()
lastRowOut = Sheets("OutputTable").Range("A" & Rows.Count).End(xlUp).Row
lastRowSou = Sheets("SourceTable").Range("A" & Rows.Count).End(xlUp).Row
For i = 2 To lastRowOut
For j = 2 To lastRowSou
If checkRow(j, i) >= 2 Then
Sheets("OutputTable").Cells(i, 5) = Sheets("SourceTable").Cells(j, 6)
Exit For
End If
Next j
Next i
End Sub
Function checkRow(sRow, i)
lastCol = Split(Sheets("OutputTable").Cells(i, Columns.Count).End(xlToLeft).Address, "$")(1)
counter = 0
For Each cell In Sheets("OutputTable").Range("A" & i & ":" & lastCol & i)
If Not Sheets("SourceTable").Range("A" & sRow & ":" & "E" & sRow).Find(cell.Value) Is Nothing Then
counter = counter + 1
End If
Next cell
checkRow = counter
End Function
Quite a few things are unclear so here were the assumptions I made:
Two or more of the cells in a row in the OutputTable have to be matched for the prediction to be made.
The first rows of both the Output and Source sheet contain "Col1, Col2" etc.
You seem to not mind whether we use the first or last matching row (from the source sheet) so I went with the first.
That's 3 loops instead of 6..
you can try this
Option Explicit
Sub main()
Dim row As Range
With Worksheets("OutputTable")
For Each row In .Range("D2", .Cells(.Rows.count, 1).End(xlUp)).Rows '<--| change "D" to "OutputTable" sheet last "col" column index (i.e. the one before "Output" column)
SearchSource row
Next
End With
End Sub
Sub SearchSource(rng As Range)
Dim cell As Range, row As Range
Dim nFounds As Long
With Worksheets("SourceTable")
For Each row In .Range("E2", .Cells(.Rows.count, 1).End(xlUp)).Rows '<--| change "E" to "SourceTable" sheet last "col" column index (i.e. the one before "Input" column)
nFounds = 0
For Each cell In rng.SpecialCells(xlCellTypeConstants)
If Not row.Find(what:=cell.Value, lookat:=xlWhole, LookIn:=xlValues) Is Nothing Then nFounds = nFounds + 1
If nFounds = 2 Then Exit For
Next
If nFounds = 2 Then rng.Cells(, rng.Columns.count + 1).Value = row.Cells(, row.Columns.count + 1).Value
Next
End With
End Sub
'Try this:
'First declare some variables:
'the number of rows of the Output table
Dim OrNum as integer
'the number of columns of the Output table
Dim OcNum as integer
'the number of rows of the Source table
Dim SrNum as integer
'the number of columns of the Source table
Dim ScNum as integer
'some dummy variables for the loops
Dim rO as integer, cO as integer
Dim rS as integer, cS as integer
And then declare a boolean variable (just for later on)
Dim bool as boolean
'Then assume the output table has it's first cell at the most 'top and the most left of the output table, which is taken to 'be the cell Z1 in the following Code
'Begin with this first cell of the Output table and get each 'value in a way, that you move first (inner loop) over the 'columns by fixing the row Index (rO) of the Output table and then (outer loop) get down to each and every row like this:
For rO = 0 to OrNum - 1
For cO = 0 to OcNum - 1
Range("Z1").Offset(rO, cO)
Next
Next
'Now you don't have only strings so you will need to check, 'if the value in the cell is a string or a number. There is VBA 'function, that can help. It's called IsNumeric. It will give 'True if the value is a numeric value. If we have a string, then it will give False. With the Function IsEmpty() you can also check if a cell is empty or not. If a cell is empty, then the function IsEmpty will return True.
For rO = 0 to OrNum - 1
For cO = 0 to OcNum - 1
bool = IsNumeric(Range("Z1").Offset(rO, cO).Value)
bool = bool Or IsEmpty (Range("Z1").Offset(rO, cO).Value)
If bool=False then
'we have a string!
'do something
End if
Next
Next

Why do my VBA code sometimes work and most of the times it doesn't?

Sub UpdateCSAH()
Dim S As String
Dim R As Long
Dim RR As Long
Dim CC As Long
Dim i As Long
Dim j As Long
Dim csah() As String 'an array that stores the CSAH sites
ReDim csah(1 To 100, 1 To 7)
Dim Ran As Range
Dim Ran1 As Range
Set Ran = Worksheets("Current Sites").Range("A1").CurrentRegion 'Ran is the region that has values
RR = 1 'row number in csah
CC = 1 'column number in csah
'check each value in Ran to see if its Route section has "CSAH"
For Each cell In Ran
R = cell.row
S = CStr(Cells(R, 4).value)
If InStr(S, "CSAH") > 0 Then 'check if "CSAH" is in the Route section
If CC > 7 Then 'reset the column number and go to the next row when reach the end of the column
CC = 1
RR = RR + 1
End If
csah(RR, CC) = cell.value
CC = CC + 1
End If
Next cell
Worksheets("CSAH Sites").Select
Range("A2:G100").Select
Selection.ClearContents
'assign each array values to cells in sheet"CSAH Sites"
i = 1
j = 1
For i = 1 To UBound(csah, 1)
For j = 1 To UBound(csah, 2)
Cells(i + 1, j) = csah(i, j)
Next j
Next i
'format the CSAH Sites values
Set Ran1 = Worksheets("CSAH Sites").Range("A1").CurrentRegion
For Each cell In Ran1
If cell.row = 1 Then
With cell.Font
.Color = -11489280
End With
ElseIf cell.row Mod 2 = 0 Then
With cell.Interior
.Color = 10092441
End With
End If
Next cell
End Sub
I have an Excel worksheet named "Current Sites" that has some data. If the 4th column has the word "CSAH", I want to store the values of that row into an array and assign those values to cells in the worksheet named "CSAH Sites". My code sometimes works (the 1st time you click), and most of times it doesn't work or doesn't work properly.
Please help me out! Thanks A Bunch!!
It looks like you want to check every row of data in the "Current Sites" sheet and if column 4 includes the "CSAH" text, then write the first 7 columns of data for that entry to the "CSAH Sites" sheet and add some colour to the even-numbered rows.
To check every row of data, you can read down just one column and use either the Offset or the Cells method to see the values of neighbouring cells. In your code you were "touching" every cell and each time you were then looking at the value in column 4 and also checking to see if the code had gone past column 7. That slows things down a lot and makes the code hard to understand.
You can also assign the values from a range of cells directly to another range of cells without using variables or an array.
See if this does what you want:
Sub UpdateCSAH()
Dim currentSitesRange As Range
Dim thisSiteRange As Range
Dim outputCell As Range
Dim numRowsOfData As Long
Const NUM_COLUMNS_OF_DATA As Integer = 7
Set currentSitesRange = Worksheets("Current Sites").Range("A1")
numRowsOfData = currentSitesRange.CurrentRegion.Rows.Count
Set currentSitesRange = currentSitesRange.Resize(RowSize:=numRowsOfData) 'currentSitesRange is the region that has values
Worksheets("CSAH Sites").Range("A2:G100").ClearContents
Set outputCell = Worksheets("CSAH Sites").Range("A2")
For Each thisSiteRange In currentSitesRange.Cells
' Look for "CSAH" in the Route section (column D)
If InStr(1, thisSiteRange.Offset(ColumnOffset:=3).Value, "CSAH", vbTextCompare) > 0 Then
' Found "CSAH" so write NUM_COLUMNS_OF_DATA columns of data to CSAH Sites sheet
outputCell.Resize(ColumnSize:=NUM_COLUMNS_OF_DATA).Value = thisSiteRange.Resize(ColumnSize:=NUM_COLUMNS_OF_DATA).Value
' Format the even-numbered rows
If outputCell.Row Mod 2 = 0 Then
With outputCell.Resize(ColumnSize:=NUM_COLUMNS_OF_DATA).Interior
.Color = 10092441
End With
End If
Set outputCell = outputCell.Offset(RowOffset:=1)
End If
Next thisSiteRange
End Sub