Delete Rows if Cell Does not equal to Zero or Blank - excel

I have researched several codes but it's either for blanks only or zeroes only, and I need a code for both blanks and zeroes.
I have 3 columns to note if this should be deleted or not
I need to delete the rows with complete details(ID, and Address)(the Name is the basis for the details), since I need the rows with incomplete details(ID or Address as zeroes or blanks) to retain.
ID Name Address
1 A 123 ABC
2 B 0
C 345 CDE
D
5 E 567 EFG
0 F 678 FGH
7 G 789 GHI
0 H 0
My first try was this code, it works for the conditions, but if I have succeeding blanks, it skips the next row, since that row goes up
lrow = 1000
For x = 2 To lrow
If Cells(x,2)<>"" Then
If Cells(x,1) <> "" Or Cells(x,1) <> "0" Or Cells(x,3) <> "" Or Cells(x,3) <> "0" Then
Cells(x,11).EntireRow.Delete
End If
End If
Next x
So I tried this code, where I start from bottom to up.
lrow = 1000
For x = lrow To 2 Step -1
If Cells(x,2)<>"" Then
If Cells(x,1) <> "" Or Cells(x,1) <> "0" Or Cells(x,3) <> "" Or Cells(x,3) <> "0" Then
Cells(x,11).EntireRow.Delete
End If
End If
Next x
But that code ignores the conditions except the first one, then also deletes the other row s with incomplete details.
I'm kind of stuck with this, since I also have to create another one where I do the reverse, keep the complete details, and delete the incomplete ones.

Replace the for loop with a do while loop. If the row is deleted, decrement the total number of rows, otherwise increment the row counter.
lastRow = 1000
row = 2
Do While row <= lastRow
If Cells(row,1)<>"" Then
If Cells(row,1) <> "" Or Cells(row,1) <> "0" Or Cells(row,3) <> "" Or Cells(row,3) <> "0" Then
Rows(row).Delete
lastRow = lastRow - 1
else
row = row + 1
End If
End If
Loop

Delete Rows with Conditions
Loop Backward
Sub testSimple()
Const lrow As Long = 1000
Dim x As Long
For x = lrow To 2 Step -1
If Len(Cells(x, 1)) > 0 And Cells(x, 1) <> 0 Then
If Len(Cells(x, 3)) > 0 And Cells(x, 3) <> 0 Then
Rows(x).Delete
End If
End If
Next x
End Sub
EDIT:
The star of the show is the If statement which should ideally (most efficiently) actually be:
If Len(Cells(x, 1)) > 0 Then
If Cells(x, 1) <> 0 Then
If Len(Cells(x, 3)) > 0
If Cells(x, 3) <> 0 Then
Rows(x).Delete
End If
End If
End If
End If
All four conditions have to be true. If one isn't, the others are not evaluated.
On the other hand you can write it like this
If Len(Cells(x, 1)) > 0 And Cells(x, 1) <> 0 _
And Len(Cells(x, 3)) > 0 And Cells(x, 3) <> 0 Then
Rows(x).Delete
End If
... the difference being that in the latter (less efficient) all four conditions are evaluated, even if the first is already false.
For the opposite you could use the same conditions and do the following (note the Else):
If Len(Cells(x, 1)) > 0 And Cells(x, 1) <> 0 _
And Len(Cells(x, 3)) > 0 And Cells(x, 3) <> 0 Then
' Do nothing
Else
Rows(x).Delete
End If
Let's rewrite the opposite using Or:
If Len(Cells(x, 1)) = 0 Or Cells(x, 1) = 0 _
Or Len(Cells(x, 3)) = 0 Or Cells(x, 3) = 0 Then
Rows(x).Delete
End If
So similar to the 'opposite idea' you could write the initial statement like this (note the Else):
If Len(Cells(x, 1)) = 0 Or Cells(x, 1) = 0 _
Or Len(Cells(x, 3)) = 0 Or Cells(x, 3) = 0 Then
' Do nothing
Else
Rows(x).Delete
End If
The Finale (for the opposite)
Using the Select Case statement you can write the opposite like this:
Sub testSimple()
Const lrow As Long = 1000
Dim x As Long
For x = lrow To 2 Step -1
Select Case True
Case Len(Cells(x, 1)) = 0, Cells(x, 1) = 0, _
Len(Cells(x, 3)) = 0, Cells(x, 3) = 0
Rows(x).Delete
End Select
Next x
End Sub
... where the commas 'mean Or', so if any of the expressions are true, the rows will be deleted.
OLD (Continuation):
Delete in One Go Using the CombinedRange Function
Sub test()
Const lrow As Long = 1000
Dim drg As Range
Dim x As Long
For x = 2 To lrow
If Len(Cells(x, 1)) > 0 And Cells(x, 1) <> 0 Then
If Len(Cells(x, 3)) > 0 And Cells(x, 3) <> 0 Then
Set drg = CombinedRange(drg, Rows(x))
End If
End If
Next x
If Not drg Is Nothing Then
drg.Delete
End If
End Sub
Delete in One Go Using the CombinedRange Function Improved
Sub testImp()
Const Cols As String = "A:C"
Const fRow As Long = 2
Dim rg As Range
With Columns(Cols).Rows(fRow)
Set rg = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If rg Is Nothing Then Exit Sub
Set rg = .Resize(rg.Row - .Row + 1)
End With
Dim drg As Range
Dim rrg As Range
For Each rrg In rg.Rows
If Len(rrg.Cells(1)) > 0 And rrg.Cells(1) <> 0 Then
If Len(rrg.Cells(3)) > 0 And rrg.Cells(3) <> 0 Then
Set drg = CombinedRange(drg, rrg.EntireRow)
End If
End If
Next rrg
If Not drg Is Nothing Then
drg.Delete
End If
End Sub
The CombinedRange Function
Function CombinedRange( _
ByVal BuildRange As Range, _
ByVal AddRange As Range) _
As Range
If BuildRange Is Nothing Then
Set CombinedRange = AddRange
Else
Set CombinedRange = Union(BuildRange, AddRange)
End If
End Function

Related

VBA Issue with If/And/Then/Else statement using ActiveCell..Offset(-1,0)=

I would like to select a cell in column C and check if the two cells above it are equal to 0. If they are equal to 0, I would like the ActiveCell to equal 1 Else I would like the ActiveCell to equal 0. I would then like to select the cell that is down 3 from the initial cell and repeat the process. I would like to do this 773 times. The issue I'm having is with the IF/AND section, it is always selecting 0 even when it should select a 1. Any idea what I did wrong. Working in an excel file that was converted from a CSV.
Range("C4").Select
For i = 1 To 773
If ActiveCell.Offset(-1, 0).Value = “0” And ActiveCell.Offset(-2, 0).Value = “0” Then
ActiveCell = "1" Else
ActiveCell = "0"
ActiveCell.Offset(3, 0).Select
Next i
It is best to avoid Activate and Select. Also, you can step the increment without having to add to the selection.
Sub test()
Dim sht As Workbook
Set sht = ActiveWorkbook 'or actual sheet name
For i = 4 To 773 Step 3
If sht.Cells(i - 1, 4).Value = 0 And sht.Cells(i - 2, 4).Value = 0 Then
sht.Cells(i, 4) = 1
Else
sht.Cells(i, 4) = 0
End If
Next i
End Sub
Testing a Range
The unexpected result (zeros) is due to the use of "0" which cannot be found.
Option Explicit
' Not recommended. Note how slow it is compared to the other solutions.
' The trick is in avoiding using "Select" and any 'flavor' of "Active".
' Runtime: 2100ms (over 2 seconds)
Sub testQuickFix()
Range("C4").Select
Dim i As Long
For i = 1 To 773
' This has to be one line: note the line separators ('_').
If ActiveCell.Offset(-1, 0).Value = 0 _
And ActiveCell.Offset(-2, 0).Value = 0 Then _
ActiveCell = 1 Else _
ActiveCell = 0
ActiveCell.Offset(3, 0).Select
Next i
End Sub
' Highly recommended, but maybe too advanced (You should learn about arrays).
' Note that this is only useful if the data are values, not formulas, because
' the whole range is overwritten.
' Runtime: 5ms
Sub testArray()
Dim rg As Range: Set rg = Range("C2:C2320")
Dim Data As Variant: Data = rg.Value
Dim i As Long
For i = 1 To UBound(Data, 1) Step 3
If Data(i, 1) = 0 And Data(i + 1, 1) = 0 Then
Data(i + 2, 1) = 1
Else
Data(i + 2, 1) = 0
End If
Next i
rg.Value = Data
End Sub
' Recommended.
' Runtime: 80ms
Sub testIfThenElseEndIf()
Dim rg As Range: Set rg = Range("C2:C4")
Dim i As Long
For i = 1 To 773
' This is considered more readable and is mostly used on SO.
If rg(1).Value = 0 And rg(2).Value = 0 Then
rg(3).Value = 1
Else
rg(3).Value = 0
End If
' It is short for:
' If rg.Cells(1).Value = 0 And rg.Cells(2).Value = 0 Then
' rg.Cells(3).Value = 1
' Else
' rg.Cells(3).Value = 0
' End If
' which I actually prefer.
Set rg = rg.Offset(3)
Next i
End Sub
' Not recommended.
' Runtime: 80ms
Sub testIfThenElse()
Dim rg As Range: Set rg = Range("C2:C4")
Dim i As Long
For i = 1 To 773
' This is valid, but rarely seen on SO.
' Note that this is one line.
'If rg(1).Value = 0 And rg(2).Value = 0 Then rg(3).Value = 1 Else rg(3).Value = 0
' Note that this is also one line, but uses a line separator.
If rg(1).Value = 0 And rg(2).Value = 0 Then rg(3).Value = 1 _
Else rg(3).Value = 0
Set rg = rg.Offset(3)
Next i
End Sub
' (A little less) recommended.
' Runtime: 80ms
Sub testIIF()
Dim rg As Range: Set rg = Range("C2:C4")
Dim i As Long
For i = 1 To 773
' This is another way.
rg(3).Value = IIf(rg(1).Value = 0 And rg(2).Value = 0, 1, 0)
Set rg = rg.Offset(3)
Next i
End Sub
I believe this would work for your instance.
Public Sub ReadingCells()
With Sheet1
Range("C4").Select
For i = 1 To 773
If ActiveCell.Offset(-1, 0).Value + ActiveCell.Offset(-2, 0).Value = "0" Then
ActiveCell.Value = "1"
Else
ActiveCell.Value = "0"
End If
ActiveCell.Offset(3, 0).Select
Next i
End With
End Sub

Find text between two identical characters and change its font color

The format of the text I'm dealing with looks like this:
|John| bought an |apple|.
The goal is to find all the text between "|"(like "John" and "apple"), change its color then delete both "|".
My current code is supposed to find the first and second instances of "|", go through each character between the two positions then change its font color, deleting both "|" and loop to do the whole thing again until no "|" can be found.
My problem is it often delete and color the wrong characters. I suspect it has something to do with character positions, but I don't know where.
Relevant code looks like this:
Dim Cell As Range
Dim iChr As Integer, N As Integer, Content As Integer
Dim openPos As Long, Dim clsPos As Long
Dim textBetween As String
For Each Cell In ws.UsedRange' relevant code is going to loop through each cell of each sheet
openPos = 0
N = 1
iChr = InStr(1, Cell.Value, "|")
Do Until iChr = 0 'Loop until no "|"
openPos = InStr(openPos + N, Cell, "|", vbTextCompare) 'first "|"
clsPos = InStr(openPos + 1 + N, Cell, "|", vbTextCompare) 'second "|"
For Content = openPos To clsPos
Cell.Characters(Content, 1).Font.Color = RGB(0, 255, 0)
Next Content
N = N + 1
Cell.Characters(clsPos, 1).Delete 'delete first and second"|"
Cell.Characters(openPos, 1).Delete
iChr = InStr(1, Cell.Value, "^") 'check if there is any "|" left
Loop
Next Cell
Please try this code.
Sub FindColorAndRemove()
' 016
Const Marker As String = "|" ' change to suit
Dim Ws As Worksheet
Dim Fnd As Range, FirstFound As String
Dim Sp() As String
Dim n As Integer
Dim i As Integer
For Each Ws In ActiveWorkbook.Worksheets
' enumerate exclusions here
If Ws.CodeName <> Sheet1.CodeName Then
Set Fnd = Ws.Cells.Find(What:=Marker & "*" & Marker, _
After:=Ws.Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchDirection:=xlNext)
If Not Fnd Is Nothing Then
FirstFound = Fnd.Address
Do
With Fnd
Sp = Split(.Value, Marker)
n = 0
.Value = Join(Sp, "")
For i = 0 To UBound(Sp) - 1
If i Mod 2 Then
With .Characters(n + 1, Len(Sp(i)))
.Font.Color = vbRed
.Font.Bold = True
End With
End If
n = n + Len(Sp(i))
Next i
End With
Set Fnd = Ws.Cells.FindNext
If Fnd Is Nothing Then Exit Do
Loop While Fnd.Address <> FirstFound
End If
End If
Next Ws
End Sub
Please pay attention to this line of code, If Ws.CodeName <> Sheet1.CodeName Then. I added it because I didn't want all sheets to be included. You can use the worksheets' tab name or code name. I recommend the CodeName because the user is less likely to change it. If you don't need the feature you can use some irrelevant criterium or delete the entire IF statement, including its End If.
Here is another approach using Collection
Sub Find_Location()
Dim iChr, StartChar, CharLen, i, j, k, m, n As Integer
Dim Ws As Worksheet
Set Ws = ActiveSheet
Dim Occurrence As Collection
For Each Cell In Ws.UsedRange
Set Occurrence = New Collection
i = Len(Cell.Text)
If i = 0 Then GoTo EndOfForLoop
j = 1
k = 0
Do Until j > i
iChr = InStr(j, Cell.Value, "|")
If iChr = 1 Then
k = k + 1
Occurrence.Add iChr
ElseIf iChr > 1 Then
k = k + 1
If Occurrence.Count = 0 Then
Occurrence.Add iChr
ElseIf Occurrence.Count > 0 Then
If (k / 2) = Int(k / 2) Then
Occurrence.Add (iChr - k)
ElseIf (k / 2) <> Int(k / 2) Then
Occurrence.Add (iChr - Occurrence.Count)
End If
End If
ElseIf iChr = 0 Then
If k = 0 Then
GoTo EndOfForLoop
Else
GoTo ModifyContent
End If
End If
j = 1 + iChr
Loop
ModifyContent:
With Cell
.Replace "|", ""
End With
m = 1
n = 2
Do Until n > k
StartChar = Occurrence.Item(m)
CharLen = (Occurrence.Item(n) - Occurrence.Item(m) + 1)
With Cell.Characters(StartChar, CharLen)
.Font.Color = RGB(0, 255, 0)
.Font.Bold = True
End With
m = m + 2
n = n + 2
Loop
EndOfForLoop:
Next
End Sub

Architecture to grab range

My code mostly works but it's taking a while to debug so I am beginning to think my architecture may be flawed XD So how can I architect this better?
I have groups of data separated by a blank row. You can tell each group apart by the ID in column C in addition to the blank row. For each ID, I have various numbers in column B that I need to capture. Sometimes those numbers only start with 5, sometimes it starts with 7. I need to capture the 5 and the 7 separately.
With projWS
With .Range("C1:C6000")
Set f = .Find(cc, LookIn:=xlValues, lookat:=xlPart)
End With
If Not f Is Nothing Then 'first occurence found
counter = 0
i = f.Row
Do
acct = .Cells(i, 2)
If (Len(projWS.Cells(i, 3)) < 1 Or Left(acct, 1) = "7") And done = False Then
acctStart = f.Row
acctRows = i - acctStart
Set acctRng = .Range(.Cells(acctStart, 2), .Cells(i - 1, 5))
Set amountRng = .Range(.Cells(acctStart, 7), .Cells(i - 1, 8))
done = True 'set flag to show range has been filled
End If
counter = counter + 1 'increment counter
i = i + 1 'move to next row
Loop Until Len(.Cells(i, 3)) < 1 'keep looping until blank row
End If
If counter - 1 > acctRows Then 'how we determine if there's a "7"
flag = True 'so we set flag to true
Set depreRng = Range(.Cells(acctStart + acctRows, 2), .Cells(i - 1, 8))
dep = depreRng.Value2 'store range into array
End If
End With
After capture, I need to drop it into another worksheet. This worksheet already has a block of 7 built in. Hence this is the loop I am using to drop the range of 7. There is no built in block for the 5.
For r = 112 To 120
For k = 1 To UBound(dep())
If .Cells(r, 1).Value2 = Trim(dep(k, 1)) Then
Debug.Print .Cells(r, 1).Value2
.Cells(r, 6) = dep(k, 6)
.Cells(r, 7) = dep(k, 7)
Exit For
Else
.Cells(r, 6) = 0
.Cells(r, 7) = 0
End If
Next k
Next r
I have debugged several errors already. The current one is that depreRng is breaking because my math is bad. Instead of debugging each error as I stumble onto it, how can I architect this better?
Ok, my approach it's different. First i use a filter for find the range of rows with the index you are looking for and then loop inside this filtered rows for find the 5xx and the 7xx range. The code:
Sub Macro1()
Dim rng_5xx_start, rng_5xx_stop, rng_7xx_start, rng_7xx_stop As Integer
rng_5xx_start = 0
rng_5xx_stop = 0
rng_7xx_start = 0
rng_7xx_stop = 0
Dim range_5xx, range_7xx As String
'filter for the index you are looking for
'specify the maximum range, the field is the "offset" from the column B (the firts of the range), so for filter for column C you need to put 2, criteria...is the critera :)
ActiveSheet.Range("$B$1:$H$6000").AutoFilter Field:=2, Criteria1:="b"
'the filter returns only the rows with the specifyed index, now a for inside this rows for find the 5xx and the 7xx sub-ranges
For Each Row In ActiveSheet.Range("b1:b6000").SpecialCells(xlCellTypeVisible)
If Cells(Row.Row, 2).Value > 4999 And Cells(Row.Row, 2).Value < 6000 Then
'or any test for understnd if i'm in the 5xx range, if you prefer use the strings use something like left(cells(row.row,2).value,1) = "5"
If rng_5xx_start = 0 Then 'found the first row with a 5xx value
rng_5xx_start = Row.Row 'set the start of the range to this row
End If
If rng_5xx_stop < Row.Row Then 'the row where i am is in the 5xx range and is grater than the current end i noticed
rng_5xx_stop = Row.Row 'refresh the end of the range...at the end this will have the last number of row of the 5xx range
End If
End If
If Cells(Row.Row, 2).Value > 6999 And Cells(Row.Row, 2).Value < 8000 Then
'same as above but for 7xx range
If rng_7xx_start = 0 Then
rng_7xx_start = Row.Row
End If
If rng_7xx_stop < Row.Row Then
rng_7xx_stop = Row.Row
End If
End If
Next
If rng_5xx_start = 0 Then
'not found 5xx rows
range_5xx = "" 'or False, or what you prefer...
Else
range_5xx = "B" & rng_5xx_start & ":H" & rng_5xx_stop
End If
If rng_7xx_start = 0 Then
'not found 7xx rows
range_7xx = "" 'or False, or what you prefer...
Else
range_7xx = "B" & rng_7xx_start & ":H" & rng_7xx_stop
End If
End Sub
That's how i would imagine a macro for your job ;)
Edit 1:
I forgot that this will leave the sheet with the filter on...use activesheet.showalldata for show all the rows and not only the filtered ones
Edit 2:
The tests
If rng_5xx_stop < Row.Row Then
rng_5xx_stop = Row.Row
End If
and
If rng_7xx_stop < Row.Row Then
rng_7xx_stop = Row.Row
End If
are not necessary, it's enough do rng_5xx_stop = Row.Row and rng_7xx_stop = Row.Row and save the two IF statements
You are grouping cells based on the first number of the cell values in column B (I am assuming that they can never be letters). If that is the case, then you can create an array of 0 to 9 and store your ranges in there. Then go through the range.areas in order to get the groupings you're looking for (as highlighted in your screenshot).
To do this, something like this is all you need. I commented code to try to explain it more:
Sub tgr()
Dim wsData As Worksheet
Dim rColB As Range
Dim BCell As Range
Dim aRanges(0 To 9) As Range
Dim SubGroup As Range
Dim lRangeNum As Long
Dim i As Long
'Change to your actual worksheet
Set wsData = ActiveWorkbook.ActiveSheet
'Change to your actual column range, this is based off the sample data
Set rColB = wsData.Range("B1", wsData.Cells(wsData.Rows.Count, "B").End(xlUp))
'Loop through the column range
For Each BCell In rColB.Cells
'Make sure the cell is populated and the starting character is numeric
If Len(BCell.Value) > 0 And IsNumeric(Left(BCell.Value, 1)) Then
'Get the starting digit
lRangeNum = Val(Left(BCell.Value, 1))
'Check if any ranges have been assigned to that array index location
'If not, start a range at that array index
'If so, combine the ranges with Union
Select Case (aRanges(lRangeNum) Is Nothing)
Case True: Set aRanges(lRangeNum) = BCell
Case Else: Set aRanges(lRangeNum) = Union(aRanges(lRangeNum), BCell)
End Select
End If
Next BCell
'You can use any method you want to access the ranges, this just loops
'through the array indices and displays the range areas of each
For i = 0 To 9
If Not aRanges(i) Is Nothing Then
For Each SubGroup In aRanges(i).Areas
'Do what you want with it here
'This just selects the subgroup so you can see it found the groups properly
SubGroup.Select
MsgBox SubGroup.Address
Next SubGroup
End If
Next i
End Sub
I see you've allready rewritten your code, but I'd like to offer how I would do it and would like to know your thoughts about it. Would this be inefficient? I guess it could be because you have to read the first character in cells 4 times for every increment, but not shure if that is a big problem.
Dim start_row As Long
Dim end_row As Long
start_row = 1
end_row = 0
For i = 2 To Range("B" & Rows.Count).End(xlUp).Row
If Cells(i - 1, 2) = "" Then
start_row = i
ElseIf Left(Cells(i - 1, 2), 1) <> Left(Cells(i, 2), 1) Then
start_row = i
End If
If Cells(i + 1, 2) = "" Then
end_row = i
ElseIf Left(Cells(i + 1, 2), 1) <> Left(Cells(i, 2), 1) Then
end_row = i
End If
If end_row <> 0 Then
Call copy_range(start_row, end_row)
end_row = 0
End If
Next i
Another approach that lets you only read the character once could be
Dim start_row As Long
Dim end_row As Long
Dim char_above As String
Dim this_char As String
start_row = 1
end_row = 1
For i = 1 To Range("B" & Rows.Count).End(xlUp).Row
If Cells(i, 2) = "" Then
end_row = i - 1
if i <>1 then Call copy_range(start_row, end_row,char_above)
start_row = i + 1
Else
this_char = Left(Cells(i, 2), 1)
If this_char <> char_above Then
end_row = i - 1
if i<> 1 then Call copy_range(start_row, end_row,char_above)
start_row = i
End If
char_above = this_char
End If
Next i
Let me know your thoughts.

Macro in Excel to remove rows containing all 0's

Okay so here is the table set up I'm working with:
I need a macro to remove the rows containing four 0's, the only way I can think of at the moment requires the cells to be empty, i.e. ""
Does 0 actually count as a string or digit or is it equivalent to "" ?
I think the problem might be related to the fact that some of my 0's are text strings and others are numbers, I just didn't think this would matter.
Try this small macro.....it examines the sum of the section of each row:
Sub RowKiller()
Dim N As Long, i As Long, wf As WorksheetFunction
Dim rng As Range
N = Cells(Rows.Count, "A").End(xlUp).Row
Set wf = Application.WorksheetFunction
For i = N To 2 Step -1
Set rng = Range(Cells(i, 1), Cells(i, 4))
If wf.Sum(rng) = 0 Then
rng.EntireRow.Delete
End If
Next i
End Sub
Here, I got one for you. Try with this.
Public Sub removeRow()
Dim row As Integer
'Set the start row.
row = 1
'Loop all row from sheet until colum "A" cell is blank
Do While Sheets("sheetname").Range("A" & row) <> ""
'If all cell are 0.
If Sheets("sheetname").Range("A" & row) = 0 And Sheets("sheetname").Range("B" & row) = 0 And Sheets("sheetname").Range("C" & row) = 0 And Sheets("sheetname").Range("D" & row) = 0 Then
'Delete entire row
Sheets("sheetname").Range("A" & row).EntireRow.Delete
Else
'Increse row
row = row + 1
End If
Loop
End Sub
Create a string of rows to delete then do ONE delete. No need to poll backwards when you do it this way and it should be a LOT faster than deleting row by row:
Sub DeleteRows()
Dim i As Long, DelRange As String
For i = 1 To Cells(Rows.Count, 1).End(xlUp).Row 'Doesn't matter which way you go when you delete in one go
If CLng(Cells(i, 1)) = 0 And CLng(Cells(i, 2)) = 0 And CLng(Cells(i, 3)) = 0 And CLng(Cells(i, 4)) = 0 Then DelRange = DelRange & "," & i & ":" & i
Next i
Range(Right(DelRange, Len(DelRange) - 1)).Delete
End Sub
Used CLng to convert the string zero to a Long zero for the test.
A small word of warning though, CLng(activecell) will return 0 if the activecell is blank so blank rows will be deleted also.
Edit: Put in a IsNumeric test to counter errors when strings are encountered (Can't CLng a true string)
Sub DeleteRows()
Dim i As Long, DelRange As String
For i = 1 To Cells(Rows.Count, 1).End(xlUp).Row 'Doesn't matter which way you go when you delete in one go
If IsNumeric(Cells(i, 1)) And IsNumeric(Cells(i, 2)) And IsNumeric(Cells(i, 3)) And IsNumeric(Cells(i, 4)) Then
If CLng(Cells(i, 1)) = 0 And CLng(Cells(i, 2)) = 0 And CLng(Cells(i, 3)) = 0 And CLng(Cells(i, 4)) = 0 Then DelRange = DelRange & "," & i & ":" & i
End If
Next i
Range(Right(DelRange, Len(DelRange) - 1)).Delete
End Sub

Create list from incomplete dataset

I have a spreadsheet with lines connecting places. Each place has a corresponding number, and is placed in a region (area).
I want a list Node with corresponding Name, and Area. Since some data is missing, I make the assumption that a line going from PARIS will also end up in PARIS.
From To From To AreaF AreaT
51191 51190 BARUM OVERL PARIS PARIS
51191 60000 BARUM BARDU PARIS 0
51059 51074 FOLLO DYRLO #N/A #N/A
51059 51070 FOLLO DYRLO #N/A BERG
51059 50795 FOLLO NYSTU #N/A #N/A
51059 59001 FOLLO VEVEL #N/A #N/A
51059 50362 FOLLO MYRVO #N/A #N/A
51059 50363 FOLLO MYRVO #N/A #N/A
51059 50812 FOLLO NORDB #N/A #N/A
What I want:
Node Name Area
50362 MYRVO BERG
50363 MYRVO BERG
50795 NYSTU BERG
50812 NORDB BERG
51059 FOLLO BERG
51070 DYRLO BERG
51074 DYRLO BERG
51190 OVERL PARIS
51191 BARUM PARIS
59001 VEVEL BERG
60000 BARDU PARIS
Any tips as to how this can be done in Excel? Any useful functions that might come in handy?
The best logic I can come up with is:
(E.g. For row 3)
Check if AreaF contains a valid Area name, not #N/A or 0 (False)
Check if AreaT contains a valid Area name (False)
Check if other rows where column A is 51059 contain valie Area names (True, row 4)
Use that Area in the new list
My problem is mainly point 3. I can't figure out what functions etc. I must use to accomplish this.
This seems to work for point 1 and 2:
=IF(ISNA(F2);IF(ISNA(G2);$M$2;IF(G2=0;$M$2;G2));IF(F2=0;IF(ISNA(G2);$M$2;IF(G2=0;$M$2;G2));F2))
Thanks!
Here is a VBA method that loops through the range and essentially is doing the eval with brut force.
I'm sure it can be cleaned up and made more efficient. Should get you started though.
Sub NodeList()
Dim sheet As Worksheet
Set sheet = ActiveWorkbook.Sheets("Sheet1")
'First Column
Dim rngA As Range
Set rngA = [A2:A10]
Dim datA As Variant
datA = rngA
Dim i As Long
Dim j As Long
'Results
Dim myarray()
ReDim myarray(100, 100)
Dim datR As Variant
Dim store As Boolean
Dim duplicate As Boolean
store = False
duplicate = False
Dim cntr As Integer
cntr = 0
'Range Column loop
For i = LBound(datA, 1) To UBound(datA, 1)
'Find first result
If IsEmpty(myarray(0, 0)) Then
'Is Col E valid?
If Not IsError(rngA(i, 5)) Then
If rngA(i, 5) <> 0 Or rngA(i, 5) <> "#N/A" Or Not IsEmpty(rngA(i, 5)) Then
'Col E is valid
store = True
col = 5
End If
End If
'Is Col F valid?
If store = False And Not IsError(rngA(i, 6)) Then
If rngA(i, 6) <> 0 Or rngA(i, 6) <> "#N/A" Or Not IsEmpty(rngA(i, 6)) Then
'Col E is valid
store = True
col = 6
End If
End If
'Store value to results
If store = True Then
myarray(0, 0) = rngA(i, 1)
myarray(0, 1) = rngA(i, col)
store = False
End If
Else
'Results has at least one value check for duplicate
'Loop thru results
For k = LBound(myarray) To UBound(myarray)
If datA(i, 1) = myarray(k, 0) Then
' duplicate found
duplicate = True
Exit For
End If
Next
If duplicate = False Then
'validate data
If Not IsError(rngA(i, 5)) Then
If rngA(i, 5) <> 0 Or rngA(i, 5) <> "#N/A" Or Not IsEmpty(rngA(i, 5)) Then
'Col E is valid
store = True
col = 5
End If
End If
'Is Col F valid?
If store = False And Not IsError(rngA(i, 6)) Then
If rngA(i, 6) <> 0 Or rngA(i, 6) <> "#N/A" Or Not IsEmpty(rngA(i, 6)) Then
'Col E is valid
store = True
col = 6
End If
End If
'Store value to results
If store = True Then
cntr = cntr + 1
myarray(cntr, 0) = rngA(i, 1)
myarray(cntr, 1) = rngA(i, col)
store = False
End If
End If
duplicate = False
End If
Next
Dim rngB As Range
Set rngB = [B2:B10]
datA = rngB
'Range Column loop
For i = LBound(datA, 1) To UBound(datA, 1)
'Find first result
If IsEmpty(myarray(0, 0)) Then
'Is Col E valid?
If Not IsError(rngA(i, 5)) Then
If rngA(i, 5) <> 0 Or rngA(i, 5) <> "#N/A" Or Not IsEmpty(rngA(i, 5)) Then
'Col E is valid
store = True
col = 5
End If
End If
'Is Col F valid?
If store = False And Not IsError(rngA(i, 6)) Then
If rngA(i, 6) <> 0 Or rngA(i, 6) <> "#N/A" Or Not IsEmpty(rngA(i, 6)) Then
'Col E is valid
store = True
col = 6
End If
End If
'Store value to results
If store = True Then
myarray(0, 0) = rngA(i, 2)
myarray(0, 1) = rngA(i, col)
store = False
End If
Else
'Results has at least one value check for duplicate
'Loop thru results
For k = LBound(myarray) To UBound(myarray)
If datA(i, 1) = myarray(k, 0) Then
' duplicate found
duplicate = True
Exit For
End If
Next
If duplicate = False Then
'validate data
If Not IsError(rngA(i, 5)) Then
If rngA(i, 5) <> 0 Or rngA(i, 5) <> "#N/A" Or Not IsEmpty(rngA(i, 5)) Then
'Col E is valid
store = True
col = 5
End If
End If
'Is Col F valid?
If store = False And Not IsError(rngA(i, 6)) Then
If rngA(i, 6) <> 0 Or rngA(i, 6) <> "#N/A" Or Not IsEmpty(rngA(i, 6)) Then
'Col E is valid
store = True
col = 6
End If
End If
If store = False Then
'Both are invalid
'look in col 'A' and reloop thru value to find another match
For p = LBound(myarray) To UBound(myarray)
If rngA(i, 1) = myarray(p, 0) Then
cntr = cntr + 1
myarray(cntr, 0) = rngA(i, 2)
myarray(cntr, 1) = myarray(p, 1)
store = False
Exit For
End If
Next
End If
'Store value to results
If store = True Then
cntr = cntr + 1
myarray(cntr, 0) = rngA(i, 2)
myarray(cntr, 1) = rngA(i, col)
store = False
End If
End If
duplicate = False
End If
Next
For i = LBound(myarray) To UBound(myarray)
Range("H" & i + 1).Value = myarray(i, 0)
Range("I" & i + 1).Value = myarray(i, 1)
Next
End Sub
Output looks like this:
I didn't add in the name but you can do that by modifying the array.
For the first question, "Any tips as to how this can be done in Excel? Any useful functions that might come in handy?":
Your logic would work fine. However, instead of a long formula in each cell, you may want to consider coding this in VBA. The format would be something similar to:
Go through all nodes
Loop through the nodes with a For...Next loop.
If the node hasn't been seen yet, add it to a list.
Use the Range.Find method, for example, to check if the node has been found already. (See here for a good discussion on .Find vs. COUNTIF, etc.)
Do your calculations on that node.
Check if Area T or Area F contains a valid name.
Use that area for the node.
To answer your second question about what functions could be used for point 3: For something not using VBA, you might consider the VLOOKUP function, as well as COUNTIF, as good functions to keep in mind. But again, See here for a good discussion on .Find vs. COUNTIF, etc.

Resources