Update table cells based on criteria using an array - arrays

I have a dynamic master table on Worksheets("Jobs") that has fixed number of columns (A:M). Columns I and J are formulas.
On a different worksheet I have a textbox that will have a job # specified. I need values in columns L & M to change to "No" for the matching job #. My previous code was working too slow. I'm trying to rewrite the code using an array, but I have a hard time doing so.
The idea is to transfer the entire table to a memory-based array and make your changes to the array, then transfer the updated table data back to the worksheet.
Question is if I do that, wouldn't that clear the content that have formulas. Can I use two arrays from two header based named ranges for column B, then another for columns L:M? Working in that array, just update and transfer the values that just need to be changed.
Thank you for any help anyone can provide.
Here is my code so far:
Sub CloseJobarr()
Dim cell As Range
Dim Txt As String
Dim ws As Worksheet
Dim Arr1 As Variant, Arr2 As Variant
Arr1 = Range("JobCol_Master").Value '<--Column B of Master Data Table that is on ws
Arr2 = Range("OpenSCCols").Value '<--Columns L:M of Master Data Table that is on ws
Set ws = ThisWorkbook.Worksheets("Jobs")
With ThisWorkbook
Txt = .Worksheets("ID").TextBoxID.Text
If Txt <> "" Then
With ws
For Each cell In Arr1
'If job# matches textbox and if job# is to correct region then...
If cell.Text = Txt And .Cells(cell.row, 4).Value = "ID" Then
End If
Next cell
End With
End If
End With
MsgBox "Job not found."
End Sub
Updated Code below using Auto Filter (I'm still experiencing screen flicking). When a job # doesn't match I get a run time error message " no cells were found" and the debug line is: .Range("OpenSCCols").SpecialCells(xlCellTypeVisible).Value = "No"
Option Explicit
Sub CloseJobarraytesting()
ThisWorkbook.Sheets("Jobs").Unprotect Password:="Andersen"
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
On Error GoTo errHndl
Dim cell As Range
Dim Txt As String
Dim ws As Worksheet
With ThisWorkbook.Worksheets("Jobs") '<--| reference relevant worksheet
.Range("JobCol_Master").AutoFilter Field:=2, Criteria1:=Worksheets("ID").TextBoxID.Text '<--| filter its "JobCol_Master" named range on textbox ID
If Application.WorksheetFunction.Subtotal(103, .Cells) > 1 Then '<--| if any cell filtered other than header
.Range("OpenSCCols").SpecialCells(xlCellTypeVisible).Value = "No" '<-- write "OpenSCCols" named range filter cells, corresponding to the filtered ID
Else
MsgBox "Job not found."
End If
.AutoFilterMode = False
End With
CleanUp:
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
ThisWorkbook.Sheets("Jobs").Protect Password:="Andersen"
Exit Sub
errHndl:
MsgBox "Error happened while working on: " + vbCrLf + _
vbCrLf + vbCrLf + "Error " + _
Str(Err.Number) + ": " + Err.Description, vbCritical + vbOKOnly, "Error"
GoTo CleanUp
End Sub

edited to have the code check filtered cells on Range("JobCol_Master")
your aim is to actual filter data, then I believe that an AutoFilter() approach should be considerable fast
assuming you defined both JobCol_Master and OpenSCCols named ranges enclosing their corresponding headers cells, you can simply go like follows
Option Explicit
Sub CloseJobarr()
With ThisWorkbook.Worksheets("Jobs") '<--| reference relevant worksheet
With .Range("JobCol_Master")
.AutoFilter Field:=1, Criteria1:=Worksheets("ID").TextBoxID.Text '<--| filter its "JobCol_Master" named range on textbox ID
If Application.WorksheetFunction.Subtotal(103, .Cells) > 1 Then '<--| if any cell filtered other than header
.Parent.Range("OpenSCCols").SpecialCells(xlCellTypeVisible).Value = "No" '<-- write "OpenSCCols" named range filter cells, corresponding to the filtered ID
Else
MsgBox "Job not found."
End If
End With
.AutoFilterMode = False
End With
End Sub
should your named ranges not contain their headers the code can be easily adapted by means of some Offset() and Resize() method applied on them, but its much easier (and logic) to resize the named ranges and have them enclose their headers

Related

sheet names in an array but not always the same sheets

I'm trying to create a pdf of the various sheet in the workbook based on certain criteria in VBA
my current code is
Sub ExportAsPDF()
Dim FolderPath As String
FolderPath = "C:\Users\USER1\Desktop\PDF\pdftest"
'sheet names in ""
Sheets(Array("SHEET1","SHEET2","SHEET3")).Select
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, filename:=FolderPath, _
openafterpublish:=False, ignoreprintareas:=False
MsgBox "All PDFs have been successfully exported."
End Sub
the above works but there are more than 9 sheets and sometimes sheet 2 will be needed sometimes it won't. how do I write the array selection to a variable based on criteria?
if a=1 then add sheet 2 to the array kind thing
Thank You for your help!
You can set up an array with the sheet names, then add Sheet2 if required:
Dim arr, test
test = True 'variable which controls adding sheet2....
arr = Array("Sheet1", "Sheet5") 'array without sheet2
'need to add sheet2?
If test Then
ReDim Preserve arr(LBound(arr) To UBound(arr) + 1)
arr(UBound(arr)) = "Sheet2"
End If
ThisWorkbook.Sheets(arr).Select
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, Filename:= _
"C:\Tester\tempo.pdf", Quality:=xlQualityStandard, IncludeDocProperties:=True, _
IgnorePrintAreas:=False, OpenAfterPublish:=True
Function AvoidWB2() As String()
Dim v() As String, i As Long, wbCount As Long
wbCount = ActiveWorkbook.Sheets.Count
ReDim v(1 To wbCount)
For i = 1 To wbCount
If i = 2 Then
'do something here regarding adding sheet 2.
Else
v(i) = ActiveWorkbook.Sheets(i).Name
End If
Next i
AvoidWB2 = v
End Function
Sub ExportAsPDF()
Dim FolderPath As String, vSheetSelection() as Variant
FolderPath = "C:\Users\USER1\Desktop\PDF\pdftest"
'sheet names in ""
'----| Implement Here|
Sheets(AvoidWB2).Select
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, filename:=FolderPath, _
openafterpublish:=False, ignoreprintareas:=False
MsgBox "All PDFs have been successfully exported."
End Sub
Here is an example based on the described requirements.
This example assumes:
You only want to consider excluding Sheet2 from the pdf.
You deterime if Sheet2 is to be included or not by the value in Cell B1 on Sheet2
I've adapted some If...Then conditioning with a For Each...Next loop to your existing code.
Sub ExportAsPDF()
Dim FolderPath As String
Dim SheetsArray As Variant
Dim TargetSheet As Worksheet
Dim ArrayCounter As Long: ArrayCounter = 0
FolderPath = "C:\Users\USER1\Desktop\PDF\pdftest"
'sheet names in ""
If Sheets("Sheet2").Range("B1").Value = "Yes" Then
ReDim SheetsArray(ThisWorkbook.Worksheets.Count - 1)
For Each TargetSheet In ThisWorkbook.Worksheets
SheetsArray(ArrayCounter) = TargetSheet.Name
ArrayCounter = ArrayCounter + 1
Next TargetSheet
Else
ReDim SheetsArray(ThisWorkbook.Worksheets.Count - 2)
For Each TargetSheet In ThisWorkbook.Worksheets
If Not TargetSheet.Name = "Sheet2" Then
SheetsArray(ArrayCounter) = TargetSheet.Name
ArrayCounter = ArrayCounter + 1
End If
Next TargetSheet
End If
ThisWorkbook.Sheets(SheetsArray).Select
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, Filename:=FolderPath, _
openafterpublish:=False, ignoreprintareas:=False
MsgBox "All PDFs have been successfully exported."
End Sub
If the value of Sheet2.Range("B1") is "Yes" then Sheet2 is included in the pdf, otherwise if it's any other value it is not included.
Explanation:
Relevant documentation:
Using Arrays (Covers setting lower/upper bound of array etc.)
If...Then...Else statement (VBA)
For...Next statement (VBA)
First I evaluate whether Sheet2.Range("B1") = "Yes" to dictate how we will populate the SheetsArray.
The statements are very similar depending on if it's "Yes" or not.
The ReDim SheetsArray is to set the dimension of our Array. Because arrays in VBA are Base 0 by default (meaning the first element of the array is 0, the second element is 1 etc...) I size the array as follows:
If the value is "Yes" the array is sized as 1 less than the number of worksheets. This is to account for the array starting at 0 rather than 1.
If the value is not "Yes" then the array is sized at 2 less than the number of worksheets. This accounts for the array starting at 0 rather than 1 AND that we are not including Sheet2.
The For Each...Next loop then iterates over the Worksheets collection, adding each worksheet name to the array. We have an extra If...Then statement within the code block for if the value is not "Yes" to determine if the TargetSheet is not Sheet2 and add the worksheet name if this condition is met. This will exclude Sheet2 from being added to our array.
The ArrayCounter variable simply increments with each worksheet name added to the array as "To set the value of an individual element, you specify the element's index."

Delete multiple columns from Multiple sheets

I am trying to delete Multiple columns from Multiple sheets while retaining those found in a list.
For example I have sheet1, sheet2, sheet3, ..., sheet7.
From these sheets I have particular columns to be keep like.
From sheet1 I want keep columns like s.no, cust.name, product, date remaining all should be deleted same from sheet2 I want to keep prod.disc,address, pin remaining all should be deleted like I have remaining sheets in that I want to keep particular columns remaining all should be deleted.
I am trying to do using arrays but not able start how to do. I have basic syntax.
Sub sbVBS_To_Delete_Specific_Multiple_Columns()
Sheets("Sheet1").Range("A:A,C:C,H:H,K:O,Q:U").EntireColumn.Delete
End Sub`[code]
But that didn't work for me because in future some columns may add in it and I want columns should recognize with name which column to keep and remaining to discard.
OK, here is the basic code. Specify the worksheet and the columns to be deleted in the main procedure. Set the row in which to find the captions in the sub-procedure.
Sub DeleteColumns()
' 17 Mar 2017
Dim ClmCaption As Variant
Dim Ws As Worksheet
Dim i As Integer
Set Ws = ActiveSheet
' better to specify the sheet by name, like Set Ws = ThisWorkbook.Worksheets("My Excel")
Application.ScreenUpdating = False ' freeze screen (speeds up execution)
ClmCaption = Array("One", "two", "three", "four", "five")
' specify all the columns you want to delete by caption , not case sensitive
For i = 0 To UBound(ClmCaption) ' loop through all the captions
DelColumn Ws, CStr(ClmCaption(i)) ' call the sub for each caption
Next i
Application.ScreenUpdating = True ' update screen
End Sub
Private Sub DelColumn(Ws As Worksheet, Cap As String)
' 17 Mar 2017
Dim CapRow As Long
Dim Fnd As Range
CapRow = 3 ' this is the row where the captions are
Set Fnd = Ws.Rows(CapRow).Find(Cap) ' find the caption
If Fnd Is Nothing Then
MsgBox "The caption """ & Cap & """ doesn't exist." & vbCr & _
"The column wasn't deleted.", _
vbInformation, "Invalid parameter"
Else
Ws.Columns(Fnd.Column).EntireColumn.Delete Shift:=xlToLeft
End If
End Sub
You can run the code as it is but you will get a lot of error messages because the specified captions don't exist.
The following uses a Scripting Dictionary object that maintains a list of worksheets to be processed as the dictionary keys with an array of column header labels to keep as the associated items.
Option Explicit
Sub delColumnsNotInDictionary()
Dim d As Long, ky As Variant, dict As Object
Dim c As Long, lc As Long
Set dict = CreateObject("Scripting.Dictionary")
dict.comparemode = vbTextCompare
dict.Item("Sheet1") = Array("s.no", "cust.name", "product", "date")
dict.Item("Sheet2") = Array("prod.disc", "address", "pin")
dict.Item("Sheet50") = Array("foo", "bar")
With ThisWorkbook
For Each ky In dict.keys
With Worksheets(ky)
lc = .Cells.Find(What:="*", After:=.Cells(1, 1), LookIn:=xlFormulas, LookAt:=xlPart, _
SearchOrder:=xlByColumns, SearchDirection:=xlPrevious, _
MatchCase:=False, SearchFormat:=False).Column
For c = lc To 1 Step -1
'filter array method of 'not found in array'
'WARNING! CASE SENSITIVE SEARCH - foo <> FOO
If UBound(Filter(dict.Item(ky), .Cells(1, c).Value2)) = -1 Then
'.Cells(1, c).EntireColumn.Delete
Else
Debug.Print .Cells(1, c).Value2 & " at " & _
UBound(Filter(dict.Item(ky), .Cells(1, c).Value2))
End If
'worksheet MATCH method of 'not found in array'
'Case insensitive search - foo == FOO
If IsError(Application.Match(.Cells(1, c).Value2, dict.Item(ky), 0)) Then
.Cells(1, c).EntireColumn.Delete
Else
Debug.Print .Cells(1, c).Value2 & " at " & _
Application.Match(.Cells(1, c).Value2, dict.Item(ky), 0)
End If
Next c
End With
Next ky
End With
dict.RemoveAll: Set dict = Nothing
End Sub
Note that I have included two methods for determining whether a column header label is within the array of columns to keep. One is case-sensitive (the array Filter method) and the other is not (worksheet function MATCH method). The case-insensitive search method is currently active.

Non-contiguous named range into an array, then into row in different sheet

I'm trying to get data posted from a non-contiguous range into a row in a separate sheet. Before I built the non-contiguous range, this code worked perfectly. I've tried several things to loop through, but nothing I tried will work. It won't copy the ranged data as it sits. It's been years since I've actually done any coding and my re-learning curve seems to be holding me back.... the logic just isn't coming to me. Help!
Sub UpdateLogWorksheet()
Dim historyWks As Worksheet
Dim inputWks As Worksheet
Dim nextRow As Long
Dim oCol As Long
Dim myCopy As Range
Dim myTest As Range
Dim myData As Range
Dim lRsp As Long
Set inputWks = Worksheets("Input")
Set historyWks = Worksheets("DataEntry")
oCol = 3 'order info is pasted on data sheet, starting in this column
'check for duplicate VIN in database
If inputWks.Range("CheckVIN") = True Then
lRsp = MsgBox("VIN already in database. Update record?", vbQuestion + vbYesNo, "Duplicate VIN")
If lRsp = vbYes Then
UpdateLogRecord
Else
MsgBox "Please change VIN to a unique number."
End If
Else
'cells to copy from Input sheet - some contain formulas
Set myCopy = inputWks.Range("VehicleEntry") 'non-contiguous named range
With historyWks
nextRow = .Cells(.Rows.Count, "A").End(xlUp).Offset(1, 0).Row
End With
With inputWks
'mandatory fields are tested in hidden column
Set myTest = myCopy.Offset(0, 2)
If Application.Count(myTest) > 0 Then
MsgBox "Please fill in all the cells!"
Exit Sub
End If
End With
With historyWks
'enter date and time stamp in record
With .Cells(nextRow, "A")
.Value = Now
.NumberFormat = "mm/dd/yyyy hh:mm:ss"
End With
'enter user name in column B
.Cells(nextRow, "B").Value = Application.UserName
'copy the vehicle data and paste onto data sheet
myCopy.Copy
.Cells(nextRow, oCol).PasteSpecial Paste:=xlPasteValues, Transpose:=True
Application.CutCopyMode = False
End With
'clear input cells that contain constants
Clear
End If
End Sub
This is an example to explain how to achieve what you want. Please amend the code to suit your needs.
Let's say, I have a Sheet1 which looks like as shown below. The colored cells make up from my non contiguous range.
Now paste the code given below in a module and run it. The output will be generated in Sheet2 and Sheet3
Code
Sub Sample()
Dim rng As Range, aCell As Range
Dim MyAr() As Variant
Dim n As Long, i As Long
'~~> Change this to the relevant sheet
With Sheet1
'~~> Non Contiguous range
Set rng = .Range("A1:C1,B3:D3,C5:G5")
'~~> Get the count of cells in that range
n = rng.Cells.Count
'~~> Resize the array to hold the data
ReDim MyAr(1 To n)
n = 1
'~~> Store the values from that range into
'~~> the array
For Each aCell In rng.Cells
MyAr(n) = aCell.Value
n = n + 1
Next aCell
End With
'~~> Output the data in Sheet
'~~> Vertically Output to sheet 2
Sheet2.Cells(1, 1).Resize(UBound(MyAr), 1).Value = _
Application.WorksheetFunction.Transpose(MyAr)
'~~> Horizontally Output to sheet 3
Sheet3.Cells(1, 1).Resize(1, UBound(MyAr)).Value = _
MyAr
End Sub
Vertical Output
Horizontal Output
Hope the above example helps you in achieving what you want.

Copy data from one workbook to new workbook and save them by specific title

I have one file which is database and contain data of all student from different classes. I want excel to make class wise files by copying data from database to new file... I am using below mention codes and these are working perfect but it only coping data till Column G and now data has extended to Column Z and its not working give me run time error.
"Note Column B tittle Class" i.e Tittle of new saved file
Sub proSaveDateClasswise()
Range("I1").Value = "Class"
Columns("B:B").AdvancedFilter Action:=xlFilterCopy, copyToRange:=Columns( _
"I:I"), unique:=True
Range("J1").Value = "Class"
Dim cell As Range
Dim curPath As String
curPath = ActiveWorkbook.Path & "\Extracted Files\\"
If Len(Dir(curPath, vbDirectory)) = 0 Then
MkDir (curPath)
End If
Application.ScreenUpdating = False
Application.DisplayAlerts = False
For Each cell In Range("I:I")
If cell.Value <> "Branch" And cell.Value <> "" Then
Range("J2").Value = cell.Value
Range("A:G").AdvancedFilter Action:=xlFilterCopy, _
criteriarange:=Range("J1:J2"), copyToRange:=Range("L:R"), unique:=False
Range(Range("L1:R1"), Range("L1:R1").End(xlDown)).Copy
Workbooks.Add
ActiveSheet.Paste
ActiveWorkbook.SaveAs Filename:=curPath & cell.Value & ".xlsx", _
FileFormat:=xlOpenXMLWorkbook, CreateBackup:=False
ActiveWindow.Close
Range(Range("L1:R1"), Range("L1:R1").End(xlDown)).ClearContents
End If
Next cell
Columns("I:R").Delete
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
I think the main issue here before we go any further is that your current macro uses some columns beyond G in order to select unique classes. That means the whole code would have to be rewritten for it to work as you expect. I suggest you provide some input and output files with dummy data to work on.

Copy only records that do not exist in the target table

Having two tables (the source and target) intend to copy only the records from the source table that do not exist in the target table (making the comparison with the value of a specific cell in each record). I thought to do it using arrays, but as I am new in this area, needed help.
Examples:
Source Table
ID Date Description
115 01-Ago Description1
120 05-Ago Description2
130 03-Ago Description5
110 08-Ago Description4
105 06-Ago Description6
Destination Table
ID Date Description
130 03-Ago Description5
110 08-Ago Description4
I want to add in the target table records from the source table that do not exist in the target table (ID's 115,120,105 in this example). Thank you!
I'm almost there. After consulting some other questions, I need something like this:
Sub Tests()
Dim MyArray() As String
Dim tgtLastRow, srcLastRow As Integer
Dim rngTarget, rngSource, cel As Range
Dim Delim As String
Delim = "#"
tgtLastRow = Range("H1").End(xlDown).Row
srcLastRow = Range("A1").End(xlDown).Row
Set rngTarget = Range("H2:H" & tgtLastRow)
Set rngSource = Range("A2:A" & srcLastRow)
MyArray = rngTarget.Value
strg = Join(MyArray, Delim)
strg = Delim & strg
For Each cel In rngSource
If InStr(1, strg, Delim & cel.Value & Delim, vbTextCompare) Then
Else
'Copy the row or range here
End If
Next cel
End Sub
But now, I have one of two problems:
If I declare MyArray as string type I have problems loading values to array
If I declare MyArray as variant type I have problems in the Join
Can anyone help-me please??
All you need is to use Either Collection object, or Dictionary Object. These objects help a lot when you try to find the unique records.
Let us take an example, We have two sheets: Source and Target.
You need to loop through Column A in both sheets and move the data from Source Worksheet to target Worksheet. Following is the code, not tested, but it should do the trick. I have added comments so you can easily understand and fit this in your situation easily
Dim ids As Collection
Sub MoveData()
On Error GoTo MoveData_Error
Set ids = New Collection
Dim sourceRange As Range
Dim idRange As Range
Dim cell As Range
Set sourceRange = Range("A1:A100") 'assign your source range here. Code will try to pick ID in this range, and check in ID Range
Set idRange = Range("A1:A100") ' assign your target range here. Code will find the ID in this range
'load all ids from target range in the collection.
On Error Resume Next ' suppressing the error if any duplicate value is found
For Each cell In idRange.Cells
ids.Add cell.Value, cell.Value ' adding in collection to maintain a unique collection
Err.Clear
Next cell
On Error GoTo MoveData_Error
'now I have information about all the availabe IDs in id collection. Now I will loop through to check
For Each cell In sourceRange
If ShouldCopy(cell) Then
'write your code to copy
End If
Next cell
On Error GoTo 0
Exit Sub
MoveData_Error:
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure MoveData of VBA Document Sheet1"
End Sub
Public Function ShouldCopy(cell As Range) As Boolean
On Error GoTo ShouldCopy_Error
If cell.Value = "" Or IsEmpty(cell.Value) Then Exit Function
ids.Add cell.Value, cell.Value ' if error occurs here, then it means the id has been already moved or found in the ID worksheet range
ShouldCopy = True
On Error GoTo 0
Exit Function
ShouldCopy_Error:
ShouldCopy = False
End Function
If you face any issues in understanding and need any help, please let me know.
Thanks,
V
Add a lookup to your source data flagging each record as either present or absent and then bounce your macro off of that column (i.e only move it into target if the lookup = absent).

Resources