In a column filled mostly by blank cells, I need to get the value of the cell thats closest to the currently selected one, in a row before it, and isn't blank.
While the "isn't blank" part can be easily achieved with the an IF and ISBLANK statement, I can't figure out how to get the position of the first cell upwards from the current one that isn't blank.
In this example spreadsheet, in the cell C7 I want to display the value of B7 minus the value of B4 (=B7-B4). The problem is that these values are separated by an unpredictable number of cells, which can range from 0 to 20 or even more. Needing to parse a spreadsheet with thousands of rows and frequent data added, selecting the upper cell manually is not an option.
In continuation of my example, I want C13 to display =B13-B10, but lets say there were two more blank rows in between them (row 13 becomes row 15), I would want it to display =B15-B10.
Can the position be obtained with a MATCH query, knowing that the Types will always be T1 or T2?
Thank you in advance.
Enter this formula in cell C2 and drag it to the bottom.
=IFERROR(IF(B2="","",B2-LOOKUP(9.99999999999999E+307,B$1:B1)),"")
Writing formulae via VBA
Below you find a VBA approach writing your subtracting formulae to column C.
BTW, searching via array is much more quicker than looping through a range.
Code
Option Explicit
Sub subtractLastValueRow()
' declare vars
Dim oSht As Worksheet ' work sheet
Dim a As Variant ' one based 2-dim data field array
Dim n As Long ' last row in column B
Dim i As Long ' item no
Dim ii As Long ' last item no
Dim j As Long
Dim s As String
' set sheet
Set oSht = ThisWorkbook.Worksheets("MySheet") ' fully qualified reference to worksheet
' get last row number of search column
n = oSht.Range("B" & oSht.Rows.Count).End(xlUp).Row
If n < 2 Then Exit Sub ' only if data avaible (row 1 = title line)
' get range (after title line) values to one based 2dim data field array
a = oSht.Range("B2:C" & n).Value ' array gets data from e.g. "A2:A100"
' loop through column B to find keyword sKey
If Len(a(1, 1) & "") > 0 Then ii = 1 + 1 ' first item in array
For i = LBound(a) + 1 To UBound(a) ' array boundaries counting from 1+1 to n -1 (one off for title line)
' value found
If Len(a(i, 1) & "") > 0 Then
For j = i + 1 To UBound(a)
If Len(a(j, 1) & "") > 0 Then
' write .Formula (alternatively use .Value, if value wanted)
oSht.Range("C" & i + 1).Formula = "=B" & i + 1 & "-B" & ii
ii = i + 1 ' note last found i
Exit For
End If
Next j
End If
Next
If Len(a(UBound(a), 1) & "") > 0 Then ' last item in array
oSht.Range("C" & UBound(a) + 1).Formula = "=B" & UBound(a) + 1 & "-B" & ii
End If
End Sub
Note
If you want to write values instead of work sheet formulae, simply replace .Formula with .Value.
In cell C3 put following formula:
=B3-LOOKUP(2,1/$B$1:B2,$B$1:B2)
And copy down in all T2 cells as required.
Related
I am using EntireRow.Delete to Delete some rows in my Excel Programm. It works very well!
I need a way to delete the EntireRow but I have to exclude some Columns at the end of that row.
Is it possible to call EntireRow.Delete and exclude some Columns? Here is my Code:
Dim j As Long
Dim count As Long
count = 0
Dim searchRng As Range: Set searchRng = Range("Q9:Q5000")
Dim Cell As Range
For j = searchRng.count To 1 Step -1
If ((searchRng(j).Value < CDate(TextBoxDelete.Value)) And (searchRng(j).Value <> "")) Then
count = count + 1
Debug.Print ("Cell " & count & ": " & searchRng(j).Value & " txtbox: " & TextBoxDelete.Value)
' searchRng(j).EntireRow(, -6).Delete
searchRng(j).EntireRow.Delete ' Original - works but I need to "cut off" the last columns
' searchRng(j).EntireRow.Cells(, 19).Delete
' Debug.Print searchRng.EntireRow.Offset(, 7)
End If
Next j
I have tried to use some Offset and other Functions on that line but with no luck. Does anyone know how I could change it so it deletes the entire Row but keeps the columns at the back lets say from Column "T" in place and does not delete those.
The problem is that EntireRow as the word says is the entire row. But you can use Resize to cut it off at a specific column.
Try the following
Option Explicit
Public Sub test()
Dim ws As Worksheet
Set ws = ActiveSheet ' better define your sheet like ThisWorkbook.Worksheets("Sheet1")
Dim LastRow As Long ' get last used row in column Q so you only loop through actual data
LastRow = ws.Cells(ws.Rows.Count, "Q").End(xlUp).Row
Dim searchRng As Range
Set searchRng = ws.Range("Q9", "Q" & LastRow)
Dim Count As Long
Dim j As Long
For j = searchRng.Count To 1 Step -1
If (searchRng(j).Value < CDate(TextBoxDelete.Value)) And (searchRng(j).Value <> "") Then
Count = Count + 1
' clear content from column A to Q
searchRng(j).EntireRow.Resize(ColumnSize:=19).ClearContents
' check if entire row is empty
If searchRng(j).Offset(ColumnOffset:=ws.Columns.Count - searchRng(j).Column).End(xlToLeft).Column = 1 Then
' row is empty delete it
searchRng(j).EntireRow.Delete xlShiftUp
End If
End If
Next j
End Sub
This
searchRng(j).Offset(ColumnOffset:=ws.Columns.Count - searchRng(j).Column)
jumps to the very last cell in that row and then uses .End(xlToLeft) to go left until it finds a cell with data. So if the column number is 1 that means the entire row is empty and can be deleted.
So in the example below the red cells trigger the deletion
and it ends up with
As you can see the row with 1 was cleared until column T because there is more data behind, but the row with 3 was entirely deleted because it was totally empty after clearing it from A to S.
\ Edit according comment
If you don't want empty cells use
If (searchRng(j).Value < CDate(TextBoxDelete.Value)) And (searchRng(j).Value <> "") Then
Count = Count + 1
' clear content from column A to Q
searchRng(j).EntireRow.Resize(ColumnSize:=19).Delete xlShiftUp
End If
This question already has answers here:
If Cell Contains This or That Paste onto Another Sheet
(2 answers)
Closed 3 years ago.
I'm a beginner when it comes to VBA and Macros; therefore, I'm not sure what the exact verbiage is but I believe I'm looking for help with looping.
My macro currently partially matches cells in each row in "SheetJS" containing either "Mercedes-Benz" or "BMW" and pastes the values to Column D in "Sheet1". However, it only copies the first iteration/cell that partially matches the text.
I want the macro to copy and past all matches. For example the 1st iteration should be copied/pasted to "Sheet1" Column D, 2nd in Column H, 3rd in L, and so on. Each iteration should have 3 cells in between.
I don't even know how to move forward with this.
Any tips would be greatly appreciated.
Thanks!
Sub Extract_Data_or()
For Each cell In Sheets("SheetJS").Range("A1:ZZ200")
matchrow = cell.Row
If (cell.Value Like "*Mercedez-Benz*") Or (cell.Value Like "*BMW*") Then
Sheets("Sheet1").Range("D" & matchrow).Value = cell.Value
End If
Next
End Sub
Edit 01.09.20
I want the macro to get all iterations/partial matches in each row and copy them. The current macro only copies the first match. I don't want to copy the entire row just the individual cells.
For example the first match in "SheetJS" should be copied to Column D in "Sheet1". The second match, (if any) should be copied to Column H, 3rd in column L, 4th in column P, etc. Every match should be placed 4 cells from each other.
SheetJS
All matches are highlighted in yellow. The values in each cell should copied over to "Sheet1"
Sheet1
The first match in each row is in Column D, the 2nd( if any) is in Columb H, etc.
You asked yesterday something similar. I asked for clarifications and I supplied a solution without receiving any sign from you...
Anyhow, maybe this time you will look at the next code and maybe test it. It works very fast, avoiding cells iteration. It works only in memory:
Private Sub Extract_Data_Bis()
Dim rngArr As Variant, dArr As Variant
Dim sh As Worksheet, i As Long, j As Long, k As Long
Dim lngOcc As Long, lngChanges As Long, boolFound As Boolean
Dim lngSameRow As Long, lngMised As Long
Set sh = Sheets("Sheet1")
rngArr = Sheets("SheetJS").Range("A1:ZZ200").Value
dArr = sh.Range("D1:F200").Value
For i = 1 To UBound(rngArr, 1)
boolFound = False: k = 0: lngSameRow = 0
For j = 1 To UBound(rngArr, 2)
If InStr(UCase(rngArr(i, j)), UCase("Mercedez-Benz")) > 0 Or _
InStr(UCase(rngArr(i, j)), "BMW") > 0 Then
If Not boolFound Then
lngSameRow = i
k = 1
Else
If lngSameRow = i Then
k = k + 1
End If
End If
lngOcc = lngOcc + 1: boolFound = True
If k <= 3 Then
dArr(i, k) = rngArr(i, j)
lngChanges = lngChanges + 1
Else
lngMised = lngMised + 1
End If
End If
Next j
Next i
sh.Range("D1:F200").Value = dArr
MsgBox lngOcc & " occurrences, versus " & lngChanges & " changes done. " & lngMised & " missed..."
End Sub
In case there are more then 3 occurrences on a row, at the end it makes a balance between occurrences, changes done and missed ones...
I have the first name in column A and the last name in Column B and I need to combine them into just column A. Also not sure if I need to check this in the code but some of the cells are empty with now names. I have tried many things but they all want me to pull the two and enter them into a different or 3rd column. But I need to put them into column A.
This is the code I have and it keeps giving me the merge error.
With Worksheet
For Counter = LastRow To FirstRow Step -1
Range("BD2:BE1000").Merge Across:=True
Next Counter
End With
You can just use string concatenation here (assuming that lastrow (1000) and firstrow (2) have been set up properly in your sample code).
With Worksheet
For Counter = LastRow To FirstRow Step -1
.Range("BD" & counter).Value = .Range("BD" & counter).value & .Range("BE" & counter).value
Next Counter
End With
Concatenate (non-empty) names into one column
[1] In a first step you can assign your data range (object variable e.g. rng) to a variant 1-based 2-dim datafield array by a one liner v = rng or v = rng.Value2.
[2] In a second step you loop through all array rows and check for non-empty names concatenating these findings in the array's first columns (overwriting the original single name part).
[3] Resizing the receiving range to 1 column only (and the number of non-empty rows allows you to write the results back to sheet.
Code example
Option Explicit ' declaration head of your code module enforces declaration of variables/objects
Sub ConcatenateNames()
Dim v As Variant, rng As Range
With ThisWorkbook.Worksheets("MySheet") ' <<~~ change to your sheet name
' [1] assign names to 2-dim datafield array v
Set rng = .Range("BD2:BE1000") ' set user defined range to memory
v = rng.Value2 ' get data
' [2] loop through data
Dim i As Long, ii As Long
For i = 1 To UBound(v)
' [2a] check for non empty names
If Len(Trim(v(i, 1)) & Trim(v(i, 2))) > 0 Then
' [2b] concatenate first and last names in array v
ii = ii + 1 ' increment counter
v(ii, 1) = v(i, 1) & " " & v(i, 2)
End If
Next i
' [3] write back to sheet and resize receiving range to ii rows and 1 column
rng.Clear ' clear original data
rng.Resize(ii, 1) = v ' write names back to sheet
End With
End Sub
Further hint
Take care of the leading point . before "Range" referring to your worksheet object: Set rng = .Range("BD2:BE1000")
I have created a macro that looks at number in a cell and then copies a group of three columns and inserts them to the right of the group. This all works fine.
I have a formula in a cell after these groups of columns that looks to see if there is a 1 in the cell. the code below is what there would be assuming I created 2 groups.
=IF(AND(H9=1,J9=1),1,0)
I want to be able to automatically add the M9=1,P=1 if I had created four groups.
If someone has the time to help it would be much appreciated.
Sorry, learning as I go on here.
I am creating a matrix where I can build up a number of functions in the columns direction and a number of inputs that effect the functions in the rows direction.
I start off with a 'group' of three columns per function, In my first group G9 is the expected condition, H9 is the result during simulation and I9 is the result during real world tests. I want to be able to say how many functions and inputs there will be and automatically create the matrix.
If I have two functions then there will be two groups of columns from G to L.
After all of the functions I have a check to see if they all passed, with two functions this check would be in M9, where I have the formula =IF(AND(H9=1,K9=1),1,0) that checks to see if there is a 1 in both H9 and K9 and then puts a 1 in M9.
If I had four functions then I would need the check formula of =IF(AND(H9=1,K9=1,N=1,Q=1),1,0) in S1
I want to create the check formula within a loop so that it adds in the correct cells to check.
Hope this explains it a little bit better, but probably not!!
Here is the code so far
Private Sub CommandButton1_Click()
' Copy the template worksheet
Worksheets("ZoneTemplate").Copy After:=Worksheets("ProjectConfig")
' Rename the worksheet to the correct Zone
Sheets("ZoneTemplate (2)").Name = Sheets("ProjectConfig").Range("B9")
' Setup the variables
Dim Loop1 As Integer
Dim MySheet As String
Dim NoOfOutputs As Integer
Dim NoOfColumnsOffset As Integer
Dim Loop2 As Integer
' Get the name of the sheet ready for use in the loop
MySheet = Sheets("ProjectConfig").Range("B9").Value
' Get the number of outputs to add
NoOfOutputs = Sheets("ProjectConfig").Range("E9") - 1
' Loop for the number of safety output functions
For Loop1 = 1 To NoOfOutputs
' select the columns to copy and copy them to buffer
Worksheets(MySheet).Range("G:I").Select
Selection.Copy
' Insert the copied columns infront of J1 and shift everything along to the right
Worksheets(MySheet).Range("J1").Insert Shift:=xlShiftToRight
Next Loop1
End Sub
So this did the trick....
Private Sub CommandButton1_Click()
' Copy the template worksheet
Worksheets("ZoneTemplate").Copy After:=Worksheets("ProjectConfig")
' Rename the worksheet to the correct Zone
Sheets("ZoneTemplate (2)").Name = Sheets("ProjectConfig").Range("B9")
' Setup the variables
Dim Loop1 As Integer
Dim MySheet As String
Dim NoOfOutputs As Integer
Dim NoOfInputs As Integer
Dim NoOfColumnsOffset As Integer
Dim Loop2 As Integer
Dim Loop3 As Integer
Dim loop4 As Integer
Dim SOAddr1 As String
Dim SimAddr As String
MySheet = Sheets("ProjectConfig").Range("B9").Value ' Get the name of the sheet ready for use in the loop
NoOfOutputs = Sheets("ProjectConfig").Range("E9") - 1 ' Get the number of outputs to add
' Loop for the number of safety output functions
For Loop1 = 1 To NoOfOutputs
Worksheets(MySheet).Range("Safety_Output_Function").Select ' select the columns to copy and copy them to buffer
Selection.Copy
Worksheets(MySheet).Range("J7").Insert Shift:=xlShiftToRight ' Insert the copied columns infront of J1 and shift everything along to the right
Next Loop1
' Loop to generate the formula for the Sim Result check
For Loop2 = 1 To (NoOfOutputs) 'Sheets("ProjectConfig").Range("E9")
NoOfColumnsOffset = 8 + (Loop2 * 3) ' Work out the cell number for the new column
SOAddr1 = Cells(9, NoOfColumnsOffset).Address(RowAbsolute:=False, ColumnAbsolute:=False) ' Convert the cell number to a letter reference
SimAddr = SimAddr & "," & SOAddr1 & "=1" ' build the string to add for each column and each time we come round the loop
Next Loop2
' put the new formulas in
Worksheets(MySheet).Cells(9, (NoOfColumnsOffset + 2)).Formula = "=IF(AND(H9=1" & SimAddr & "),1,0)"
' Loop to generate the formula for the Hardware Result check
For Loop3 = 1 To (NoOfOutputs) 'Sheets("ProjectConfig").Range("E9")
NoOfColumnsOffset = 9 + (Loop3 * 3) ' Work out the cell number for the new column
SOAddr1 = Cells(9, NoOfColumnsOffset).Address(RowAbsolute:=False, ColumnAbsolute:=False) ' Convert the cell number to a letter reference
SimAddr = SimAddr & "," & SOAddr1 & "=1" ' build the string to add for each column and each time we come round the loop
Next Loop3
' put the new formulas in
Worksheets(MySheet).Cells(9, (NoOfColumnsOffset + 2)).Formula = "=IF(AND(I9=1" & SimAddr & "),1,0)"
NoOfInputs = Sheets("ProjectConfig").Range("D9") - 1 ' Get the number of Inputs to add
' Loop for the number of safety output functions
For loop4 = 1 To NoOfInputs
Worksheets(MySheet).Range("9:9").Select ' select the columns to copy and copy them to buffer
Selection.Copy
Worksheets(MySheet).Range("A10").Insert Shift:=xlDown ' Insert the copied columns infront of J1 and shift everything along to the right
Next loop4
End Sub
In Excel, I am trying to get a macro to move numbers with a "-".
I have a column E with a list of numbers
54525841-1
454152
1365466
1254566-1
1452577-1
I want a macro to move all the numbers that have a dash or hyphen at the end to column C.
So I would need E1 54525841-1 to be moved to C1.
You'll need to change "Sheet1" to the name of the sheet where your data is.
This looks through every cell (with data) in the E column and moves the value accross to the C column if it contains a dash.
Sub MoveDashes()
Dim Sheet As Worksheet
Dim Index As Long
Set Sheet = ThisWorkbook.Worksheets("Sheet1")
For Index = 1 To Sheet.Cells(Application.Rows.Count, "E").End(xlUp).Row
If InStr(1, Sheet.Cells(Index, "E"), "-") > 0 Then
Sheet.Cells(Index, "C") = Sheet.Cells(Index, "E").Value
Sheet.Cells(Index, "E").Value = ""
End If
Next
End Sub
Does it have to be a macro? How about Advanced Filter?
Your numbers are in column E. Let's assume they have a header.
E1: Number
E2: 54525841-1
E3: 454152
E4: 1365466
E5: 1254566-1
E6: 1452577-1
In a separate area of your worksheet (let's say column G) put the following criteria:
G1: Number
G2: *-*
Your advanced filter criteria would look like this:
Anything with a "-" in it will be copied to column C.
I got it to work by this:
Sub MoveDash()
x = Range("E" & Rows.Count).End(xlUp).Row
For Each Cell In Range("E2:E" & x)
If InStr(Cell, "-") <> 0 Then
Cell.Offset(, 1) = Cell
Cell.ClearContents
End If
Next Cell
end sub
You can do this without VBA, but here is an efficient way to do it using the dictionary object.
Sub MoveNumbersWithDash()
Application.ScreenUpdating = False
Dim i As Long, lastRow As Long
Dim varray As Variant
Dim dict As Object
Set dict = CreateObject("scripting.dictionary")
lastRow = Range("E" & Rows.Count).End(xlUp).Row
varray = Range("E1:E" & lastRow).Value
For i = 1 To UBound(varray, 1)
If InStr(1, varray(i, 1), "-") <> 0 Then
dict.Add i, varray(i, 1)
End If
Next
Range("C1").Resize(dict.Count).Value = _
Application.WorksheetFunction.Transpose(dict.items)
Application.ScreenUpdating = True
End Sub
How it works:
The major theme here is avoiding calls to Excel (like a for each loop). This will make the function blazing fast (especially if you have tens and thousands of rows) and more efficient. First I locate the last cell used in E then dump the entire row into a variant array in one move. Then I loop through each element, checking if it contains a "-", if it does, I add it to a dictionary object. POINT: Add the entry as the ITEM, not KEY. This makes sure that we allow for duplicates. The variable I will be unique for each entry, so I use that as the key. Then I simple dump the entire array of cells with "-" into column C.
Why Dictionary?
The dictionary object is very fast and comes with 2 really great functions: .Keys and .Items. These will return an array of all the keys or items in the dictionary, which you can use the Transpose function on to dump an entire column of values into Excel in one step. Super efficient.