VBA to shift inventory rows up when items are deleted? - excel

Last summer, I made a pretty basic VBA for an inventory sheet. Column A lists item name, and column B, C, and D are additional info for each item listed. The inventory is two-columned (A2:D28 and G2:J28). The VBA I made makes it so that if I delete the item entry in cell A4, the info in cells B4:D4 clears automatically with it.
The question is: I've been struggling to find a way to make the cells shift up a row when the row above it is cleared, to prevent the list from accumulating empty rows as inventory items are deleted. Most examples I found online were to delete those empty cells, whereas I'd rather just clear them and keep my formatting intact.
Is there a way to shift specific cells up like that? And, it would be lovely if there was a way to shift items from the top of the second table (G2:J2) down and over to the bottom of the first table, into A28:D28.
Any help would be greatly appreciated, or even a thumb towards a relevant tutorial. Thank you!

This code is a little tricky because of the two columns you have on your screen. My code below reads the two columns into a single one, sorts blank rows to the bottom and then splits the resulting single column back into two. All of this is done without touching the cells themselves. Therefore formatting stays in place.
Option Explicit
Enum Nsp ' Table specs
' 023
' These enumerations define your table. Modify to suit
NspAnchorClm = 1 ' 1 = column A
NspTblClmCount = 4 ' number of Table Columns
NpsSpaceClmCount = 2 ' number of blank sheet columns between List Columns
NspListClmCount = 2 ' number of List Columns
NspFirstRow = 2
NspNumRows = 10 ' number of rows per List Column
End Enum
Sub ResetList()
' 023
Dim Clm() As Variant ' First sheet column of each list column
Dim ArrIn As Variant ' Input data array
Dim Cin As Long ' input column counter
Dim Rin As Long ' input row counter
Dim ArrOut As Variant ' Output data array
Dim Rout As Long ' output row counter
Dim Rng As Range ' the sheet range of varying dimension
Dim Tmp As Variant ' intermediate memory
Dim L As Integer ' List column counter
Dim C As Long ' column counter
Dim R As Long ' row counter
Clm = Array(NspAnchorClm, NspAnchorClm + NspTblClmCount + NpsSpaceClmCount)
Tmp = (NspTblClmCount * NspListClmCount) + (NpsSpaceClmCount * (NpsSpaceClmCount - 1))
Set Rng = Range(Cells(NspFirstRow, Clm(0)), _
Cells(NspFirstRow + NspNumRows - 1, Tmp))
' read all of the list into an array
ArrIn = Rng.Value
' define a single list column array
ReDim ArrOut(1 To NspNumRows * NspListClmCount, 1 To NspTblClmCount)
' transfer the data to a single list column
For L = 1 To NspListClmCount
For R = 1 To NspNumRows
Rout = (L - 1) * NspNumRows + R
For C = 1 To NspTblClmCount
Cin = Clm(L - 1) + C - 1
ArrOut(Rout, C) = ArrIn(R, Cin)
Next C
Next R
Next L
' ArrIn is cleared and re-purposed to take data from ArrOut
ReDim ArrIn(1 To UBound(ArrOut), 1 To UBound(ArrOut, 2))
Rin = 0
For Rout = 1 To UBound(ArrOut)
' skip rows where the first column is blank
If Len(ArrOut(Rout, 1)) Then
Rin = Rin + 1
For C = 1 To UBound(ArrOut, 2)
ArrIn(Rin, C) = ArrOut(Rout, C)
Next C
End If
Next Rout
' assign NspNumRows high sections of ArrIn to ArrOut
For L = 1 To NspListClmCount
ReDim ArrOut(1 To NspNumRows, 1 To NspTblClmCount)
For R = 1 To NspNumRows
For C = 1 To NspTblClmCount
ArrOut(R, C) = ArrIn(((L - 1) * NspNumRows) + R, C)
Next C
Next R
Set Rng = Cells(NspFirstRow, Clm(L - 1)).Resize(NspNumRows, NspTblClmCount)
Rng.Value = ArrOut
Next L
End Sub
I'm afraid this code will make the code you already have obsolete. If the first cell in a row is empty any content in the others will be omitted, just like your own code does.
Please pay attention to the enumeration at the top of the code. It works like a switchboard where you can enter all parameters. You can modify them as you wish. For example my code has NspNumRows = 10. Your sheet has 27 data rows per column. You will need to change that number. Just to help you find your way:-
The "AnchorColumn" is the first sheet column of the first list column. All other columns are counted from there. It need not be column A. You could leave column A blank and anchor your list in Column B (=2).
"TableColumns" are the columns that repeat in each "ListColumn". You can have more than 4 or fewer.
A "ListColumn" consists of several "TableColumns". You can have more than 2.
"SpaceColumns" are blank sheet columns inserted between "ListColumns".
NspFirstRow specifies the first data row. Above it are captions or other data which this program doesn't touch on. You could reserve the first 10 rows for something else and start your list in row 11.
By setting these 6 enumerations you can create lists of 2 or more List Columns, anywhere on the worksheets, with any number of data rows. Not all of this has been tested exhaustively. When you delete a row anywhere (in fact only the first cell of that row) the list is rewritten to move the blank row to the bottom of the last list column.

Related

Loop through name list and if names exist in selection start after last name

I apologize, this is my first crack at Excel VBA so excuse my lack of knowledge!
So I have a list of (currently) 3 names to assign to the days in column A in a repeating order in Excel.
Currently my VBA code allows it to populate the selected cells with the names in a repeating pattern (this part is good), however there are two pieces I need help with.
1- with current code, once it reaches the bottom of the names it checks for the blank box that would end that list and starts over at the tops as directed but it puts a blank cell first (see screenshot). How can I have it put next name without adding blank cell first?
2- I want to be able to (once this gets going)select the entire D column through what dates need to be filled and:
-check the lowest non blank box
-match to list and set the
counter to name below that so
it continues the name order
from the last person who was
assigned
This is code I have now:
Sub EXAMPLE()
Dim count As Integer
count = 0
For Each c In Selection
c.Value = Range("X1").Offset(count, 0).Value
If c.Value = "" Then count = -1 And c.Value = Range("x1").Offset(count, 0).Value
count = count + 1
Next c
End Sub
Sorry I know that was long, I hope this makes sense.
I think it's worth reading about arrays, as this task is ideally suited to their use. Your best bet would be to read the names into an array and then build a recurring array whose dimension is equal to the number of rows in your dates column (or selection, or however you want to define the size of the output range).
Code would look a little like this:
Dim v As Variant
Dim people() As Variant, output() As Variant
Dim rowCount As Long, i As Long, j As Long
Dim endRange As Range
'Read the list of names into an array.
'This just takes all data in column "X" -> amend as desired
With Sheet1
Set endRange = .Cells(.Rows.Count, "X").End(xlUp)
v = .Range(.Cells(1, "X"), endRange).Value
End With
'Sense check on the names data.
If IsEmpty(v) Then
MsgBox "No names in Column ""X"""
Exit Sub
End If
If Not IsArray(v) Then
ReDim people(1 To 1, 1 To 1)
people(1, 1) = v
Else
people = v
End If
'Acquire the number of rows for repeating list of names.
'This just takes all data in column "A" -> amend as desired
With Sheet1
Set endRange = .Cells(.Rows.Count, "A").End(xlUp)
rowCount = .Range(.Cells(3, "A"), endRange).Rows.Count
End With
'Sense check date data.
If endRange.Row < 3 Then
MsgBox "No dates in Column ""A"""
Exit Sub
End If
'Make a recurring array.
ReDim output(1 To rowCount, 1 To 1)
i = 1
Do While i <= rowCount
For j = 1 To UBound(people, 1)
output(i, 1) = people(j, 1)
i = i + 1
If i > rowCount Then Exit Do
Next
Loop
'Write the output to column "D"
Sheet1.Range("D3").Resize(UBound(output, 1)).Value = output

Copy Sheet Rows to New Sheet Based on Split Cell Text

I have a problem I need help with involving Excel and VBA. I know next to nothing about Excel/VBA, and I need a coding solution to help me avoid performing the extremely tedious task of doing this manually (think hundreds of lines that need to be parsed where one row could become multiple rows in a new sheet). I've been searching the web for solutions, but I just keep getting confused by the answers (because I don't know anything about VB and using it to program a macro in Excel), so I figured I'd seek help for my specific problem.
Here is the rundown: I have a spreadsheet where I need to copy rows from a source sheet to a target sheet. The source sheet has 2 columns (A & B) that can be thought of as a key/value pair where col A contains the key and col B contains the value. The problem lies with the values in col B. The values can either be a single line of text or a numbered list of different texts
What I want to do is for each row in the source:
split the values in col B to get an array of each individual value (if the value is in the form of a numbered list)
create new rows in the target sheet by looping over the split array of values such that a new row will be created where:
new row col A = source row col A key and new row col B = current iteration index from the array of split values.
if no numbered list, just copy the source row into target sheet
Source
A B
key1 1. text1
2. text2
key2 1. text3
Target
A B
key1 text1
key1 text2
key2 text3
The numbered list in a cell will be multiple lines where each line of text is prepended by a decimal and a dot. This applies to single line cells as well.
(Update) Bear in mind that the values in either col A or B are not simple text values. These are full on sentences. So, I'm not sure a simple formula is going to work.
Split Multi Line
It is unclear which line separator occurs in the multi line cells. Choose one, vbLf worked for me.
Adjust the values in the constants section to fit your needs.
The Code
Sub SplitMultiLine()
Const cSheet1 As Variant = "Sheet1" ' Source Worksheet Name/Index
Const cFirstR As Integer = 1 ' Source First Row Number
Const cFirstC As Variant = "A" ' Source First Column Letter/Number
Const cLastC As Variant = "C" ' Source Last Column Letter/Number
Const cMulti As Integer = 2 ' Multi Column
Const cSplit As String = vbLf ' Split Char(vbLf, vbCrLf, vbCr)
Const cDot As String = "." ' Dot Char (Delimiter)
Const cSheet2 As Variant = "Sheet1" ' Target Worksheet Name/Index
Const cTarget As String = "E1" ' Target First Cell Address
Dim vntS As Variant ' Source Array
Dim vntSplit As Variant ' Split Array
Dim vntT As Variant ' Target Array
Dim lastR As Long ' Source Last Row
Dim i As Long ' Source Array Row Counter
Dim j As Integer ' Source/Target Array Column Counter
Dim k As Long ' Target Array Row Counter
Dim m As Integer ' Split Array Row Counter
' Paste Source Range into Source Array.
With Worksheets(cSheet1)
lastR = .Cells(.Rows.Count, cFirstC).End(xlUp).Row
vntS = .Range(.Cells(cFirstR, cFirstC), .Cells(lastR, cLastC))
End With
' Count the number of rows in target array.
For i = 1 To UBound(vntS)
k = k + UBound(Split(vntS(i, cMulti), cSplit)) + 1
Next
' Write from Source to Target Array.
ReDim vntT(1 To k, 1 To UBound(vntS, 2))
k = 0
For i = 1 To UBound(vntS)
k = k + 1
vntSplit = Split(vntS(i, cMulti), cSplit)
For m = 0 To UBound(vntSplit)
If InStr(vntSplit(m), cDot) > 0 Then
vntT(k, cMulti) = Trim(Right(vntSplit(m), Len(vntSplit(m)) _
- InStr(vntSplit(m), cDot)))
Else
vntT(k, cMulti) = vntSplit(m)
End If
For j = 1 To UBound(vntS, 2)
If j <> cMulti Then
vntT(k, j) = vntS(i, j)
End If
Next
k = k + 1
Next
k = k - 1
Next
' Paste Target Array into Target Range calculated from Target Frist Cell.
With Worksheets(cSheet2).Range(cTarget)
.Resize(UBound(vntT), UBound(vntT, 2)) = vntT
End With
End Sub
An Over-Commenting
Sub SplitMultiLineOverCommented()
Const cSheet1 As Variant = "Sheet1" ' Source Worksheet Name/Index
Const cFirstR As Integer = 1 ' Source First Row Number
Const cFirstC As Variant = "A" ' Source First Column Letter/Number
Const cLastC As Variant = "C" ' Source Last Column Letter/Number
Const cMulti As Integer = 2 ' Multi Column
Const cSplit As String = vbLf ' Split Char(vbLf, vbCrLf, vbCr)
Const cDot As String = "." ' Dot Char (Delimiter)
Const cSheet2 As Variant = "Sheet1" ' Target Worksheet Name/Index
Const cTarget As String = "E1" ' Target First Cell Address
Dim vntS As Variant ' Source Array
Dim vntSplit As Variant ' Split Array
Dim vntT As Variant ' Target Array
Dim lastR As Long ' Source Last Row
Dim i As Long ' Source Array Row Counter
Dim j As Integer ' Source/Target Array Column Counter
Dim k As Long ' Target Array Row Counter
Dim m As Integer ' Split Array Row Counter
' Paste Source Range into Source Array.
With Worksheets(cSheet1)
' The last row of data is usually calculated going from the bottom up,
' it is like selecting the last cell and pressing CTRL UP and returning
' =ROW() in Excel.
lastR = .Cells(.Rows.Count, cFirstC).End(xlUp).Row
' Paste a range into an array actually means copying it. The array
' created is a 1-based 2-dimensional array which has the same number
' of rows and columns as the Source Range.
vntS = .Range(.Cells(cFirstR, cFirstC), .Cells(lastR, cLastC))
End With
' Count the number of rows in Target Array.
' You refer to the last row of the array with UBound(vntS) which is short
' for UBound(vntS, 1) which reveals that it is referring to the first
' dimension (rows).
For i = 1 To UBound(vntS)
' We are splitting the string by cSplit which is the line
' separator (delimiter). When you enter something into a cell and
' hold left Alt and press ENTER, the vbLf character is set in place
' of the line separator. But the data may have been imported from
' another system that uses another line separator. When splitting the
' string, a 0-based array is 'created' and its UBound is the last
' row, but since it is 0-based we have to add 1.
k = k + UBound(Split(vntS(i, cMulti), cSplit)) + 1
Next
' Write from Source to Target Array.
' After we have calculated the number of rows, we have to resize the
' Target Array. To avoid confusion, I always use '1 To' to be certain that
' it is a 1-based array. Since the number columns of the Source Array and
' the Target Array is the same, we use the UBound of the Source Array to
' resize the second dimension of the Target Array - UBound(vntS, 2) where
' 2 is indicating the second dimension, columns.
ReDim vntT(1 To k, 1 To UBound(vntS, 2))
' We will use again k as the row counter since its value is no more
' needed. This is what I have many times forgotten, so maybe it is
' better to use a different variable.
k = 0
' Loop through the columns of Source Array.
For i = 1 To UBound(vntS)
' Increase the row of Target Array or e.g. align it for writing.
k = k + 1
' Split the string (lines) in the Multi Column into the 0-based
' Split Array.
vntSplit = Split(vntS(i, cMulti), cSplit)
' Loop through the values of the Split Array
For m = 0 To UBound(vntSplit)
' Check if value contains cDot. The Instr function returns 0 if
' a string has not been found, it's like =FIND(".",A1) in Excel,
' except that Excel would return an error if not found.
If InStr(vntSplit(m), cDot) > 0 Then
' If cDot was found then write the right part after cDot
' to the current row of column cMulti but trim the result
' (remove space before and after.
' It's like =TRIM(RIGHT(A1,LEN(A1)-FIND(".",A1))) in Excel.
vntT(k, cMulti) = Trim(Right(vntSplit(m), Len(vntSplit(m)) _
- InStr(vntSplit(m), cDot)))
Else
' If cDot was not found then just write the value to the
' current row.
vntT(k, cMulti) = vntSplit(m)
End If
' Loop through all columns.
For j = 1 To UBound(vntS, 2)
If j <> cMulti Then
' Write to other columns (Not cMulti)
vntT(k, j) = vntS(i, j)
End If
Next ' Next Source/Target Array Column
' Increase the current row of Target Array before going to next
' value in Split Array.
k = k + 1
Next ' Next Split Array Row
' Since we have increased the last current row but haven't written to
' it, we have to decrease one row because of the "k = k + 1" right below
' "For i = 1 To UBound(vntS)" which increases the row of Target Array
' for each next row in Source Array.
k = k - 1
Next ' Next Source Array Row
' Paste Target Array into Target Range calculated from Target Frist Cell.
' Like we pasted a range into an array, we can also paste an array into
' a range, but it has to be the same size as the array, so by using
' the Resize method we adjust the Target Range First Cell to the Target
' Range, using the last row and column of the Target Array. Again,
' remember UBound(vntT) is short for UBound(vntT, 1) (rows).
With Worksheets(cSheet2).Range(cTarget)
.Resize(UBound(vntT), UBound(vntT, 2)) = vntT
End With
End Sub
You can do this with two formulas.
I'm assuming your data is in Sheet1.
For the first columns, use the following formula:
=IF(ISBLANK(Sheet1!A2),A1,Sheet1!A2)
For the second one use:
=IFERROR(RIGHT(Sheet1!B2,LEN(Sheet1!B2)-FIND(". ",Sheet1!B2)-1),Sheet1!B2)
And populate down.
edit:
The first formula will look at the corresponding cell in Sheet1, column A. If it is blank, it will take the value of the cell above where the formula is. If it isn't blank, it will take the value of the cell in Sheet1, column A that it just checked.
The second formula looks for the string ". " in the cells in Sheet1 column B and removes it and everything to the left of it from the text. If the string in question (". ") is not found (meaning there is no numbering in that given cell) it would return an error, so the whole thing is wrapped in an IFERROR statement which returns the value of the cell in Sheet1 column B if it is triggered.

VBA Code to link checkboxes to certain columns

I have three columns E(insufficient QTY) F(Too Slow) and G(Not Listed) They all have checkboxes in them. I need to link
E to H
F to I
G to J
The following code works nicely if there was only 1 column of checkboxes but I don't know how to improve the code to run by checkboxes in a certain column. Right now it just searches the entire sheet for checkboxes and links them to the desired column.
Sub LinkChecks()
'Update 20150310
i = 2
For Each cb In ActiveSheet.CheckBoxes
cb.LinkedCell = Cells(i, "I").Address
i = i + 1
Next cb
End Sub
Edit
Ok... let's try again:
Since the Check Box object does not have cell information for the cell it's located in, we will have to use the Offset property more creatively.
Since we know there are 3 check boxes per row, we can find the total number of check boxes and divide by 3 to find out how many rows there are.
Then by setting a Range to a single cell at the top of column "E", you can use the offset property on that cell.
Assuming you placed your Check Boxes on the sheet down column "E" sequentially, and then down column "F" next, then down "G", we can reset the offsets once we get to the last row of each column. (If you place the check boxes on the sheet in row order, you'll have to invert the loop logic.) (If you placed the check boxes on randomly, you are out of luck and will have to set your linked cells manually.)
Sub LinkChecks()
Dim rng As Range
Dim strColumn As String
Dim i As Integer
Dim intCount As Integer
Dim intRowCnt As Integer
Dim intRowOffset As Integer
Dim intColumnOffset As Integer
Dim dCnt As Double
i = 1 ' Your initial row offset
intCount = 0 ' A counter for total number of check boxes
intRowCnt = 0 ' A Row counter to find last row
intRowOffset = i ' Current Row offset from initial rng cell
intColumnOffset = 3 ' Current Column Offset (3 over from first check box column)
strColumn = "E" ' Set a starting Column of your first check box
Set rng = ActiveSheet.Cells(1, strColumn) ' Set initial rng cell
' Count how many check boxes are on the active sheet
For Each cb In ActiveSheet.CheckBoxes
intCount = intCount + 1
Next cb
' Since you know you have 3 check boxes per row,
' you can divide by 3 to get your row count
dCnt = intCount / 3
' *** Put test for remainder problems here ***
For Each cb In ActiveSheet.CheckBoxes
cb.LinkedCell = rng.Offset(intRowOffset, intColumnOffset).Address
intRowOffset = intRowOffset + 1
' Increment your row count until you get to last row
intRowCnt = intRowCnt + 1
If intRowCnt >= dCnt Then
intRowCnt = 0 ' Reset you row counter
intColumnOffset = intColumnOffset + 1 ' Increment Offset to the next column
intRowOffset = i ' Reset Row offset back to top row
End If
Next cb
End Sub
As long as your check boxes were placed on the sheet down each column, the above program should find the correct Linked Cell for each box.
If they were placed in a different order, then at least this code shows you how to set an initial Range cell and how you can reference other cells with an offset.
Hopefully this code or a combination of these ideas will help you with your problem. :)

Excel transpose formula

I've been wraping my head around it for some time and just don't know how to approach this problem. My table consists of groups of data which I want to transpose from rows to columns. Every row has an index number in first column and all of the rows in one group have the same index.
1 a
1 b
1 c
1 d
1 e
1 f
1 g
1 h
2 as
2 bs
2 cs
5 ma
5 mb
5 mc
5 md
and I want my final result to be:
1 a b c d e f g h
2 as bs cs
5 ma mb mc md
is it possible to do this with formulas or do I have to do it in VBA?
You can also do this using a macro. Here is one method.
To enter this Macro (Sub), alt-F11 opens the Visual Basic Editor.
Ensure your project is highlighted in the Project Explorer window.
Then, from the top menu, select Insert/Module and
paste the code below into the window that opens.
To use this Macro (Sub), alt-F8 opens the macro dialog box. Select the macro by name, and RUN.
Option Explicit
Sub ReArrange()
Dim vSrc As Variant, rSrc As Range
Dim vRes As Variant, rRes As Range
Dim I As Long, J As Long, K As Long
Dim lColsCount As Long
Dim Col As Collection
'Upper left cell of results
Set rRes = Range("D1")
'Assume Data in A1:Bn with no labels
Set rSrc = Range("a1", Cells(Rows.Count, "A").End(xlUp)).Resize(columnsize:=2)
'Ensure Data sorted by index number
rSrc.Sort key1:=rSrc.Columns(1), order1:=xlAscending, key2:=rSrc.Columns(2), order2:=xlAscending, MatchCase:=False, _
Header:=xlNo
'Read Source data into array for faster processing
' compared with going back and forth to worksheet
vSrc = rSrc
'Compute Number of rows = unique count of index numbers
'Collection object can only have one entry per key
' otherwise it produces an error, which we skip
Set Col = New Collection
On Error Resume Next
For I = 1 To UBound(vSrc)
Col.Add Item:=vSrc(I, 1), Key:=CStr(vSrc(I, 1))
Next I
On Error GoTo 0
'Compute Maximum Number of columns in results
' Since there is one entry per Index entry, maximum number of
' columns will be equal to the Index that has the most lines
' So we iterate through each Index and check that.
For I = 1 To Col.Count
J = WorksheetFunction.CountIf(rSrc.Columns(1), Col(I))
lColsCount = IIf(J > lColsCount, J, lColsCount)
Next I
'Set up Results array
' Need to add one to the columns to account for the column with the Index labels
ReDim vRes(1 To Col.Count, 1 To lColsCount + 1)
'Now populate the results array
K = 1
For I = 1 To Col.Count
vRes(I, 1) = vSrc(K, 1)
J = 2
Do
vRes(I, J) = vSrc(K, 2)
J = J + 1: K = K + 1
If K > UBound(vSrc) Then Exit Do
Loop Until vSrc(K, 1) <> vRes(I, 1)
Next I
'Set the results range to be the same size as our array
Set rRes = rRes.Resize(rowsize:=UBound(vRes, 1), columnsize:=UBound(vRes, 2))
'Clear the results range and then copy the results array to it
rRes.EntireColumn.Clear
rRes = vRes
'Format the width. Could also format other parameters
rRes.EntireColumn.ColumnWidth = 10
End Sub
Yes its possible. You would need the following functions:
IF
MATCH
ISNA
INDEX
Assume you have the data in sheet 1 in columns A and B:
C1:
place the value "1" in cell C1
C2:
=C1+1
drag down as much as needed
D1
=MATCH(C1,A:A, 0)
Drag down as much as cell C2
E1
=MATCH(C1,A:A, 1)
Drag down as much as cell C2
Sheet 2:
Now place the following formulas in cell A1 in sheet2:
=IF(ISNA(Sheet1!$D1), "", IF(Sheet1!$D1="", "", IF(COLUMN()-1+Sheet1!$D1 <=Sheet1!$E1, INDEX(Sheet1!$B:$B, COLUMN()-1+Sheet1!$D1), "")))
Drag / Copy it to as many cells as needed:
Result:
Also I have an article on my blog about the INDEX function. It might help Excel INDEX Function.
You can also download the complete file here.

Select the top 10 maximum values

I need help writing a loop that finds the 10 highest values in column "F". For each of the 10 highest values that are selected, I want to paste that value (as well as the associated values in column C, D, and E) in another spreadsheet.
Thanks
The Aggregate function is designed to ignore error values (among other things). Here's a SUB Aggregate and Large to get a threashold to triggger your copy code
Sub GetTop10(r As Range)
Dim v As Variant
Dim t As Variant
Dim i As Long
' 14 = function LARGE
' 6 = ignore error values
' 10 = get 10'th largest value
t = Application.WorksheetFunction.Aggregate(14, 6, r, 10)
v = r
For i = 1 To UBound(v, 1)
If Not IsError(v(i, 1)) Then
If v(i, 1) >= t Then
' copy r.cells(i,-2).resize(1,4) to your other sheet
End If
End If
Next
End Sub

Resources