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.
Related
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)
I am trying to perform ratio analysis for a collection of financial statements, each of which are contained in a separate excel worksheet. The layout and format of each sheet is exactly the same. See below.
Financial Statement Sheet
For the ratio analysis section I have a separate worksheet where I want to make an array of the computed ratios. See below.
Ratio Analysis Sheet
Unfortunately, I really don’t want to click through each worksheet to create these formulas and would rather write a script with a FOR loop. Since I need to find a ratio for every year of the financial statement the formula itself will need to continuously shift columns to derive a ratio for every year.
This is where I’m stuck. I’m not sure how to loop through every cell of my ratio summary array and use a formula that also changes for each year of the financial statement. I’m thinking I need to make a nested for loop, but I’m really not sure how to attempt the script.
Can anyone do me a huge favor and suggest how I might even start planning this out?
My VBA Developer Console
Here's what I have started, but I need more to figure this out. I don't think my formula will shift columns with this script, and I need help putting the computed values into the array on the ratio analysis sheet.
Sub Current_Ratio()
Dim Current_Ratio As Integer
Dim FirstYear As Long, LastYear As Long
FirstYear = 2015
LastYear = 2019
For i = 1 To Worksheets.Count
If Worksheets(i).Name Like "*Balance Sheet*" Then
For y = FirstYear To LastYear
Current_Ratio = Worksheets(i).Range("F21").Value / Worksheets(i).Range("F51").Value
'need line to place the computed current ratios into range B3:B37 on sheet17(Ratio Analysis)
Next y
End If
Next i
End Sub
Are you open to a formula solution rather than VBA? Here's how I would try it first. I say "try it" because the INDIRECT() function is slow and if you have too many formulas you might not like the performance. But I wouldn't make that judgement until I tried it.
I have a sheet named Company 1 Balance Sheet and in B21:D21 I have 10, 15, and 20 respectively. In B51:D51 I have 20. I also have a sheet named Company 2 Balance Sheet that has the same numerators but 30 as the denominator.
Here is the formula in C2
=INDIRECT("'"&A2&" Balance Sheet'!"&CHAR(66+VALUE(SUBSTITUTE(SUBSTITUTE(B2,"T-",""),"yr","")))&"21")/INDIRECT("'"&A2&" Balance Sheet'!"&CHAR(66+VALUE(SUBSTITUTE(SUBSTITUTE(B2,"T-",""),"yr","")))&"51")
I started by pointing and clicking to build this formula
='Company 1 Balance Sheet'!B21/'Company 1 Balance Sheet'!B51
Then I used INDIRECT to build that formula as a string so I could change the inputs. Let's take just the numerator portion because it works the same for both. My company is in column A, so I can start by replacing that part of the formula
=INDIRECT("'"&A2&" Balance Sheet'!B21")
The Balance Sheet portion of the name will be consistent, so I'll leave that. I'll need to change the B to refer to the proper year. I'll start by making that variable. The letter B is 66 in the ASCII character set.
=INDIRECT("'"&A2&" Balance Sheet'!"&CHAR(66)&"21")
Now I can add whatever year I want to 66 to increment that. My year is in column B, but it has a lot of crud around it. First I need to isolate the numeric portion
=SUBSTITUTE(SUBSTITUTE(B2,"T-",""),"yr","")
That gets rid of the stuff around the number. Then I use VALUE to turn it into a number. I can add that number to 66 so that T-1yr refers to 67 which is C.
And that's it. It's a little painful to set up, but once it's done, you just copy it down. Note the Div/0 errors means you don't have data for that year. Once you're formulas are working the way you want, wrap it in an IFERROR() function to hide those.
In your example, the company name is not repeated on every row. That doesn't work well for this method, but you can have a column where the company is repeated a simply hide that column if your particular presentation is important.
Other things you can with hidden columns: Put in the sheet's name if you don't consistent sheet names or if you just want the formula to be a little easier to read. You could also put the 21 and 51 in hidden columns and refer to those in your indirect function.
I am looking for help with a macro so I can easily sort and see data.
I have a spreadsheet with rows 1 and 2 containing item information. Rows 3-93 have the ordering information. For every row in 3-93, if there is a value in any column in between F and VU, copy the the corresponding value in columns A,B,C for that row to a new sheet called Orders and copy the corresponding values in rows 1 and 2 for that column.
for example, I need this:
......A..........B...........C...................................F...........................................GU
1..................................................100235410 (Navy Shirt)......101028 316 (Moss FR Plaid)
2..................................................................XL.........................................2XL T
3..Jack....Brown...Current.............................2..............................................1
Copied over to a new sheet called Orders, like this:
.......A.........B.............C.......................................D................................E..................F
1...Jack...Brown....Current..............100235410 (Navy Shirt).................XL.................2
2...Jack...Brown....Current............101028 316 (Moss FR Plaid).........2XL T..............1
I am struggling with the VBA logic and can't seem to get it work right. Any help or advise is greatly appreciated.
Thanks!
A problem like this can be a challenge to get your head around if you just dive into the coding especially for beginners it requires the definition of bite sized chunks that can be coded as descrete processes. I suggest you create a flow chart or step-by-step procedure that describes what you would do if you had to make the changes by hand. If your procedure accurately describes those steps, you can then create code that does each of the individual steps and string those code snips together in the order you described in your flow chart.
Also, be sure to define processes that can be repeated for each row or sheet or other descrete data set you will work with. You can then loop these processes to avoid writing the same code over and over again.
For your example above, if I understand correctly, you will first want to define the range in which customers are defined: in this case the range from A3 to A93 (or whatever the highest row number might be. If that varies, you'll want to define a range that changes with the particular data set (using the .End method, for example, if there are no empty cells in the data (ideally there should not be) you could do this:
Dim wbOrders As Workbook
Set wbOrders = ActiveWorkbook
Dim wsOrders As Worksheet
Set wsOrders = wbOrders.ActiveSheet
Dim rngOrders As Range
Set rngOrders = wsOrders.Range("A3", wsOrders.Range("A3").End(xlDown))
Once you have defined the range in which orders are found, you can loop through the data to work with the individual orders:
Dim rngCustomer As Range
For Each rngCustomer In rngOrders
'~~>Here you could define other ranges to work with (perhaps columns F to VU of the
' current row)
'~~>Here you could also define other loops/routines to move the data
Next rngCustomer
Inside the above loop structure you could Dim a range of customer orders, loop through the range finding columns that contain data, use each column with found data (one at a time) to get the data(probably one cell at a time) from that row and rows 1 and 2, transfering it either onto either a new
sheet, OR into an array of values that can be added to all new sheets after the first loop finds and defines all the customer order data and puts it in the array.
I hope this gives you enough to start tackling the problem. If you hit snags during the coding of an individual step, search around on StackOverflow or the web in general to find the solutions. If you don't find them, post a question that details what you've tried and includes the specific non-working code to StackOverflow and I almost guarantee you'll have an answer in 24 hours or less.
I've got a somewhat specific task I'm trying to accomplish in Excel that appears to be beyond my depth. I've got a dataset with two five-word lists per each row/observation, like this example:
What I'm hoping to accomplish is to highlight all of the words that are in both 5-word lists within a single row. The amount of overlap between these two five-words lists varies from one row to the next. And I'm not concerned with identifying any duplicate entries across different rows. That is, it'd be great if it's possible to create a macro that would give this:
I've searched quite a bit on this site and using google to figure out how to create a macro to do this. I've found a number of similar macros, but every one that I've found is geared towards identifying all of the duplicates between two entire sheets, or two entire columns, or something similar, which doesn't quite match what I'm trying to do. Based on the macros I have been able to find, I get the sense that doing what I want should be possible, but I don't know enough about visual basic to edit the macros I've found to suit my needs.
If it's possible to program a macro to do this, it'd save me quite a bit of time, since otherwise I'm looking at doing this duplicate-identification manually for two current datasets that I have (each with 150-200 observations), plus I plan on collecting data in the future that would require this same procedure. Beyond that, any macro that's capable of helping me here may be able to help others with similar needs.
Thanks in advance for any help you're able to provide!
Try conditional formatting.
Select "table 2" (e.g. I2:Mn where n is the last row) with I2 (upper left corner) being the active cell. Then use this formula:
=OR(I2=$C2:$G2)
I think using Conditional Formatting is maybe a simpler solution but one way to do this using VBA is as follows:
Sub HighlightDuplicates()
Dim masterList(), highlightList(), rw As Long, col As Long
masterList = Range("C1:G150")
highlightList = Range("I1:M150")
For rw = 1 To UBound(masterList, 1)
For col = 1 To UBound(masterList, 2)
If highlightList(rw, col) = masterList(rw, col) Then
Cells(rw, col).Offset(0, 8).Interior.Color = vbRed
End If
Next col
Next rw
End Sub
Here we read both lists in as arrays and then iterate over them to check for matches.
The Offset(0, 8) is a bit of a magic number (yikes!) which gets the correct cell highlighted based on your layout.
I tried searching for this, but I kept coming across questions of how to make a cell show as blank instead of false. I want to clear the formula out.
I am working from an access database (that i can't do much to), that contains data on sessions on our computers. What the database will give me currently is basically "Section 1, 9-10", "Section 1, 10-11", etc. There are a total of 5 sections like this for 11 hours. In the past, I've had to copy from those queries into excel sheets. What I've gotten to at this point is I can create a query for each section that just gives me the information I need, start time and duration, which I can paste into a workbook I made.
What this workbook does is take the data from those columns and moves it to page 2 sorted by hour with if statements. the problem with that is I need those if statements to go all the way across, and far enough down to be sure to include the highest number of sessions we've had, plus a comfortable buffer, so it's 7000 rows down.
After that sorting is done, I can take all the data from that second sheet and paste it into monthly records. the problem is, with there being formulas in each of those cells, it adds approximately 7-10 Mb to the file size.
Is there someway so that cells that evaluate to false have their formula removed? Or, probably more likely, a better way to go about this without much additional software?
I'm guessing you want to use an if formula and if it evaluates to False then display nothing. You can do this by setting the value for the false result to "".
=if(expression,value if true, "")
The alternative is to use a macro to loop through each cell and clear it if the value is false.
Sub falseToClear()
' Change to the range you need to parse
Set Rng = Sheets(1).UsedRange
For Each cell In Rng
If cell.Value = False Then cell.ClearContents
Next cell
End Sub