I am very new to VBA, and most of the times I manage to fit my needs by simple jury-rigging someone else's code - sometimes I also find exactly what I was looking for in answers to other people's questions. But right now it seems I can't figure out how to solve this problem (I tried asking in other excel forums, but with no result).
I'm working with very large data output files, which present all the variables I need to analyze scattered in sheets, in multiple workbooks. In order to statistically analyze these data files we often have to rearrange the output data into a more statistically friendly format.
What I'm trying to code is a Summary new sheet for each of the workbooks I need analyzed, which will be populated with a dynamic range copied from each of the sheets and pasted one below the other.
The difficulty resides in the fact that the range is not fixed: has a variable starting point as row in column "A", and a variable length (and thus a variable ending point, but always in Column "F").
So I would need to adapt a simple copy/paste code like the one I've been using for ages
Sub SummurizeSheets()
Dim ws As Worksheet
Application.ScreenUpdating = False
Sheets("Summary").Activate
For Each ws In Worksheets
If ws.Name <> "Summary" Then
ws.Range("A1:A2").Copy
ActiveSheet.Range("A65536").End(xlUp).Offset(1, 0).PasteSpecial Transpose:=True
End If
Next ws
End Sub
with a dynamic range starting in:
Column "A", row =(MATCH(Summary!$M$1;[SheetName]!A:A;0)
and ending in the last non-blank cell starting from the range-start point (which is effectively the last populated cell within the range belonging to the variable described in the Summary Column M1)
this last part looks like frankenstein's monster as:
MATCH(TRUE;INDEX(ISBLANK(INDIRECT(CONCATENATE(CONCATENATE("A";MATCH(Summary!$M$1;[SheetName]!A:A;0));":";"A100")));0;0);0)+ROW(INDIRECT(CONCATENATE("A";MATCH(Summary!$M$1;[SheetName]!A:A;0))))-1
which basically looks for the first blank cell in an arbitrary custom range, from the above-mentioned starting point to A100, and, once found, offsets the row number by (-1).
Finally, the question: how can I translate those two cell references into the ws.Range("xx:xx") above to have my old pal copy/paste do its job with the range I require?
If the output is a row number, just use Cells() in a combination with Range():
Range(Cells(ROW NUMBER, COLUMN NUMBER), Cells(ROW NUMBER2, COLUMN NUMBER2))
where the ROW NUMBER is the output of your frankenstein and the COLUMN NUMBER is obviously the number of the column. ROW NUMBER2 is the row number of your last row
Related
I have inherited a poorly designed workbook, and I am trying to make it work a bit better without starting from scratch.
The last problem I have is that I have a formula in a column that I need to copy to the next column, but change the row number referenced in the formula. The easiest thing to do would be to change the format of the workbook but that will cause an uprising by the users.
=IF((CommaSeparatedListContains(RTM!$I$8,ROW()-2))=TRUE,"X","")
The code above is what I need to copy, but I need to change it so that it looks at I9 instead of I8. RTM is the name of the sheet that the cell is on, and CommaSeparatedListContains is a macro that will return true if the referenced cell has a value (ROW()-2) in the comma delimited list.
Basically I need a macro to add a new column to the worksheet that works like the others, so that the end users who don't know how to use Excel can just click a button and add a column.
For example, that code is in cell A1, and I need to move it to B1 keeping the I the same but increment the row number. If I remove Both $ signs it would change it to J8, if I have $I8 it stays I8, and if I have $I$8 it stays I8.
Thanks for re-affirming my understanding. I'll give this a shot with providing some code, based on your string:
Dim LC as Long, i as Integer
Columns(9).Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
Cells(1,10).Value="" 'Add your header
LC = Cells(1, Columns.Count).End(xlToLeft).Column
i = LC-1 'See description below code
Range("J2:J100").Formula="=IF((CommaSeparatedListContains(RTM!$I$" & i & ",ROW()-2))=TRUE,""X"","""")"
For i, you want to ensure that you substract the correct number... given the example of I8 being the cell you want to reference, and assuming that Column I is the last column of your sheet, then the 9th column, 8th row, is the cell you want to reference. So, the variable i = last column - 1, in this example.
In this case, the column is always added to the right of column I, the assumed last column in the sheet.
One other assumption is that you're using rows 2 to 100 for the range that you have the formula... So, Range("J2:J100").Formula will be affected by your actual range for the formula.
I want to write a formula like =SUM(tab2!A:A) but instead use the column title of A which is say "count". How can I modify this to look more like: =SUM(tab2!"count")?
The reason I want to do this is because I copy and paste a spreadsheet from another source in tab2 and the column referring to "count" may be in a different column. I want the formula to give me the correct calculation as soon as I paste the new spreadsheet by automatically finding the column to sum up by title name.
I must be missing something because this seems like a very basic question and I can't find the answer anywhere...
Thanks for your help!
I like the idea of naming ranges proposed by #Doug, but if the issue is that you are dumping your data in [and you don't know in advance which column is going to be where] and would need to rename your range every time, there are other options - I suggest using OFFSET. OFFSET allows you to dynamically create a range, starting at a particular point and moving down/up / right/left for as many rows and columns as you determine.
In your case, you will need to combine that with a method for searching the columns to see which one says "Count". I am assuming that your column headings are always in row 1. If they aren't [or if they aren't always in row 2, or any row you know in advance]... you can get around that but then I'd recommend you try to make your data more uniform instead of creating unnecessary Excel workarounds.
In total your formula would look like this:
=SUM(OFFSET(A1,,MATCH("Count",1:1,0)-1,ROWS(A:A),1))
What this does is first determine which column the word "Count" is found in, in row 1. Then it subtracts 1 from that number - this now equals the number of columns to the right that it is, from column A. It uses offset to create a reference to that column, for all rows, and sums those rows together.
Check out the name manager for naming ranges :)
You didn't say whether you would consider a macro solution. If so, this may work.
If the sheet you are pasting into is Sheet2 and the sheet you want the result in is Sheet1, then this macro, if placed in the Worksheet_Activate event of Sheet1 will give you the result as soon as you click on the Sheet1 tab afetr pasting your data into Sheet2:
Private Sub Worksheet_Activate()
Dim r As Range
Dim rCol As Range
Dim rFound As Range
Dim ws As Worksheet
Dim lTotal As Long
Set ws = Sheet2
Set r = ws.Cells
Set rFound = r.Find("count")
If Not rFound Is Nothing Then
Set rCol = rFound.EntireColumn
lTotal = Application.WorksheetFunction.Sum(rCol)
End If
Cells(1, 1) = lTotal
End Sub
It does assume there is only one cell with the word "count" in it on Sheet2.
I always see on SO that people use this to find the last row in a column
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
I have always used this function in my projects to find it as I want the last unused row but now that i always see people using End xlUp approach i am wondering why since it only gives that column which may not be correct.
Function GetLastRow(sh As Worksheet) As Long
Dim X As Long
X = sh.UsedRange.Rows.Count 'force excel to recalculate last row
GetLastRow = sh.Cells.SpecialCells(xlLastCell).Row
End Function
Is there any situation where this function fails to give the last row and why it happens?
Some times it happens that it give me a row that could be many lines after the last row of data I assume due to formatting(exported reports generally - I then work my way back up in a loop to remove these rows if fully blank)
What is UsedRange actually considering as used?
I really want a reliable function that I can depend on in the future
Excel tracks cells sparsely: think of it as tracking a row and column pair for each cell that has ANY information (actually its a bit more complex than that but its still a good way of thinking about it). The information can be formatting or data or formulas or ...
So the last used cell is the last entry in the cell tracking table. But the cell tracking table is not reset just by clearing information.
And since Excel 2007 sh.UsedRange.Rows.Count does not (unfortunately) always reset the cell table.
Mostly when you want to find the last used cell you want to find the last cell that actually contains data or formulas rather than the last cell in the cell table.
Using Range.End(xlUp) etc finds the last VISIBLE cell that contains data or formulas, so needs to be used with care if you hide rows or use filtering. And requires looping code when considering multiple columns. But its the fastest of the 3 main methods.
Using Find is usually the most reliable method (but it ignores shapes and comments, and has problems with merged cells and empty pivot tables):
jLastRow = oSht.Cells.Find(What:="*", LookIn:=xlFormulas, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
jLastCol = oSht.Range("A1:A" & CStr(jLastRow)).EntireRow.Cells.Find(What:="*", LookIn:=xlFormulas, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
In Excel, if you enter data from A1 to Z100, then the used range goes from A1 to Z100.
If you delete rows 50 to 100, the used range is still A1 to Z100. Just clearing cells does not reduce the used range. Excel will mark the last row/last column where data was entered previously.
UsedRange can give you an unexpected result if you want to find the last populated cell in the spreadsheet, because it takes into account rows and columns that may have been in use previously.
If you want to find the last row in a column, the method with rows.count and end(xlUp) is more accurate, because it considers the current values. UsedRange may be bigger.
I have seen (searched) similar examples, but not quite what I am looking for.
I have a Workbook in Excel that has several sheets, Sheet A and B. These sheets have a bunch of data, so in order to display the most significant data on Sheet B from Sheet A, I want to mirror only the rows that I want to specify depending on the cell values on SheetA....I need to delete entire rows in Sheet B depending on the value in Sheet A.
For instance, in Sheet A I have column X with 10 values (Yes/No), and I have linked the same data with formulas back to Sheet B. That is, that if in SheetA X1="Yes", then SheetB cell Y1="Done"...if SheetA X2="Yes", then SheetB cell Y2="Done"...if SheetA X3="No", then SheetB cell Y1="Missing"..and so on.
So I only want the rows in SheetB with cell values="Done" to be there and thus want rows with cell values="Missing" to be automatically deleted. In this fashion, I would be creating a table that only includes the rows with "Done" values for the specified cell.
I know there are macros in Excel, but I have never written code in VBA, and the language handlers and variables escapes me entirely.
Is there a way to write a macro that can be called with in a formula; that is, e.x) if(A10="Yes", "", delete row macro here)???
Thanks!
From the wording in your question it seems you want to create a function that can be used in a cell that will alter other cells. That cannot be done. The functions, when used in a formula, are limited to changing the cell itself, and not other cells.
More then one way to skin a cat. Like Abe said you can`t use formula to alter other cells. But you can use VBA. The below sub removes entire rows where the cell in range is equal to 1. But you can make it equal to whatever you want.
Sub DeleteRows()
Dim FoundCell As Range
Set FoundCell = Worksheets("SheetB").Range("YourRange").Find(what:=1)
Do Until FoundCell Is Nothing
FoundCell.EntireRow.Delete
Set FoundCell = Worksheets("SheetB").Range("YourRange").FindNext
Loop
End Sub
Of course this is extra work. What you should do instead of copying the data from A to B and then processing it, just copy the done cells from A to B.
This question stems off another post I had. (see Search through column in excel for specific strings where the string is random in each cell)
Using the above image as reference, I am trying to search through column B (actually over 1000 lines) using column E as the "lookup values." The end goal would be for "just" the names to be displayed in column C. The trick is all the randomly generated characters the encompass the names. Below is what I would want the datasheet to look like. A formula or module should work, but the vlookup and other lookup function I can't get to work.
For a worksheet function approach, you could enter in C3 and fill down this formula:
=LOOKUP(8^5,SEARCH(E$3:E$7,B3),E$3:E$7)
The constant 8^5=32768 is chosen to be larger than the maximum possible string length so that LOOKUP returns the last matching value. The formula returns #N/A if no string is found.
Another possibility, which may be easier to understand then assylias post initially, but also may be a bit more time consumptive (although with 1,000 rows, I don't think it will matter much) is below.
This requires that you name the range in column E as myNames (or whatever name you wish, just update the code - alternatively, you cuold just write Range("E1:E6")). Also, if you move the random values from column B, update that in the code as well.
Sub findString()
Dim celString As Range, rngString As Range, celSearch As Range, rngSearch As Range
Dim wks As Worksheet
Set wks = Sheets("Sheet1") 'change sheet reference to whatever your sheet name is
Set rngString = wks.Range("myNames")
Set rngSearch = Intersect(wks.UsedRange, wks.Range("B1").EntireColumn)
For Each celString In rngString
For Each celSearch In rngSearch
If InStr(1, celSearch.Text, celString.Value) > 0 Then
celSearch.Offset(, 1) = celString.Value
End If
Next
Next
End Sub
Since, I worked on your original question as well, I would suggest getting the counts through Siddharth's answer and then running this, or assylias's code above to get the names next to the columns. You could put a button the sheet, or just use the Macro dialog box to run the macro.