Problems with vba discrimination - excel

I have written a simple iteration loop that goes through each row of a workbook and reads the value in the first cell. Using the instring function of vba I determine whether or not the cell contains certain characters and if the criteria is met it either deletes or preserves the row. When I run this code, however, there are two cells that are spelled and formatted identically to each other, but one is deleted and one is saved. The cells in question contain the text (CJ 20210526) - Merlin FLIR Calibration. I have included the code for reference, because it is not very long and after scouring it multiple times I cannot find the issue. I don't understand how two cells that are exactly the same can be treated so disparately.
Filter Code in VBA

You need to run the code backwards.
Replace:
For x=1 to last_row
by:
For x=last_row to 1 Step -1
Also dim x as Long
(there may be other errors)

Related

Several conditions with IF

i have a data set in Excel where in the 1st column there is and ID with 12 characters in which i must change from 1 to -1 if the first 6 characters from left to right has several conditions.
I have tried the following, but i cant get it work with out using VBA
=IFT((LEF([#Cuenta],6)=AND("503-01","503-02","503-50","503-60")),-1,A847)
I would like to make it as short as possible cause the person that works with the sheet gets confused with long formulas

Deleting rows based on date

I am very new to VBA and macros in Excel. I have a very large excel spreadsheet in which column A holds dates. I am trying to delete the rows which have only Today's Date and this is what I have come up with till now..
Sub DELETEDATE()
Dim x As Long
For x = 1 To Cells.SpecialCells(xlCellTypeLastCell).Row
Debug.Print Cells(x, "A").Value
If CDate(Cells(x, "A")) < CDate("Now()") Then
Cells(i, "A").EntireRow.Delete
End If
Next x
Next i
End Sub
There are a number of issues with your code. If you are new to VBA then you should put Option Explicit at the start of each Class/Form/Module so that you will get quicker feedback on errors you are making.
You should always qualify excel references by the Worksheet so that you are not working with the implicit active sheet e.g. you need myWb.Range rather than just Range. There are lots of examples if you google this topic.
You can use the macro recorder to record code to see what Excel is expecting in terms of usage. Code produced by the macro recorder is generally not very good code so only use it to clarify, not a style to aspire to.
You are also making a classic mistake when you come to delete a row, which is that you are changing the collection with which you are working.
VBA and Excel(or any other application) are two seperate entities. Neither knows what the other is doing unless they pass messages to each other through a set of well defined interfaces. These interfaces are provided by the application. E.g. you tell Excel you want to work on a range object by using the Range 'interface' etc.
When you look at your for loop you have told VBA that it is going to loop x number of time with x being a value obtained from excel.
VBA is given the final number for x but has absolutely no idea that this value is connected to a set of objects in Excel.
In your for loop you delete one of the rows in Excel. This means that all rows after the row you delete are renumbered to be one less than they were. But VBA doesn't know this, it just knows it is increasing x by 1 until it reaches the maximum value you set earlier.
The problem is, now that you have deleted a row, that that maximum value no longer represents the number of rows you are processing.
Even more horrible, the row that was e.g. row 4 is now row 3 because you deleted the old row 3, but x, which is currently 3 is going to be increment to 4, but the old row 4 is now row 3 so that x=4 is now looking at the old row 5 which is the new row 4. This means that every time you delete a row, you will skip over the row that was one more than the row you deleted.
Its easy to avoid this problem by counting down rather than counting up. So your for loop should be
For x = Cells.SpecialCells(xlCellTypeLastCell).Row to 1 step -1
You should aslo use variable names that mean something. In this case I'd suggest myRow rather than 'x'.
Two other good practises are
Always do a Debug.Compile project before trying to run your code. This will check the whole of your code for syntax errors.
Install the free and fantastic Rubberduck addin for VBA. You can use this addin after your code compiles cleanly. Use the Code Inspections to find out where you have made assumptions you didn't know you'd made.

UsedRange.Rows.Count shows different number than the actual [duplicate]

This question already has answers here:
Find last used cell in Excel VBA
(14 answers)
Closed 1 year ago.
I need some expert advice on VBA code. I have created a procedure to compare two short reports (the same format, generated from the system). I used the for loop starting from the first row until the last one using UsedRange.Rows.Count. The problem I have is that UsedRange.Rows.Count always return 30 even though the reports are generally shorter than that - usually around 20-25 rows. It returns 30 even if I clear the content inside these rows. It only returns the correct number when I permanently delete the blank rows up to row 30.
Why is that happening? It bothers me because also for some reason the macro highlights the blank rows (from the last actually used to row 30) showing them as a difference between the reports, while they exist and are the same (blank) in both.
Thank you
UsedRange will expand to include all cells that Excel registers as containing any data. Values, formulae, formatting — even (and I suspect that this may be what is happening here) a cell that has had a value deleted from it. Anything outside of UsedRange is treated as not yet officially existing yet.
As an analogy, this is the difference between an empty box, and no box (with the worksheet cells being boxes). UsedRange tells you where the last box is, even if you've emptied it (with Value = "" or ClearContent); only deleting the Rows/Columns/Cells will get rid of the box itself.
There are many much better ways to get the size of your data than by using UsedRange (although, it at least provides an Upper Bound)
For example, if your data is contiguous, then you can select a single cells (e.g. Range("A1") / Cells(1,1)), and get the CurrentRegion (Cells(1,1).CurrentRegion.Rows.Count). Or, if you know a column that always contains data, you can use End(..) to find the bottommost cell (Cells(Rows.Count,1).End(xlUp).Row)

Automate concatenation process

Here I am stucked with one excel issue where i want to concatenate from column F till column I where the logic is when the benchmark column A3 (for example) is blank it need to concatenate column F till column I till there is a value at column A4.and this logic need to automatically concatenate the mentioned column till there is a value under the benchmark column. currently i need to keep change the concatenate range in order to concatenate it fully with the logic. Appreciate if anyone can help me out.
Below image shows how i am doing manually which very time consuming
You can use the MATCH function (with a wildcard) to find the next non-blank row; and use that in an INDEX function to detect the range to concatenate.
Assuming your data starts in A3 and the lowest possible row is row 1000 (change the 1000's in the formula below if it might be much different:
J2: =IF(A2="","",CONCAT(INDEX(F2:$I$1000,1,0):INDEX(F2:$I$1000,IFERROR(MATCH("*",A3:$A$1000,0),1000-ROW()),0)))
Note: It is possible to also develop solutions using INDIRECT and/or OFFSET. Unfortunately, these functions are volatile, which means they recalculate anytime anything changes on your worksheet. If there are a number of formulas using these functions, worksheet performance will be impaired. INDEX and MATCH are non-volatile (except in ancient versions of Excel - pre-2003 or so)
The OFFSET-function would come on handy here. One solution is to do it like
This works in my worksheet.
Cell Q6 just defines the number of rows downwards that the MATCH-function is checking for the next "HEADER1" value. If "HEADER1" is found, the MATCH-function returns how many rows down-1. If no "HEADER1"-value is found within that range, that value is then the number of rows used.
If the first column also has "HEADER2" and so on, you can add the MID-function to both references inside MATCH to limit which part of the string are to be searched for.
I tried to adjust the references properly to fit your sheet, but I may have missed something:
=IF(ISBLANK($B2),"",CONCAT(OFFSET($B2,0,0,IFNA(MATCH(MID($B2,1,6),MID(OFFSET($B2,1,0,$B$1),1,6),0),$B$1),4)))

Combining multiple non contiguous ranges into one named range to clean up formula

I have a formula that I'm using which works pretty well, but I would like to clean it up.
=SUMIF(IB02R, A56, IB02P)+SUMIF(IB03R, A56, IB03P)+SUMIF(IB04R, A56, IB04P)
IB02R,IB03R,IB04R & IB02P,IB03P,IB04P are ranges that I defined in the name manager. They look at entire rows.
IB02R looks at B4-ND4 of another sheet
IB02P looks at B5-ND5 of another sheet
and so on.
Here is the original formula:
=SUMIF('2014'!$A$4:$ND$4,A75,'2014'!$A$5:$ND$5)+SUMIF('2014'!$A$7:$ND$7,A75,'2014'!$A$8:$ND$8)+SUMIF('2014'!$A$10:$ND$10,A75,'2014'!$A$11:$ND$11)
I would like to simplify this to combine all R's and P's so instead of having 3 sumif statements I could just have =SUMIF(IB0234R, A56, IB0234P). With IB0234R and IB0234P being the ranges contained within IB02R and so forth.
The formula is looks for a match between a particular cell and every cell in 3 different rows. Rows 4,7, and 10.
If there is a match anywhere within those rows it sums up the corresponding values in Rows 5,8, and 11 respectively.
Both of my formulas work, but I would like to simplify for sake of readability and clarity.
Is this possible? I've tried a few different ways to no success.
Here is your formula:
=SUMPRODUCT((CHOOSE({1;2;3},'2014'!$A$4:$ND$4,'2014'!$A$7:$ND$7,'2014'!$A$10:$ND$10)=A56)*CHOOSE({1;2;3},'2014'!$A$5:$ND$5,'2014'!$A$8:$ND$8,'2014'!$A$11:$ND$11))
This will take a little bit of fancy named-range-fu to make it readable, but it's definitely doable.
First, combine the "first row" of each section together into another named range, like so:
=CHOOSE({1;2;3},IB02R,IB03R,IB04R) - We'll call this IB00R
Now do the same with the "second row":
=CHOOSE({1;2;3},IB02P,IB03P,IB04P) - We'll call this IB00P
Now the formula becomes:
=SUMPRODUCT((IB00R=A56)*IB00P)
To understand exactly how the formula is working, I suggest clicking Evaluate Formula on the Formulas tab, and stepping through it, and stepping in and out of your named ranges.
EDIT: Ok now I'm doubting myself - not sure if this is working correctly. I know it will work because I've done it before, but the formula below may not be quite right. I'll figure it out in a bit.
EDIT 2: As written, this doesn't work. However, there is a way around the problem, but I can't remember what it is. Still fiddling with it. If I can't figure it out I'll delete this answer.
EDIT 3: Working now. I forgot, in order to combine non contiguous rows you have to use CHOOSE() instead of INDEX() Sorry for the false start.
FYI, I ABSOLUTELY like Rick's answer a whole lot more, but I'll admit that what he did was new to me (which is why I love this site!!), so I only knew how to do this using VBA.
With VBA, the function you could use to do this would be as follows:
Function Disjoined_SumIf(CriteriaRange As Range, Criteria As Range, SumRange As Range) As Double
Dim ar As Integer
Dim cl As Integer
For ar = 1 To CriteriaRange.Areas.Count
For cl = 1 To CriteriaRange.Areas(ar).Cells.Count
If CriteriaRange.Areas(ar).Cells(cl).Value = Criteria.Value Then Disjoined_SumIf = Disjoined_SumIf + SumRange.Areas(ar).Cells(cl).Value
Next cl
Next ar
End Function
And you would use it in your spreadsheet as =Disjoined_SumIf(IB0234R, A56, IB0234P)
This function will work based upon the following assumptions:
The criteria range is split up the same way the sum range is (which in your case it is)
It doesn't take into account things such as non-numeric data in your sum-range which would blow up the function
It's a quick and dirty solution that could be built upon to make it more robust, but it would work!!
So, again, I'd definitely go with Rick's solution, but I'm adding this for completeness' sake.

Resources