I am fairly new to VBA, but I am decently experienced with Java and Python. I was given the task of organizing a nearby school's grading standards on an excel spreadsheet, and I wanted to give the teachers the option to reorganize the spreadsheet at the click of a button, so I have decided to use VBA.
This is what I have so far for my code (it's sloppy, but I will clean it up once I get it working well):
Private Sub Workbook_Open()
' Initialize Variables
Dim i%, j%
Dim vTemp$, StdList$
Dim Stds As Collection
Set Stds = New Collection
' Compute the index of the last Standard on Test worksheet
lastStd = Sheet3.Range("B" & Rows.Count).End(xlUp).Row
' Remove Duplicates from the Standards and remove commas
On Error Resume Next
For i = 2 To lastStd
Stds.Add (Sheet3.Cells(i, 2)), Chr(34) & (Sheet3.Cells(i, 2)) & Chr(34)
Next i
On Error GoTo 0
For i = 1 To Stds.Count
Stds.Item(i) = Replace(Stds.Item(i), ",", Chr(130))
Next i
' Sort the Standards Alphabetically (using Bubble Sort)
For i = 1 To Stds.Count - 1
For j = i + 1 To Stds.Count
If Stds(i) > Stds(j) Then
vTemp = Stds(j)
Stds.Remove (j)
Stds.Add vTemp, vTemp, i
End If
Next j
Next i
' Reinitialize Cell Data
Sheet8.Range("A1:J100").Clear
For i = 1 To Stds.Count
Sheet8.Cells(i, 1).Value = Stds.Item(i)
Next i
' Output the Standards to the Excel Spreadsheet
For i = 1 To Stds.Count
StdList = StdList & Stds.Item(i) & ","
Next i
With ThisWorkbook.Sheets("Sheet1").Range("F3").Validation
.Delete
.Add Type:=xlValidateList, _
AlertStyle:=xlValidateAlertStop, _
Formula1:=StdList
End With
End Sub
The code executes when I open up the spreadsheet, but I get "Run Time Error 1004 'Application Defined or Object Defined Error'" upon execution. The goal is to have the code search through the grading standards, enumerate a collection, remove the duplicates, sort the standards alphabetically, and replace the commas with a character that looks like a comma so that I can convert the collection to a list and place that list into a drop down list somewhere on the spreadsheet. When I select the debug option, these three lines are highlighted:
.Add Type:=xlValidateList, _
AlertStyle:=xlValidateAlertStop, _
Formula1:=StdList
My guess is that I am either struggling with the syntax, or there is a type mismatch somewhere in there that I am not seeing; both of which are likely.
You can run debug.print on your parameters - that would point out that xlValidateAlertStop was empty
It should actually be xlValidAlertStop
Another solution to avoide one loop is to use Scripting.Dictionary object
Sub SubDicList()
'With a CD's Collection DataBase
'|Group And singer |Album Title| Date| Price| Qty | Web Link|
'MainList is DataField of Group and singer
'SubList is The Dropdown Validation List to get from DataField of Album TiTle
Dim ws As Worksheet, nRow As Long, i As Integer
Dim MainList As Range, MainData As Range, DataSelected As Variant
Dim SubList As String, SubData As Variant
Dim Dic As Object
Set Dic = CreateObject("Scripting.Dictionary")
Set ws = Worksheets("Data")
DataSelected = Range("K4") 'Data validation from a dropdown list with MainList Content
nRow = ws.Cells(2 ^ 20, 2).End(xlUp).Row 'Last row
Set MainList = ws.Range(ws.Cells(5, 2), ws.Cells(nRow, 2)) 'MainList as Range (Group and Singers)
For Each MainData In MainList
If MainData.Value = DataSelected Then
SubData = MainData.Offset(0, 1) 'SubData is Album Title of Group and singers
If Not Dic.Exists(SubData) Then 'If Album Title is not in the Sripting Dictionary
Dic.Add SubData, CStr(SubData) 'Add Allbum Titel to the Sripting Dictionary
SubList = SubList & SubData & "," 'Prepare the Validation List for each step of loop
End If
End If
Next MainData
With Range("L4").Validation 'Range("L4"): Where to put the Data Validation Dropdown List
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:=SubList
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
Set Dic = Nothing
Set MainList = Nothing
Set ws = Nothing
End Sub
Related
On the first sheet, I have a Table called "Inventory" that has two columns. The First Column contains all the barcodes and the Second Column contains the Item's Description.
Example:
Barcode
Description
1111
Item 1
2222
Item 2
3333
Item 3
4444
Item 4
2222
Item 222
On the second sheet, I have a table called "Out" where you enter the barcode in a cell in the first column and the adjacent cell in the second column displays the Item's Description based on the barcode entered with the help of the formula vlookup.
At this point, I have encountered no problems and everything works great.
But some of the barcodes have multiple different descriptions (as shown in the example in the case of barcode "2222") and I want to be able to change the item's description with a drop down list that only shows the different items which have the same barcode.
For example if I enter the barcode "2222" the adjacent cell will show "Item 2". I want to install a drop down list on the Description Cell that will show Item 2 and Item 222 and select the one I want.
Can you help me solve this problem?
Sub Data_Val()
dim Inventory_Sheet, Out_Sheet as Worksheet
set Inventory_sheet = Thisworkbook.worksheets("Inventory")
set Out_Sheet = thisworkbook.worksheets("Out")
Out_Sheet.activate
Range("B2").Select
With Selection.Validation
.Delete
.Add Type:=xlValidateList, _
AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, _
Formula1:="='Inventory'!$B$2:$B$6"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End With
End Sub
Please, use the next a approach:
The following code needs a reference to 'Microsoft Scripting Runtime'. It can be handled without it, using late binding, but you will not benefit of the intellisense suggestions. Use the next code to automatically create it:
Sub addScrRunTimeRef()
'Add a reference to 'Microsoft Scripting Runtime':
'In case of error ('Programmatic access to Visual Basic Project not trusted'):
'Options->Trust Center->Trust Center Settings->Macro Settings->Developer Macro Settings->
' check "Trust access to the VBA project object model"
Application.VBE.ActiveVBProject.References.AddFromFile "C:\Windows\SysWOW64\scrrun.dll"
End Sub
Run the code and save the workbook!
Create a Public variable in a standard module:
Public dictDescript As Scripting.Dictionary
Copy the next code in the "Inventory" sheet code module:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range, arrT, i As Long
Set rng = Me.ListObjects("Inventory").DataBodyRange
If Not Intersect(Target, rng) Is Nothing Then
Set dictDescript = New Scripting.Dictionary
arrT = rng.value 'to make code faster
For i = 1 To UBound(arrT)
'update the dictionary
If Not dictDescript.Exists(arrT(i, 1)) Then
dictDescript.Add arrT(i, 1), arrT(i, 2)
Else
dictDescript(arrT(i, 1)) = dictDescript(arrT(i, 1)) & "|" & arrT(i, 2)
End If
Next i
End If
End Sub
It will update the necessary dictionary when something is modified in the table.
Copy the next code in the sheet keeping "Out" table code module:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rngO As Range
Set rngO = Me.ListObjects("Out").DataBodyRange.Columns(1)
If Not Intersect(Target, rngO) Is Nothing Then
If dictDescript Is Nothing Then 'if nothing has been placed in the dictionary:
Dim rng As Range, arrT, i As Long, strCondition As String
Set rng = Worksheets("Inventory").ListObjects("Inventory").DataBodyRange
Set dictDescript = New Scripting.Dictionary
arrT = rng.value 'to make code faster
For i = 1 To UBound(arrT)
'update the dictionary
If Not dictDescript.Exists(arrT(i, 1)) Then
dictDescript.Add arrT(i, 1), arrT(i, 2)
Else
dictDescript(arrT(i, 1)) = dictDescript(arrT(i, 1)) & "," & arrT(i, 2)
End If
Next i
End If
'return the validation list from the dictionary:
strCondition = dictDescript(Target.value)
If strCondition = "" Then 'if a wrong string has been inputed (not one of the barcodes in Inventory sheet)
MsgBox Target.value & " barcode, does not exist in ""Inventory"" sheet...", vbInformation, _
"No appropriate barcode input"
Target.Offset(0, 1).Validation.Delete: Target.Offset(0, 1).value = ""
Exit Sub
End If
'create the validation:
Target.Offset(0, 1).value = ""
With Target.Offset(0, 1).Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, Formula1:=strCondition
.IgnoreBlank = True
.InCellDropdown = True
.ShowInput = True
.ShowError = True
End With
End If
End Sub
Play with your barcodes and send some feedback.
I created named ranges in excel, naming of range is based on key values on the sheet2.
Now I created dropdown list on another sheet1, with formula usage - INDIRECT, based again on key, on sheet1 . How I can add blank/special symbol to dropdown list? I cant add empty cell between the sorted items on sheet.
Sheet2:
I have 2 named ranges based on MAT/AE columns, first one is range of C2:C4, and the next one is C5:C6.
And there I have Sheet1, I using data validation , concatenating MAT1&AE11 using formula INDIRECT and i have values based on ranges from Sheet2.
SO my question is, how to add blank/special character to this list?
Code for ranges :
Sub Start()
lf_index_row = 1
lf_name_space_row = 2
gf_namespace = ""
Do
lf_index_row = lf_index_row + 1
lf_material = Sheets(gc_data).Cells(lf_index_row, 1)
lf_location = Sheets(gc_data).Cells(lf_index_row, 2)
gf_new_namespace = "X" & lf_material & lf_location
If gf_new_namespace = "X" Then
If gf_namespace = "" Then
End
Else
'create namespace
Set lf_range = Range(Cells(lf_start_number, 3), Cells(lf_end_number, 3))
lf_range.Select
Range(Cells(lf_start_number, 3), Cells(lf_end_number, 3)).Select
ActiveWorkbook.Names.Add Name:=gf_namespace, RefersTo:=lf_range
End
End If
End If
If gf_namespace <> gf_new_namespace Then
If gf_namespace = "" Then
'initialize newnamespace
gf_namespace = gf_new_namespace
lf_start_number = lf_index_row
lf_end_number = lf_index_row
Else
'create namespace
Set lf_range = Range(Cells(lf_start_number, 3), Cells(lf_end_number, 3))
lf_range.Select
Range(Cells(lf_start_number, 3), Cells(lf_end_number, 3)).Select
ActiveWorkbook.Names.Add Name:=gf_namespace, RefersTo:=lf_range
'initialize newnamespace
gf_namespace = gf_new_namespace
lf_start_number = lf_index_row
lf_end_number = lf_index_row
End If
Else
lf_end_number = lf_index_row
End If
Loop
End Sub
Indirect formula :
Definition of first named range :
If the list is in Range("A1:A10") this is how to achieve a validation list with only one empty position:
with the following code:
Sub TestMe()
Dim list1 As Range
Dim validationFormula As String
Set list1 = Range("A1:A10")
Dim myCell As Range
For Each myCell In list1
If Not IsEmpty(myCell) Then
validationFormula = validationFormula & myCell.Value2 & ","
End If
Next
validationFormula = validationFormula & Chr(160)
With Range("B5").Validation
.Delete
.Add Type:=xlValidateList, Operator:=xlBetween, Formula1:=validationFormula
.IgnoreBlank = False
.InCellDropdown = True
End With
End Sub
What is the idea of the code? The validation string is made in the validationFormula, through concatenating all the cells that are Not IsEmpty(). Once the validationFormula is ready, Chr(160) is added to it, to make sure that we have the empty cell available as well.
It can be added even like this: validationFormula = Chr(160) & "," & validationFormula, if you need to have it at the first position:
Once the validationFormula string is prepared, we can allow ourself to write .IgnoreBlank = True, as far as there is only one blank in the list - the one we need.
Credits to this guy here, for the looping idea - https://superuser.com/questions/1254754/data-validation-from-2-lists-excel-2010
Check if cell with row-value = 3 and column-value = 4 is blank with the following:
Set objExcel = CreateObject("Excel.Application")
Set excelInput = objExcel.Workbooks.Open("myfile")
If excelInput.Sheets("Sheet1").Cells(3, 4) <> vbNullString Then
'do the thing
End If
The above code is VBScript but it should work. If not, its almost identical in VBA.
I'm sure there's an obvious answer here, but I'm stuck. This part in particular is throwing 424: Object Required. The really odd part, to me, is that it does successfully append 0s to the column, but then halts, and doesn't continue.
If cellLen < 9 Then
Set C.Value = 0 & C.Value
End If
The rest of the code is below for clarity. In case it's not clear, this is the intended code flow:
Grabs named fields
Copies those columns to a new sheet
Renames them and deletes the original sheet
Creates some new sheets for use with a different script
Searches for missing leading 0s in a specific column
Adds them back in (this is the part the breaks)
Deletes rows where that specific column's cell value is 0
Pulls that cleaned-up column out to a new file and saves it
Sub Cleanup_Mapwise_Import()
Dim targetCols As Variant
Dim replColNames As Variant
Dim index As Integer
Dim found As Range
Dim counter As Integer
Dim headerIndex As Integer
Dim question As Integer
Dim rowCount As Variant
Dim colNum As Variant
Dim colLetter As Variant
Dim C As Range
Dim cellLen As Integer
' Add or remove fields to be copied here
targetCols = Array("gs_account_number", "gs_meter_number", "gs_amr_identification", _
"gs_amr_phase", "gs_city", "Name", "Phase", _
"gs_rate_schedule", "gs_service_address", _
"gs_service_map_location", "gs_service_number")
' Put the same fields from above in the desired order here, with the desired name
replColNames = Array("Acct #", "Meter #", "AMR ID", "AMR Phase", "City", _
"Name", "Phase", "Rate", "Address", "Srv Map Loc", "Srv Num")
counter = 1
ActiveSheet.Range("A1").Select
' This counts the number of columns in the source array and sets the index to that value
For index = LBound(targetCols) To UBound(targetCols)
Set found = Rows("1:1").Find(targetCols(index), LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByColumns, SearchDirection:=xlNext, MatchCase:=False)
' This is basically an insertion sort, and ends up with the columns in A:K
If Not found Is Nothing Then
If found.Column <> counter Then
found.EntireColumn.Cut
Columns(counter).Insert shift:=xlToRight
Application.CutCopyMode = False
End If
counter = counter + 1
End If
Next index
' There is a more dynamic way of doing this, using index
' As it is, replace A:K with the range of actual data
' PROTIP: targetCols is 1-indexed, and has 11 entries -->
' A:K encompasses that entire array -->
' Add/subtract 1 for each entry you add/remove
Range("A:K").Cut
Set TargetSheet = Sheets.Add(After:=Sheets(Sheets.Count))
TargetSheet.Name = "Contributors"
Range("A:K").Insert
question = MsgBox("Do you want to delete the original sheet?", vbYesNo + vbQuestion, "Delete Sheet")
If question = vbYes Then
Sheets(1).Activate
Sheets(1).Delete
Else
End If
Sheets.Add.Name = "Data"
Sheets("Contributors").Move After:=Sheets("Data")
Sheets.Add.Name = "Graph"
Sheets("Graph").Move After:=Sheets("Contributors")
Sheets("Data").Activate
Range("A1").Value = "Date/Time"
Range("B1").Value = "kW"
Range("C1").Value = "Amps"
' Yes, counter is 0-indexed here, and 1-indexed previously
' headerIndex does an absolute count of 0 To # targetCols, whereas index is relative
' If you change these, there is a non-zero chance that the For will throw an error
counter = 0
Sheets("Contributors").Activate
ActiveSheet.Range("A1").Select
For headerIndex = 0 To (UBound(targetCols) - LBound(targetCols))
ActiveCell.Value = replColNames(counter)
' If you don't use a Range, it fits columns based on headers, which isn't large enough
' A1:Z500 is a big enough sample to prevent that
ActiveCell.Range("A1:Z500").Columns.AutoFit
ActiveCell.Offset(0, 1).Select
counter = counter + 1
Next headerIndex
' Find column number with meters numbers, then assign its corresponding letter value
colNum = Application.WorksheetFunction.Match("Meter #", Range("A1:ZZ1"), 0)
colLetter = (Split(Cells(, colNum).Address, "$")(1))
rowCount = Range(colLetter & Rows.Count).End(xlUp).Row
'Range(colLetter & "2:" & colLetter & rowCount).Select
'Selection.SpecialCells(xlCellTypeBlanks).Select
'Selection.Delete Shift:=xlUp
' Meter numbers are 9 digits, so if one is shorter, assume a trimmed leading 0 and append it
For Each C In Range(colLetter & "2:" & colLetter & rowCount).Cells
' If cell type isn't set to text, the 0s will be non-visible, which while not an issue for the CSV, is confusing
' Note that this does not persist, as CSVs have no way of saving Excel's formatting
C.NumberFormat = "#"
cellLen = Len(C.Value)
If C.Value = "0" Or cellLen = 0 Then
C.Delete shift:=xlUp
End If
If cellLen < 9 Then
Set C.Value = 0 & C.Value
End If
Next C
question = MsgBox("Do you want to create a CSV file with meter numbers for use with MDMS?", vbYesNo + vbQuestion, "MDMS File")
If question = vbYes Then
' Call CopyMeters for use with MDMS
Sheets("Contributors").Activate
CopyMeters
Else
End If
End Sub
Sub CopyMeters()
Dim index As Integer
Dim fileSaveName As Variant
Dim rowCount As Variant
Dim colNum As Variant
Dim colLetter As Variant
Dim cellLen As Integer
colNum = Application.WorksheetFunction.Match("Meter #", Range("A1:ZZ1"), 0)
colLetter = (Split(Cells(, colNum).Address, "$")(1))
rowCount = Range(colLetter & Rows.Count).End(xlUp).Row
MsgBox ("Filename will automatically be appended with ""Meter List""")
fileSaveName = Split(ActiveWorkbook.Name, ".")
fileSaveName = fileSaveName(LBound(fileSaveName)) & " Meter List"
'For Each C In Range(colLetter & "2:" & colLetter & rowCount)
' C.NumberFormat = "#"
' cellLen = Len(C)
' If C.Value = "0" Or cellLen = 0 Then
' C.Delete shift:=xlUp
' End If
' If cellLen < 9 And cellLen <> 0 Then
' C.Value = "0" & C.Value
' End If
'Next C
Range(colLetter & "1:" & colLetter & rowCount).EntireColumn.Copy
Set newBook = Workbooks.Add
newBook.Worksheets("Sheet1").Range("A1").PasteSpecial (xlPasteAll)
Selection.Replace What:="0", Replacement:="", LookAt:=xlWhole, _
SearchOrder:=xlByColumns, MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
Selection.Columns.AutoFit
newBook.SaveAs Filename:=fileSaveName, FileFormat:=xlCSV, CreateBackup:=False
End Sub
The error message is telling you that C is not an object. Therefore, you do not need the Set statement. Change your code to this:
If cellLen < 9 Then
C.Value = 0 & C.Value
End If
Why not just change the numberformat on the range? Or use a function for the value? A function would be something like
Public Function FormatValues(ByVal Input as String) as String
If Input <> vbNullString Then FormatValues = Format(Input, "000000000")
End Function
And it would be called like:
C.Value = FormatValues(C.Value)
But, if you're strictly interested in what the value looks like, and not as much as what the value is (since the leading zero will only be retained for strings) you could do something like this:
Public Sub FixFormats()
ThisWorkbook.Sheets("SomeSheet").Columns("A").NumberFormat = "000000000")
End Sub
This would format Column A of Worksheet "SomeSheet" to be of the format "0000000" which means numbers would look like "000000001", "000000002" so on so forth, regardless of whether something like "2" was actually entered.
I don't know if this is even possible, but I am trying to do a couple of things that require me to populate drop down lists, and a normal cell by cell list, based on the items in a column on another sheet. I know how to do this with a regular column, but the column I want to use changes length and has many duplicate values in it, as it is an inventory of sorts.
So on this sheet, in C5,
that value should be able to be selected from a drop down list based on the contents of column B in the second sheet, but also without having to scroll through duplicates.
I am happy to use either VBA or general excel formulas to achieve this. I am also aware that I don't think I have explained this very well so please feel free to prompt me for more information as required.
Here is a start. I assume the the list of items is in Sheet2 somewhere from B1 through B1000. It's O.K. if the range is only partially filled (adjust the 1000 to suit your needs).
The code scans this list and builds a DV string. The Data Validation is then applied to Sheet1 cell C5:
Sub setupDV()
Dim rSource As Range, rDV As Range, r As Range, csString As String
Dim c As Collection
Set rSource = Sheets("Sheet2").Range("B1:B1000")
Set rDV = Sheets("Sheet1").Range("C5")
Set c = New Collection
csString = ""
On Error Resume Next
For Each r In rSource
v = r.Value
If v <> "" Then
c.Add v, CStr(v)
If Err.Number = 0 Then
If csString = "" Then
csString = v
Else
csString = csString & "," & v
End If
Else
Err.Number = 0
End If
End If
Next r
On Error GoTo 0
'MsgBox csString
With rDV.Validation
.Delete
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:= _
xlBetween, Formula1:=csString
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = False
End With
End Sub
What I'm looking to do is comb through a column and pull all the unique identifiers out of that column and then paste the results in a table in a different worksheet. I found the code below and it is very close to what I need. However, I have two major problems with it that I cannot figure out. First the area that this macro searches is constant ie "A1:B50". I need this to be one column and be dynamic since more data and new unique identifiers will be added to this worksheet. Second I cannot figure out how to paste my results to a specific range on a different worksheet. For example if I wanted to take the results and paste them in "sheet2" starting in at "B5" and going to however long the list of unique identifiers is.
Sub ExtractUniqueEntries()
Const ProductSheetName = "Sheet1" ' change as appropriate
Const ProductRange = "B2:B"
Const ResultsCol = "E"
Dim productWS As Worksheet
Dim uniqueList() As String
Dim productsList As Range
Dim anyProduct
Dim LC As Integer
ReDim uniqueList(1 To 1)
Set productWS = Worksheets(ProductSheetName)
Set productsList = productWS.Range(ProductRange)
Application.ScreenUpdating = False
For Each anyProduct In productsList
If Not IsEmpty(anyProduct) Then
If Trim(anyProduct) <> "" Then
For LC = LBound(uniqueList) To UBound(uniqueList)
If Trim(anyProduct) = uniqueList(LC) Then
Exit For ' found match, exit
End If
Next
If LC > UBound(uniqueList) Then
'new item, add it
uniqueList(UBound(uniqueList)) = Trim(anyProduct)
'make room for another
ReDim Preserve uniqueList(1 To UBound(uniqueList) + 1)
End If
End If
End If
Next ' end anyProduct loop
If UBound(uniqueList) > 1 Then
'remove empty element
ReDim Preserve uniqueList(1 To UBound(uniqueList) - 1)
End If
'clear out any previous entries in results column
If productWS.Range(ResultsCol & Rows.Count).End(xlUp).Row > 1 Then
productWS.Range(ResultsCol & 2 & ":" & _
productWS.Range(ResultsCol & Rows.Count).Address).ClearContents
End If
'list the unique items found
For LC = LBound(uniqueList) To UBound(uniqueList)
productWS.Range(ResultsCol & Rows.Count).End(xlUp).Offset(1, 0) = _
uniqueList(LC)
Next
'housekeeping cleanup
Set productsList = Nothing
Set productWS = Nothing
End Sub
I think your solution is a bit more tricky than it needs to be. Collecting unique ids becomes almost trivial is you use a Dictionary instead of a list. The added benefit is that a dictionary will scale much better than a list as your data set becomes larger.
The code below should provide you with a good starting point to get you going. For convenience's sake I used the reference from your post. So output will be on sheet2 to starting in cell B5 going down and the input is assumed to be on sheet1 cell B2 going down.
If you have any questions, please let me know.
Option Explicit
Sub ExtractUniqueEntries()
'enable microsoft scripting runtime --> tools - references
Dim unique_ids As New Dictionary
Dim cursor As Range: Set cursor = ThisWorkbook.Sheets("Sheet1").Range("B2") 'change as Required
'collect the unique ids
'This assumes that:
'1. ids do not contain blank rows.
'2. ids are properly formatted. Should this not be the could you'll need to do some validating.
While Not IsEmpty(cursor)
unique_ids(cursor.Value) = ""
Set cursor = cursor.Offset(RowOffset:=1)
Wend
'output the ids to some target.
'assumes the output area is blank.
Dim target As Range: Set target = ThisWorkbook.Sheets("Sheet2").Range("B5")
Dim id_ As Variant
For Each id_ In unique_ids
target = id_
Set target = target.Offset(RowOffset:=1)
Next id_
End Sub
A small modification will do it; the key is to define the ProductRange.
Sub ExtractUniqueEntries()
Const ProductSheetName = "Sheet1" ' change as appropriate
Dim ProductRange
ProductRange = "B2:B" & Range("B" & Cells.Rows.Count).End(xlUp).Row
Const ResultsCol = "E"
Dim productWS As Worksheet
Dim uniqueList() As String
Dim productsList As Range
Dim anyProduct
Dim LC As Integer
ReDim uniqueList(1 To 1)
Set productWS = Worksheets(ProductSheetName)
Set productsList = productWS.Range(ProductRange)
Application.ScreenUpdating = False
For Each anyProduct In productsList
If Not IsEmpty(anyProduct) Then
If Trim(anyProduct) <> "" Then
For LC = LBound(uniqueList) To UBound(uniqueList)
If Trim(anyProduct) = uniqueList(LC) Then
Exit For ' found match, exit
End If
Next
If LC > UBound(uniqueList) Then
'new item, add it
uniqueList(UBound(uniqueList)) = Trim(anyProduct)
'make room for another
ReDim Preserve uniqueList(1 To UBound(uniqueList) + 1)
End If
End If
End If
Next ' end anyProduct loop
If UBound(uniqueList) > 1 Then
'remove empty element
ReDim Preserve uniqueList(1 To UBound(uniqueList) - 1)
End If
'clear out any previous entries in results column
If productWS.Range(ResultsCol & Rows.Count).End(xlUp).Row > 1 Then
productWS.Range(ResultsCol & 2 & ":" & _
productWS.Range(ResultsCol & Rows.Count).Address).ClearContents
End If
'list the unique items found
For LC = LBound(uniqueList) To UBound(uniqueList)
productWS.Range(ResultsCol & Rows.Count).End(xlUp).Offset(1, 0) = _
uniqueList(LC)
Next
'housekeeping cleanup
Set productsList = Nothing
Set productWS = Nothing
End Sub