Copy data to a new sheet if cell contains a value - excel

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.

Related

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.

For Loop for Formula that Changes Cell References

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.

How to look through a sheet and find a value, get the column, and then count how many times a value appears in that column

I am working with data that is sent to me. The sheets always contain the same headers though they aren't really headers because it doesn't come in table form, but the columns change every pull so it is never in the same column so I can't do the Index Match like I'm used to. I need to get this to work without converting the data to a table because others that use this don't know how to do that. Is there a way to search the sheet to find the cell containing the value, capture that column address, and then count how many times the column contains a letter?
I have a front excel page that keeps account of how many times something happens. Currently I use this formula =COUNTIF('UDO '!AJ:AJ,"Y"). It works the only thing is that I can't set it up as an array because the column isn't always AJ, so I'm always having to change it manually and I'd like to automate it. So I want to be able to search the sheet that contains the information for the text value example: "Review Required FY*" and get the column that contains this (it should be a unique value) then I want to look down that column and countif it has a "Y" or "y" marked in the cell. The sheets are always varying in length and column numbers. I thought about using an HLookUp but I can't get it to work. I also could not get Index Match to work, because I never know how much data or the column order the Audit tab will be in or have.
So on the Main tab I have a cell that counts how many files I have to audit I want to go to Audit tab, look for "Review Required FY*", capture that column and count how many times "Y" or "y" are there. I'd like to be able to set this up to do it all by itself.
I currently do not have any code because I can't find anything that works.
Using VBA
Option Explicit
Sub Looper()
Dim ws As Worksheet, Found As Range, LR As Long
For Each ws In Worksheets
Set Found = ws.Cells.Find("Review Required FY*")
If Not Found Is Nothing Then
LR = ws.Cells(ws.Rows.Count, Found.Column).End(xlUp).Row
MsgBox Application.WorksheetFunction.CountIf(ws.Range(ws.Cells(1, Found.Column), ws.Cells(LR, Found.Column)), "Y")
End If
Set Found = Nothing
Next ws
End Sub
Assuming the header only appears once, you can find out what row your header is in you can use an array formula like this (apply using Ctrl+Shift+Enter):
=MAX(ROW(A1:A10)*COUNTIF(OFFSET(A1:Z1,ROW(A1:A10)-1,0),"Review Required FY*"))
(looks in the first 10 rows across A:Z)
You can feed the result of that into a lookup to find the column number.
EDIT - something like this:
The first formula needs to be entered using Ctrl+Shift+Enter but the other 2 do not.
The 1000 in the last formula is a best guess at how much data there might be below your header - no problem setting that much larger to be on the safe side, as long as you don't try to count past the end of the sheet.

In VBA, looking to write a script that will carry out a function if a cell contains certain text, with ranges set as columns

I have a long string of data with lots of columns. The columns all have names like "Person_1_Date", "Person_1_Name," "Person_2_Name," Person_2_Date" etc.
I'm trying to write a script that will take the titles for each person (Person_1, Person_2), etc. and re-arrange them in a particular order (Date, Name, Date, Name, rather than Date, Name, Name, Date, as it is here).
The rough partial script I'm experimenting with looks like the following so far:
Sub Test()
Dim Rng As Range
Dim k As Long
Set Rng = Columns("B:B")
For k = Columns("B").Column To Columns("DE").Column Step 1
If (IsNumber(Search("Person_x_Date", "k" & 1)) Then 'Looking to make this get called if the Search comes back with a number'
Columns("k:k").Cut
Rng.Selection.Insert Shift:=xlToRight
Next k
End Sub
A few things I'm looking for help with. Is it possible to write a Search function that will look for parts of what's in a cell, while ignoring another? With Person_x_Date I'm hoping it to find a cell with anything in place of the x, so it could find Person_1_Date, Person_2_Date, etc. Is this possible?
The rest is a bit of a mess. Essentially, I want to set a range as a whole column, and if the search finds something, it'll copy and paste it into the correct place, and continue again until it finds what should come next (Like Person_X_Date).
I know the latter part of the code I wrote is quite far from that. I'm just looking for any kind of suggestions to help me put this together.
Be aware that excel sorting can be done horizontally (most people just use it vertically).
This in effect allows you to sort columns into order based on values in a row. (Normal vertical sorting will sort rows based on a value in a column).
In the sort dialogue, click the options button to choose horizontal sorting.
I think this will actually give you what you need.
however, if you need to manipulate the data values, you could create a formula row that changes the values and then sort on the data in the formula row..

Modifying a Dynamic Name with VBA

I have a spreadsheet that links to several other spreadsheets to pull data that compiled into one sheet; I am tasked with modifying the sheet to graph this data. There is a row of 12 slots for each month in a person's report; if a person was working in a month then the month will be displayed in the appropriate slot. For the graph I have figured out to use a dynamic name range so that the number of months in the graph is equal the number of months present in the line; the only problem is that the start pointing changes based on what month a person started.
I am able to make a rather large nested if statement but that doesn't seem like a very good way to approach resolving my problem. Is there a way I can have a VBA script change the value of the dynamic name range or just modify the chart's value directly? If this isn't a viable option is there any other way to approach this problem other than a dynamic named range?
I can't change how this report sheet behaves as this is a spreadsheet related to work and that's how they want it to be for reporting reasons; I'm simply looking to add the graphing functionality.
EXAMPLES:
https://www.dropbox.com/s/zn6yt4l6kjvwq33/Example1.xlsx
https://www.dropbox.com/s/j88tgoik68s4lhx/Example2.xlsx
In the first example you see an example of the problem when you select "Second" from the dropbox next to Agent Name. It's including pointless data in the graph.
In the 2nd example I have resolved the problem by adding an if statement to the dynamic named ranges used in the chart; but as you can see to include 2 months it's now massive and to include all 12 it would be pretty much impossible. If I were to add a 3rd person who started in March they would also not work in the 2nd example.
Is there a way I can make it so the values in the Dynamic name range are changeable through VBA? Can I modify the chart's values directly using VBA? Is there a way to bypass this problem without even using VBA?
Thanks.
(I'm not 100% certain I've understood the problem, but this might at least help to get closer...)
Try defining three names, something like this:
start_month
data
chart_data
Where start_month contains the number of the column that you want to be charted, data is all twelve columns of data, something like =OFFSET($C$1,0,0,COUNT($A:$A),12) and chart_data selects the column you want from the data, e.g. =INDEX(data, , start_month)
Now set your chart's series to WorkbookName!chart_data, using whatever your workbook's called. As the start_month value changes, so should the values displayed in the chart.
You can add or modify a Name in VBA like:
Dim nm As Name
Dim rng As Range
'Here, you could use a more complicated expression'
' to derive the proper address dynamically.'
Set rng = Sheets("Sheet1").Range("A2:A25")
'Now, add a Name to the workbook:'
Set nm = ActiveWorkbook.Names.Add("My_Name", rng)
With some string functions/variables, you could derive/caclulate the range address in VBA.

Resources