I am working with a excel sheet there in column A somewhere one cell is containing workbook name (suppose my work book name is abc then "abc" is in a cell in column A probably in range"A8 OR A9" ). I want to find out that row & delete all row above that. I using below code
Option Explicit
Sub abc()
If Cells(8, 1).Value = ActiveWorkbook.Name Then
Range("A1:A7").Delete
Else
Range("A1:A8").Delete
End If
End Sub
Pls if anyone can help me to solve the issue
You can find workbook name and delete the rows above the cell containing that name as:
Sub abc()
Dim SearchCol As Long, SearchRow As Long
Dim StrName As String
Dim FoundCell As Range
'get the workbook name after removing the extension
StrName = Left(ThisWorkbook.Name, (InStrRev(ThisWorkbook.Name, ".", -1, vbTextCompare) - 1))
'search for the workbook name
Set FoundCell = Cells.Find(What:=StrName, LookAt:=xlWhole)
If FoundCell Is Nothing Then
MsgBox "File name not found in Sheet" '---> if workbook name not found in the sheet
Else
Rows(1 & ":" & FoundCell.Row - 1).EntireRow.Delete '---> delete the rows if name found
End If
End Sub
EDIT:
________________________________________________________________________________
Option Explicit
Sub SearchWrkbookName()
Dim oSht As Worksheet
Dim lastRow As Long, i As Long
lastRow = 22
Dim strSearch As String
strSearch = Left(ActiveWorkbook.Name, (InStrRev(ActiveWorkbook.Name, ".", -1, vbTextCompare) - 1)) '---> to remove extension from worksheet name
Dim aCell As Range
Set oSht = Sheets(1)
Set aCell = oSht.Range("1:" & lastRow).Find(What:=strSearch, LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
If Not aCell Is Nothing Then
Rows("1:10").Select
End If
End Sub
Edited code is working version of your code written in comments.
Related
I was (reluctantly) nice to offer to help my HR department with creating a macro that will import an exported CSV from our accounting software to our existing Excel worksheet to track sales.
I thought I finally figured it out. But, now I'm getting subscript out of range errors when I import the data.
Does anyone see something I'm missing? Thank you.
Note: the staff barely knows how to use a computer, let alone excel. I'm not going to teach them how to use power query. I just wanted to have a nice button "update" they click on... select the file and done.
Dim FileToOpen As String
FileToOpen = GetFileName
If FileToOpen <> "" Then
Dim OpenBook As Workbook
Set OpenBook = Workbooks.Open(FileToOpen)
'Find last cell in CSV file.
Dim Source_LastCell As Range
Set Source_LastCell = LastCell(OpenBook.Worksheets(1))
'Find last cell in reporting workbook.
'ThisWorkbook means the file that the code is in.
Dim Target_LastCell As Range
Set Target_LastCell = LastCell(ThisWorkbook.Worksheets("Services Data")).Offset(1)
'Copy and paste - it's a CSV so won't contain formula, etc.
With OpenBook.Worksheets(1)
.Range(.Cells(2, 1), Source_LastCell).Copy _
Destination:=ThisWorkbook.Worksheets("Services Data").Cells(Target_LastCell.Row, 1)
End With
OpenBook.Close SaveChanges:=False
End If
End Sub
Public Function GetFileName() As String
Dim FD As FileDialog
Set FD = Application.FileDialog(msoFileDialogFilePicker)
With FD
.InitialFileName = ThisWorkbook.Path & Application.PathSeparator
.AllowMultiSelect = False
If .Show = -1 Then
GetFileName = .SelectedItems(1)
End If
End With
Set FD = Nothing
End Function
Public Function LastCell(wrkSht As Worksheet) As Range
Dim lLastCol As Long, lLastRow As Long
On Error Resume Next
With wrkSht
lLastCol = .Cells.Find("*", , , , xlByColumns, xlPrevious).Column
lLastRow = .Cells.Find("*", , , , xlByRows, xlPrevious).Row
End With
If lLastCol = 0 Then lLastCol = 1
If lLastRow = 0 Then lLastRow = 1
Set LastCell = wrkSht.Cells(lLastRow, lLastCol)
On Error GoTo 0
End Function
The Subscript out of range on the below line
Set Target_LastCell = LastCell(ThisWorkbook.Worksheets("Services Data")).Offset(1)
indicates that the code was not able to find the worksheet Services Data.
I would do this differently though. Does this help? I have commented the code so you should not have any difficulty to understand it. However, if you do, then simply ask.
Option Explicit
Dim NothingToCopy As Boolean
Sub Sample()
Dim wbCsv As Workbook
Dim wsThis As Worksheet, wsThat As Worksheet
Dim FileToOpen As Variant
Dim rngFromCopy As Range, rngToCopy As Range
'~~> Services Data worksheet
Set wsThis = ThisWorkbook.Sheets("Services Data")
'~~> Browse to csv file
FileToOpen = Application.GetOpenFilename("Csv Files (*.csv), *.csv")
If FileToOpen = False Then Exit Sub
'~~> Open the csv file
Set wbCsv = Workbooks.Open(FileToOpen)
'~~> Set the sheet from where to copy
Set wsThat = wbCsv.Sheets(1)
'~~> Identify the range to copy and paste
Set rngFromCopy = wsThat.Range(wsThat.Cells(2, 1), LastCell(wsThat))
'~~> If CSV is blank then there is nothing to copy
If NothingToCopy = True Then
MsgBox "There is no data to copy"
Else
'~~> Identify where to copy
Set rngToCopy = wsThis.Cells(LastCell(wsThis).Row + 1, 1)
'~~> Copy and paste
rngFromCopy.Copy rngToCopy
'~~> Give time to excel to do the copy and paste
DoEvents
End If
'~~> Close without saving
wbCsv.Close (False)
End Sub
'~~> Function to find last row and column
Private Function LastCell(wrkSht As Worksheet) As Range
Dim wsThatLRow As Long, wsThatLCol As Long
With wrkSht
'~~> Check if the worksheet as has data
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
'~~> Get last row and column
wsThatLRow = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
wsThatLCol = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Column
Else
wsThatLRow = 1
wsThatLCol = 1
'~~> CSV has nothing to copy
NothingToCopy = True
End If
Set LastCell = .Cells(wsThatLRow, wsThatLCol)
End With
End Function
I need to create an Excel VBA Macro that is able to Loop through some Files and if it finds the given String it should fill the Excel Worksheet where I need to.
Currently it looks like this: I show a UserForm that has a TextBox where the String gets entered and a Button.
If the User clicks on that Button then the files should get looped through and if it finds the string in one of that files it should enter something new to the excel where the macro is called from.
I have searched on SO but with no Luck, I found this:
Sub LoopThroughFiles()
Dim StrFile As String
StrFile = Dir("C:\Users\xxx\xxx\xxx\test\*test*")
Do While Len(StrFile) > 0
Debug.Print StrFile
StrFile = Dir
Loop
End Sub
But this looks like it loops and looks if the filename has test in it and not if the actual file has a Value that is called "test".
Also the string that needs to be found is always in the first column of the files. And I would have to read the second column in that activeCell that I would get if the String is found and add that to the Excel where I call this Macro from.
Sincerly Faded ~
Edit:
Sub ReadDataFromAnotherWorkBook()
' Open Workbook A with specific location
Dim src As Workbook
Set src = Workbooks.Open("C:\Users\xxx\Desktop\xxx\test\x1x.xlsx", True, True)
Dim valueBookA As String
Dim valueBookB As Integer
valueBookA = src.Worksheets("Tabelle1").Cells(2, 1) ' Works but here I need to put the enteredValue and search for it
Cells(1, 1).Value = valueBookA
' Close Workbooks A
src.Close False
Set src = Nothing
' Dialog Answer
MsgBox valueBookA
End Sub
This gives me a Value from the read Excel which is good as a first start. I need to loop that to open up more files and also I need the part where I can search for the given String and get the value in that row.
Edit2:
This is what I have now but I cant get the value.. what am I doing wrong :/
Sub ReadDataFromAnotherWorkBook()
Dim SearchString As String
Dim SearchRange As Range, cl As Range
Dim FirstFound As String
Dim sh As Worksheet
' Open Workbook A with specific location
Dim src As Workbook
Set src = Workbooks.Open("C:\Users\x\Desktop\xxx\test\xxx.xlsx", True, True)
' Set Search value
SearchString = TextBox1.Value ' TEST mit TextBox Value -- works
Application.FindFormat.Clear
' loop through all sheets
For Each sh In src.Worksheets
' Find first instance on sheet
Set cl = sh.Cells.Find(What:=SearchString, _
After:=sh.Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
If Not cl Is Nothing Then
' if found, remember location
FirstFound = cl.Address
MsgBox FirstFound
' format found cell
Do
' cl.Font.Bold = True
' cl.Interior.ColorIndex = 3
Debug.Print FirstFound
MsgBox FirstFound ' Does not work..
' Debug.Print cl.Value
MsgBox cl.Value ' Also does not work -- I need the VALUE that is in the Excel Row or Column where the string gets found
' find next instance
Set cl = sh.Cells.FindNext(After:=cl)
' repeat until back where we started
Loop Until FirstFound = cl.Address
End If
Next
MsgBox "Value in Excel? : " + FirstFound 'cl.Value > Is empty..
MsgBox "SEARCHSTRING :: " + SearchString ' Gives me the right String
' Close Workbooks A ' Closes the Workbook
src.Close False
Set src = Nothing
End Sub
Use Dir to loop over the files in turn
Sub SearchFiles()
Const FOLDER = "C:\Users\xxx\Desktop\xxx\test\"
Dim wb As Workbook, wbSrc As Workbook
Dim ws As Worksheet, wsSrc As Worksheet
Dim sText As String, sFilename As String
Dim cell As Range, rng As Range
Dim n As Long, i As Long, FirstFound As String
sText = TextBox1.Value
' location of search results
Set wb = ThisWorkbook
Set ws = wb.Sheets(1) ' results of search
ws.Cells.Clear
ws.Range("A1:B1") = Array("Search Test = ", sText)
ws.Range("A2:C2") = Array("Address", "Col A", "Col B")
ws.Range("A2:C2").Font.Bold = True
i = 3
' scan all xlsx files in folder
sFilename = Dir(FOLDER & "*.xlsx")
Do While Len(sFilename) > 0
Set wbSrc = Workbooks.Open(FOLDER & sFilename, True, True)
For Each wsSrc In wbSrc.Sheets
n = n + 1
Set rng = wsSrc.Columns(1)
Set cell = rng.Find(What:=sText, _
After:=rng.Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
' text found
If Not cell Is Nothing Then
FirstFound = cell.Address
Do ' update sheet
ws.Cells(i, 1) = cell.Address(0, 0, xlA1, True)
ws.Cells(i, 2) = cell
ws.Cells(i, 3) = cell.Offset(0, 1)
i = i + 1
Set cell = rng.FindNext(After:=cell)
' repeat until back where we started
Loop Until FirstFound = cell.Address
End If
Next
wbSrc.Close
sFilename = Dir
Loop
MsgBox n & " sheets scanned", vbInformation
End Sub
Today with VBA I was trying to come with something that would modify several .xlsx files in a folder and then would copy and paste the data to another worksheet. In one of the steps, I am trying to select and delete specific columns from the other Excel file based on headers' names in VBA. The columns I would like to remove are all between the headers named "Unsubscribed" and "Webinar Question 1". The issue is that when it come to this part, it gives me an error 91:
With Worksheets("Sheet0").Range("A1:BB1")
Range(Worksheets("Sheet0").Range("A1:BB1").Find(What:="Unsubscribed", LookIn:=xlValues, MatchCase:=False).Offset(0, 1), _
Cells(Worksheets("Sheet0").Cells(Worksheets("Sheet0").Rows.Count, "A").End(xlUp).Row, _
Worksheets("Sheet0").Range("A1:BB1").Find(What:="Webinar Question 1", LookIn:=xlValues, MatchCase:=False).Offset(0, -1).Column) _
).Select
End With
The whole VBA is:
Private Sub CommandButton1_Click()
Dim my_files As String
Dim folder_path As String
Dim wb As Workbook, lRow As Long
Dim ws As Worksheet, LR As Long
Dim wsCopy As Worksheet
Dim wsDest As Worksheet
Dim lCopyLastRow As Long
Dim lDestLastRow As Long
folder_path = "C:\Users\XXXX\Desktop\"
my_files = Dir(folder_path & "\*.xlsx")
Do While my_files <> vbNullString
Set wb = Workbooks.Open(folder_path & "\" & my_files)
Set ws = wb.Worksheets("Sheet0")
'INSERT COLUMN AND PASTE FILE NAME IN ALL ROWS UNTIL LAST ROW
LR = ws.Range("B9").End(xlDown).Row
ws.Columns(1).Select
ActiveCell.EntireColumn.Insert
ws.Range("A8").Value = "Title"
ws.Range("A9:A" & LR).Value = ActiveWorkbook.Name
'INSERT COLUMN AND PASTE CELL A5 VALUE IN ALL ROWS UNTIL LAST ROW
ws.Columns(2).Select
ActiveCell.EntireColumn.Insert
ws.Range("B8").Value = "Duration"
ws.Range("B9:B" & LR).Value = ws.Range("E5").Value
'DELETE A1:A7 ROWS
ws.Range("A1:A7").EntireRow.Delete
'DELETE ORGANIZATION COLUMN
Dim rng As Range
With Worksheets("Sheet0").Range("A1:BB1")
Set rng = Worksheets("Sheet0").Range("A1:BB1").Find(What:="Organization", _
lookat:=xlWhole, MatchCase:=False)
Do While Not rng Is Nothing
rng.EntireColumn.Delete
Set rng = .FindNext
Loop
End With
'SELECT AND DELETE ALL COLUMNS BETWEEN UNSUBSCRIBED AND WEBINAR QUESTION 1
With Worksheets("Sheet0").Range("A1:BB1")
Range(Worksheets("Sheet0").Range("A1:BB1").Find(What:="Unsubscribed", LookIn:=xlValues, MatchCase:=False).Offset(0, 1), _
Cells(Worksheets("Sheet0").Cells(Worksheets("Sheet0").Rows.Count, "A").End(xlUp).Row, _
Worksheets("Sheet0").Range("A1:BB1").Find(What:="Webinar Question 1", LookIn:=xlValues, MatchCase:=False).Offset(0, -1).Column) _
).Select.Delete
End With
Set wsCopy = Worksheets("Sheet0")
Set wsDest = Workbooks("Book1.xlsm").Worksheets("Sheet2")
lCopyLastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Offset(1).Row
lDestLastRow = wsDest.Cells(wsDest.Rows.Count, "A").End(xlUp).Offset(1).Row
wsCopy.Range("A2:BB1" & lCopyLastRow).Copy _
wsDest.Range("A" & lDestLastRow)
wb.Close True
my_files = Dir()
Loop
MsgBox ("All Files Are Updated")
End Sub
For clarification, the worksheet where I have the VBA has Sheet1 and Sheet2, and the file that i am modifying has Sheet0.
What am I missing? Thank you very much and have a nice day.
I am trying to make a macro to insert a new column after the last occupied column in a sheet, then search for the column title "Part Number" in my example and Ctrl+F search for each string listed in the column, and search for it in another workbook. If the string is found in that workbook, I want "Found in 'Workbook Name'" to be filled in the same row as the part number it just searched for but the column that was created at the beginning. This is a part of a larger function so I am passing all the variables in including what's being searched for 'colTitle1', the book and sheet the values are on, 'BOM', the sheet "BOMSheet", and the document being searched 'SearchDoc".
The main function is here:
Public Sub OCCLCheck(colTitle As String, BOM As Workbook, BOMSheet As Worksheet)
Dim OCCL As Variant
Dim OpenBook As Workbook
Dim pn As Variant
Dim lastRow As Integer
'Counts number of rows in Column A with content
lastRow = WorksheetFunction.CountA(Range("A:A"))
'Flashy but not good for regular use - uncomment when not showing off product
'Application.ScreenUpdating = False
'Code for user to indicate the OCCL doc with a file path box - add something to prompt again if cancelled
OCCL = Application.GetOpenFilename(Title:="Choose OCCL File", FileFilter:="Excel Files (*.xls*),*xls*")
If OCCL <> False Then
Set OpenBook = Application.Workbooks.Open(OCCL)
'OpenBook.Sheets(1).Range("A1:E20").Copy
End If
'Application.ScreenUpdating = True
Call SearchFunc("Part Number", BOM, BOMSheet, OCCL)
End Sub
The search function is here:
Public Sub SearchFunc(colTitle1 As String, BOM As Workbook, BOMSheet As Worksheet, SearchDoc As Workbook)
Dim pn As String
Dim colTitle2 As String
Dim c As Variant
Dim lastRow As Integer
'Code to search for something on something else, made for searching across books
'Find the column with colTitle1
With ActiveSheet.UsedRange
Set c = .find(colTitle1, LookIn:=xlValues)
If Not c Is Nothing Then
pn = ActiveSheet.Range(c.Address).Offset(1, 0).Select
End If
End With
'Count number of rows to iterate search through
lastRow = WorksheetFunction.CountA(Range("A:A"))
For i = 1 To lastRow
If Cells.find(What:=Workbooks(BOM).Worksheets(BOMSheet).Range(i, 2).Value, After:=ActiveCell, _
LookIn:=Workbooks(SearchDoc).Worksheets(1).xlValues, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Activate <> .Range(i, 2).Value Then 'Write not on occl to first unoccupied column also add code to find unoccupied column before this loop
End If
End Sub
I am pretty lost at where to go now as I know what I want to do but I am new to VBA so getting the program to do it is my problem ATM, any suggestions are appreciated!
This is the error with the macro searching for const "Part Number"
[3
Most of the essential parts needed to build your solution should be within this script. I used xlWhole in the Find so that ABC1 would not match ABC10 but if part numbers are fixed length maybe xlPart is OK. Refactor into smaller subs and functions as necessary.
Option Explicit
Sub macro()
Const COL_TITLE = "Part Number"
Dim wb As Workbook, ws As Worksheet, found As Range
Dim wbSearch As Workbook, wsSearch As Worksheet
Dim rng As Range, iResultCol As Integer, iPartCol As Integer
Set wb = ThisWorkbook
Set ws = wb.Sheets("BOM D6480000005")
' headers
Set rng = ws.UsedRange.Rows(1)
' determine part number col
Set found = rng.Find(COL_TITLE, , xlValues, xlPart)
If found Is Nothing Then
MsgBox "Can't find " & COL_TITLE, vbCritical, "Search failed"
Exit Sub
End If
iPartCol = found.Column
' determine last col
iResultCol = rng.Columns.count + rng.Column
ws.Cells(1, iResultCol) = "Search Result"
Debug.Print rng.Address, iPartCol, iResultCol
Dim sFilename As String
sFilename = Application.GetOpenFilename(Title:="Choose OCCL File", FileFilter:="Excel Files (*.xls*),*xls*")
If Len(sFilename) > 0 Then
Set wbSearch = Application.Workbooks.Open(sFilename)
Else
MsgBox "No file chosen", vbExclamation
Exit Sub
End If
' find last row
Dim iLastRow As Long, iRow As Long, sPartNo As String, count As Long
iLastRow = ws.Cells(Rows.count, iPartCol).End(xlUp).Row
Debug.Print "iLastRow", iLastRow
' search each sheet
For Each wsSearch In wbSearch.Sheets
For iRow = 2 To iLastRow
sPartNo = ws.Cells(iRow, iPartCol)
If Len(sPartNo) > 0 Then
Set found = wsSearch.UsedRange.Find(sPartNo, , xlValues, xlWhole)
If found Is Nothing Then
' not found
Else
ws.Cells(iRow, iResultCol) = "Found in " & wbSearch.Name & _
" " & wsSearch.Name & _
" at " & found.Address
count = count + 1
End If
End If
Next
Next
' end
wbSearch.Close False
MsgBox count & " matches", vbInformation, "Finished"
End Sub
I have checked a bunch of different posts and can't seem to find the exact code I am looking for. Also I have never used VBA before so I'm trying to take codes from other posts and input my info for it to work. No luck yet. At work we have a payroll system in Excel. I am trying to search for my name "Clarke, Matthew" and then copy that row and paste it to the workbook I have saved on my desktop "Total hours".
CODE
Sub Sample()
Dim wb1 As Workbook, wb2 As Workbook
Dim ws1 As Worksheet, ws2 As Worksheet
Dim copyFrom As Range
Dim lRow As Long '<~~ Not Integer. Might give you error in higher versions of excel
Dim strSearch As String
Set wb1 = ThisWorkbook
Set ws1 = wb1.Worksheets("yourSheetName")
strSearch = "Clarke, Matthew"
With ws1
'~~> Remove any filters
.AutoFilterMode = False
'~~> I am assuming that the names are in Col A
'~~> if not then change A below to whatever column letter
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
With .Range("A1:A" & lRow)
.AutoFilter Field:=1, Criteria1:="=*" & strSearch & "*"
Set copyFrom = .Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow
End With
'~~> Remove any filters
.AutoFilterMode = False
End With
'~~> Destination File
Set wb2 = Application.Workbooks.Open("C:\Sample.xlsx")
Set ws2 = wb2.Worksheets("Sheet1")
With ws2
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
lRow = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
lRow = 1
End If
copyFrom.Copy .Rows(lRow)
End With
wb2.Save
wb2.Close
End Sub
SNAPSHOT
Expanding on what timrau said in his comment, you can use the AutoFilter function to find the row with your name in it. (Note that I'm assuming you have the source workbook open)
Dim curBook As Workbook
Dim targetBook As Workbook
Dim curSheet As Worksheet
Dim targetSheet As Worksheet
Dim lastRow As Integer
Set curBook = ActiveWorkbook
Set curSheet = curBook.Worksheets("yourSheetName")
'change the Field number to the correct column
curSheet.Cells.AutoFilter Field:=1, Criteria1:="Clarke, Matthew"
'The Offset is to remove the header row from the copy
curSheet.AutoFilter.Range.Offset(1).Copy
curSheet.ShowAllData
Set targetBook = Application.Workbooks.Open "PathTo Total Hours"
Set targetSheet = targetBook.WorkSheet("DestinationSheet")
lastRow = Cells.Find(What:="*", After:=Range("A1"), LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).row
targetSheet.Cells(lastRow + 1, 1).PasteSpecial
targetBook.Save
targetBook.Close
As you can see I put placeholders in for the specific setup of your workbook.
I know this is old, but for anyone else searching for how to do this, it can be done in a much more direct fashion:
Public Sub ExportRow()
Dim v
Const KEY = "Clarke, Matthew"
Const WS = "Sheet1"
Const OUTPUT = "c:\totalhours.xlsx"
Const OUTPUT_WS = "Sheet1"
v = ThisWorkbook.Sheets(WS).Evaluate("index(a:xfd,match(""" & KEY & """,a:a,),)")
With Workbooks.Open(OUTPUT).Sheets(OUTPUT_WS)
.[1:1].Offset(.[counta(a:a)]) = v
.Parent.Save: .Parent.Close
End With
End Sub