In Excel 365, currently I manually update multiple named ranges based on a dynamic header row, with dynamic values under each header. I hoping someone can help me with using VBA code to update the names and ranges all together based on the current data/table.
The goal is to have a drop down list with the relevant Units values, without blanks or duplicates, for each Item.
The source data comes from an external workbook that is updated weekly. The data is in columns of Item and Units, but there is often multiple instances of each Item and Units and each week there may be new Items or some removed, same with the associated Units values. The source is as per below example:
ITEM
UNITS
AA 120
100
AA 120
100
AA 120
150
AA 60
350
BB 200
36
BB 200
30
BB 200
30
SH 1001
55
SH 1001
55
The unique headers are pulled using the formula
=IFERROR(INDEX($B$4:$B$600, MATCH(0, COUNTIF($K$2:K2, $B$4:$B$600), 0)),"")
The following formula is in the row below the headers, to pull the unique values for each header
=UNIQUE(FILTER($C$4:$C$600,$B$4:$B$600=L2))
The resulting table is as per below example:
AA 120
AA 60
BB 200
SH 1001
100
350
36
55
150
30
Currently I have highlight all the headers and the row below and select Create from Selection under Defined Names and select Top row. This creates named ranges that refer to the cell below each header. I then edit each range that the name refers to, by adding # at the end, so it refers to the spilled data, as it is a dynamic range.
e.g. Update the named range reference for AA_120 from =SHEET1!$L$3 to =SHEET1!$L$3#
I do this one by one for 100+ named ranges. Any tips or help to make this more efficient?
oh and the formula I'm using for the source of the Data Validation is =INDIRECT(C7) where C7 is the Item/named range. This all works well...
TIA
EDIT:
I worked out some VBA code to add the # at the end of the referred range. The range for all Named Ranges starts on row 3, so this worked to update all relevant ranges at once...
Sub RangeRename()
Dim n As Name
For Each n In Names
If Right(n.RefersTo, 1) = "3" Then n.RefersTo = n.RefersTo & "#"
Next n
End Sub
Would still really appreciate it if someone could improve on my VBA, to update all the ranges from when the source data is updated. Currently I delete all named ranges in the Name Manager, then select the 2 rows with all the updated Item and Units data. Then Create from Selection under Defined Names and select Top row. Then I run my macro. But if the Macro could do all above, that would be great. The difficulty I see if that the amount of Items and corresponding Units vary as it is dynamic. Plus there are 3 data sources on 3 different sheets, all rows 2&3 but columns start at L, N & T for the 3 sheets.
What I have worked out will work for me, but if anyone can improve on my code, I'd be extremely grateful!
Maybe I don't understand exactly what you're trying to do...but can't you get the info you want with a simple pivot table? I imagine there are advanced tricks you can apply to that to get to the formatting you want...
But using current Excel formulas, I think I got what you want by doing this:
I pasted your source data into a worksheet (A1:B10) and made it into
a table (Control+T), selecting 'My table has headers' option. (I think it's better to use a table but it's certainly possible to tweak the formulas below if you don't want to.)
I picked another cell on the same or another sheet--let's say E2--and entered =TRANSPOSE(UNIQUE(Table1[ITEM])). This gives a horizontal spill of unique items.
In the cell immediately below that (ie, E3) I entered =IFERROR(SORT(UNIQUE(FILTER(Table1[[UNITS]:[UNITS]],Table1[[ITEM]:[ITEM]]=D15))),""). (You need to repeat the bracketed column names like this if you want to drag things horizontally, which we do.) If you don't need/want to sort the Units in the columns we're going to create, you can leave that part of the formula out.
I dragged that cell horizontally to the left, going way beyond you need it right now (in case any new Items appear in your list). The IFERROR in the formula will keep the table looking clean on the right-most columns where no data currently appear. If/when you add more rows to your source data table, the spilled formulas will adjust accordingly, so I think your worry about dynamic sources is taken care of.
Results shown below. Maybe there's a way to devise a single formula that when put into E3 will spill into a 2-D array, so you don't even need to drag things...but I'll leave that to someone else.
Just in case anyone is wanting to achieve the same thing.
I used a filter to create the column of items, then TRANSPOSE(UNIQUE(FILTER(G:G,B:B=T7 for the rows of unit options next to each item.
For the VBA I added the following to a module
Function xDAV(c As Range) As Range
Dim r As Range
With Sheets("LookUpList").Columns("T:T") 'Items
Set r = .Find(What:=c.Value, LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
'Units are in col U
'col U is 1 column to the right from col T
n = 1 'change to suit
If Not r Is Nothing Then
Set f = r.Offset(, n)
If f.Offset(, 1) = "" Then
Set xDAV = f 'single cell or blank cell
Else
rc = f.End(xlToRight).Column 'get last column with data
Set xDAV = f.Resize(, rc + 1 - f.Column)
End If
End If
End With
End Function
Then in the data validation I entered =toXDAV in the source field.
Related
I'm new to VBA and was just trying to figure this out.
I have a the following range CR7, CR9:CR24, CR28:CR39, CR45:CR50, CR52
Currently, each of these cells reference a value from another worksheet.
CR7='Trend'!CO7
CR9='Trend'!CO9
CR10='Trend'!CO10
This will follow the entire range.
What I was wanting to do was change the reference to 3 columns to the right.
CR7='Trend'!CO7 will then be CR7='Trend'!CR7
What I was trying to do was remove the last 3 characters after the "!" and then replace it with "RC[-3]" but I don't know what I'm doing at all. I just looked up different codes on this site and tried to piece it together, knowing that I would probably run into some obstacles because I'm new at this.
Here's the code I was trying to implement but running into an error.
For Each C In Range("CR7,CR9:CR24,CR28:CR39,CR45:CR50,CR52")
C.Value = Left(C.Value, InStr(C.Value, "!") - 1)
C.Value = C.Value & "RC[-3]"
Next
Any suggestions on how to get this corrected or is there a better way in doing this? Thanks in advance for looking into this!!
Solution
As stated in the comments, since you are looking for a specific header, the solution might be faster as follows:
Assuming the headers are in Sheet 1 as follows
Jan-22 (IE:actual data is 01-01-2022)
Feb-22
Mar-22
10
30
60
20
40
70
Where A1 = Jan-22, B2= Feb-22, etc.
Just use a combination of index and match, seems like you need to bring the row at the same level, so it will be as follows:
Set a referece in your formula to where you will have the criteria to look: In this example will be A1
=INDEX(Sheet1!$A:$C,ROW(),MATCH($A$1,Sheet1!$1:$1,0))
I'll explain:
Index will bring the column where you want the data, in this case will be the data under the month (Col A), seems like the row should be at the same level, so we will say the row will be equal to where the formula row is being casted, that's why the row(), finally, it will be in where the column matches the criteria that you want where the header is found, if you need an offset even after finding the column name, just sum it up at that part
MATCH($A$1,Sheet1!$1:$1,0) + 3
Demo:
I have this formula:
IF(ROWS($Q$27:Q27)<=$P$25,INDEX(DataTable[[#All],[Time]],$P27),"")
and if I drag it to the right, it should automatically read each column respectively; example:
=IF(ROWS($Q$27:R27)<=$P$25,INDEX(DataTable[[#All],[Name]],$P27),"")
^Notice that the first Q27 is fixed, the second Q27 is variable.
I drag this formula to the right by 15 columns, and down to 50 rows. that's 750 formulas in total.
I want to do this in vba, but if I did this, it will be 750 lines of code for each cell representing each row/column.
example: .Range("G17").Formula=IF(ROWS($Q$27:R27)<=$P$25,INDEX(DataTable[[#All],[Name]],$P27),"""")
and if I drag it down, it will automatically pick up what I exactly want, example:
=IF(ROWS($Q$27:Q28)<=$P$25,INDEX(DataTable[[#All],[Time]],$P28),"")
so this formula should be written 750 times in total for the cell range [ A27:N76 ]
Any faster / more dynamic approach? and if possible, can I make it depend on more than 50 lines based on a cell value inside the sheet?
Example:
This should do it all in one line:
Range("A27:N76").FormulaR1C1 = "=IF(ROWS(R27C17:RC[16])<=R25C16,INDEX((DataTable[[#All],[Name]],RC16),"""")"
EDIT: Seems a more that one line of code required after all 😊
The code below will do what you want (this time fully tested)
Sub FillFormulas()
Dim inC%, rgHead As Range
''' Assumes the target sheet is Active.
''' o If that's not the case, change this With statement to reference the target sheet
With ActiveSheet
''' Set rgHead to the Table's header row
Set rgHead = .ListObjects("DataTable").Range.Rows(1)
''' Add the formulas to the target range, column by column updating the table header on the fly
With .Range("A27:N76")
For inC = 1 To .Columns.Count
.Columns(inC).FormulaR1C1 = _
"=IF(ROWS(R27C17:RC[16])<=R25C16,INDEX(DataTable[[#All],[" & rgHead.Cells(inC) & "]],RC16),"""")"
Next inC
End With
End With
End Sub
so this formula should be written 750 times in total for the cell range [A27:N76]
You don't need to do that. If you specify range.Formula, it will fill the proper formulas all the way across and down. Just give it the formula of the top/left most cell.
So, in your case
Range("A27:N76").Formula = "=IF(ROWS($Q$27:R27)<=$P$25 ... "
EDIT: This response had some obvious errors
This has an obvious error (as tested part and then merged to the full thing).
Range(A27:N76).FormulaR1C1 = "=IF(ROWS(R27C17:RC[16])<=R25C16,INDEX((DataTable[[#All],[Name]],$P27),"""")"
I have a large table that contains a large data. Most of the time when I apply a filter to it i can manipulate and edit the filtered data with no problems. However sometimes(every 200th time perhaps...) when i select filtered range and try to paste in the selection some text - it seems like it has done the job but when I unfilter the table, the range that was edited is the range as it wasn't filtered at all.
Example:
my data is A1:A10
the filtered range is the cells A1 and A10,
when I select the filtered range and paste a text, occasionally the whole A1:A10 range is changed.
Anyone faced this issue?
the consequences are disastrous.
How will i avoid it in the future.
Thanks!
Ok I figured it out.
When the data is filtered, I select cells and want to apply some changes to it - what happens is that excel defines the rows range for manipulation as "upper row in selection to bottom row in selection":
the problem is that sometimes the row indexes are not consecutive(common issue when the data is not logically ordered in the first place) and excel treats the whole range in between the visible selected cells as the range for manipulation TOO.
It hapened to me occasionally only because my data is more or less ordered.
Example: a small table of numbers
**nums**
1
2
3
4
5
3
6
if i filter the nums table to show only 3s
it will show me this:
**nums**
3
3
when i select these two cells by dragging from one 3 to the other, paste the number 0 and unfilter the table back, the result will be
**nums**
1
2
0
0
0
0
6
because the cells inbetween the visible cells were in selected range too.
To prevent it, the solution is as Lior suggested:
Find & Select --> Go to... --> Visible cells only.
After you select the column you want to edit use select visible cells only.
in the link there is an example of the copy you can use the same for the paste.
http://office.microsoft.com/en-001/excel-help/copy-visible-cells-only-HA010244897.aspx
I admit I am completely new to Excel VBA. At the moment, I am in charge of generating a summary of data based of an automatically generated table of data spit out on a site. I already figured out how to scrape the data onto my Excel thankfully.
The scraped data is something of the following format:
TYPE | Number_Days_Logged | (a bunch of other important columns of data in row (6-7 items)|
What I need to do now is the following.
I need to do an if statement or comparison. There are two "types" under the type column. Let's say 0 and 1. I really only need type '1', so I need to filter out any 'type' 0's. Then I need to check the "number_days_logged". If this number is <= 1, I need to add it to a table with other entries with this condition. I then need to check "number_days_logged" for entries >= 85 and <100. These results will need to be put into a second table. Finally, I need to do the same this for values >= 100.
So in the end I need three different tables, the first with a green color formatting, then orange, and finally red. Each table need to be titled with the above information, though I really only need data from specific columns in each row. (each row is labeled, I just need a specific few columns)
This seems incredibly complicated to me, but I am willing to learn. If anyone can prod me in the correct direction, or make it simpler I would appreciate it. I can add any other details as required.
I think select statements are pretty clear and they tend to run a little faster too.
dim typeRange as range
set typeRange = range(cells(startRow,col),cells(stopRow,col))
dim entry as range
dim currentRow as integer
for each entry in typeRange
currentRow = entry.Row
if entry.value=1 then
select case cells(currentRow,'Number_Days_Logged's column').value
case is<=1
'copy and paste or transcribe the cells however you need them done.
case is<85
'do nothing
case is<100
'copy and paste or transcribe the cells however you need them done.
case is>=100
'copy and paste or transcribe the cells however you need them done.
end select
end if
next
You would first setup the tables with the colors you wanted, and (for this code) a named range for the data. I'd probably used named ranges for your tables also. Here's the skeleton of the VBA code.
Dim testrow As Range
For Each testrow in ActiveSheet.Range("Scraped Data")
If testrow.Cells(1) = 1 Then
If testrow.Cells(2) <= 1 Then
<add to 0-days table>
ElseIf testrow.Cells(2) >= 85 AND testrow.Cells(2) < 100 Then
<put in 2nd table>
ElseIf testrow.Cells(2) >= 100 Then
<put in third table>
End If
End If
Next testrow
I've got a spreadsheet with plenty of graphs in it and one sheet with loads of data feeding those graphs.
I've plotted the data on each graph using
=Sheet1!$C5:$C$3000
This basically just plots the values in C5 to C3000 on a graph.
Regularly though I just want to look at a subset of the data i.e. I might just want to look at the first 1000 rows for example. Currently to do this I have to modify the formula in each of my graphs which takes time.
Would you know a way to simplify this? Ideally if I could just have a cell on single sheet that it reads in the row number from and plots all the graphs from C5 to C 'row number' would be best.
Any help would be much appreciated.
OK, I had to do a little more research, here's how to make it work,
completely within the spreadsheet (without VBA):
Using A1 as the end of your desired range,
and the chart being on the same sheet as the data:
Name the first cell of the data (C5) as a named range, say TESTRANGE.
Created a named range MYDATA as the following formula:
=OFFSET(TESTRANGE, 0, 0, Sheet1!$A$1, 1)
Now, go to the SERIES tab of the chart SOURCE DATA dialog,
and change your VALUES statement to:
=Sheet1!MYDATA
Now everytime you change the A1 cell value, it'll change the chart.
Thanks to Robert Mearns for catching the flaws in my previous answer.
This can be achieved in two steps:
Create a dynamic named range
Add some VBA code to update the charts data source to the named range
Create a dynamic named Range
Enter the number of rows in your data range into a cell on your data sheet.
Create a named range on your data sheet (Insert - Name - Define) called MyRange that has a formula similar this:
=OFFSET(Sheet1!$A$1,0,0,Sheet1!$D$1,3)
Update the formula to match your layout
Sheet1!$A$1 set this to the top left hand side of your data range
Sheet1!$D$1 set this to the cell containing the number of rows
3 set this value to the number of columns
Test that the named range is working:
Select the dropdown menus Edit - Go To, type MyRange into the reference field.
Your data area for the chart should be selected.
Add some VBA code
Open the VBA IDE (Alt-F11)
Select Sheet1 in the VBAProject window and insert this code
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address <> "$D$1" Then Exit Sub
'Change $D$1 to the cell where you have entered the number of rows
'When the sheet changes, code checks to see if the cell $D$1 has changed
ThisWorkbook.Sheets("Sheet1").ChartObjects(1).Chart.SetSourceData _
Source:=ThisWorkbook.Sheets("Sheet1").Range("MyRange")
' ThisWorkbook.Sheets("Chart1").SetSourceData _
Source:=ThisWorkbook.Sheets("Sheet1").Range("MyRange")
'The first line of code assumes that chart is embedded into Sheet1
'The second line assumes that the chart is in its own chart sheet
'Uncomment and change as required
'Add more code here to update all the other charts
End Sub
Things to watch for
Do not directly use the named range as the data source for the chart. If you enter the named range "MyRange" as the Source Data - Data Range for the chart, Excel will automatically convert the named range into an actual range. Any future changes to your named range will therefore not update your chart.
Performance might be impacted by the approaches listed above.
The OFFSET function in the named range is "volatile" which means that it recalculates whenever any cell in the workbook calculates. If performance is an issue, replace it with the INDEX formula.
=Sheet1!$A$1:INDEX(Sheet1!$1:$65536,Sheet1!$D$1,2)
The code fires everytime data is changed on Sheet1. If performance is an issue, change the code to run only when requested (i.e. via a button or menu).
You could look at dynamic ranges. If you use the OFFSET function, you can specify a starting cell and the number of rows and columns to select. This site has some useful information about assigning a name to an OFFSET range.
You can set the range for a chart dynamically in Excel. You can use something like the following VBA code to do it:
Private Sub Worksheet_Change(ByVal Target as Range)
Select Case Target
Case Cells(14, 2)
Sheet1.ChartObjects(1).Chart.SetSourceData Range("$C5:$C$" & Cells(14,2))
...
End Select
End Sub
In this case, the cell containing the number of the last row to include is B14 (remember row first when referring to the Cells object). You could also use a variable instead of the Cells reference if you wanted to do this entirely in code. (This works in both 2007 and 2003.) You can assign this procedure to a button and click it to refresh your chart once you update the cell containing the last row.
However, this may not be precisely what you want to do ... I am not aware of a way to use a formula directly within a chart to specify source data.
Edit: And as PConroy points out in a comment, you could put this code in the Change event for that worksheet, so that neither a button nor a key combination is necessary to run the code. You can also add code so that it updates each chart only when the matching cell is edited.
I've updated the example above to reflect this.
+1s for the name solution.
Note that names don't really really reference ranges, they reference formulae. That's why you can set a name to something like "=OFFSET(...)" or "=COUNT(...)". You can create named constants, just make the name reference something like "=42".
Named formulae and array formulae are the two worksheet techniques that I find myself applying to not-quite-power-user worksheets over and over again.
An easy way to do this is to just hide the rows/columns you don't want included - when you go to the graph it automatically excludes the hidden rows/columns
Enhancing the answer of #Robert Mearns, here's how to use dynamic cells ranges for graphs using only the Excel's formulas (no VBA required):
Create a dynamic named Range
Say you have 3 columns like:
A5 | Time | Data1 | Data2 |
A6 | 00:00 | 123123 | 234234 |
...
A3000 | 16:54 | 678678 | 987987 |
Now, the range of your data may change according to the data you may have, like you have 20 rows of data, 3000 rows of data or even 25000 rows of data. You want to have a graph that will be updated automatically without the need to re-set the range of your data every time you update the data itself.
Here's how to do it simply:
Define another cell that it's value will have the number of the occupied cells with data, and put the formula =COUNTIF(A:A,"<>"&"") in it. For example, this will be in cell D1.
Go to "Formulas" tab -> "Define Name" to define a name range.
In the "New Name" window:
i. Give your data range a name, like DataRange for example.
ii. In the "Refers to" set the formula to: =OFFSET(Sheet1!$A$1, 0, 0,Sheet1!$D$1,3),
where:
Sheet1!$A$1 => Reference: is the Reference from which you want to base the offset.
0 => Rows: is the number of rows, up or down, that you want the upper-left cell of the results to refer to.
0 => Columns: is the number of columns, to the left or right, that you want the upper-left cell of the results to refer to.
Sheet1!$D$1 => Height: is the height, in number of rows, that you want the result to be.
3 => Width: is the width, in number of columns, that you want the result to be.
Add a Graph, and in the "Select Data Source" window, in the Chart data range, insert the formula as you created. For the example: =Sheet1!DataRange
The Cons: If you directly use the named range as the data source for the chart, Excel will automatically convert the named range into an actual range. Any future changes to your named range will therefore not update your chart.
For that you need to edit the chart and re-set the range to =Sheet1!DataRange every time. This may not be so usable, but it's better than editing the range manually...