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.
Related
Good day, I am a newbie to VBA. I have not included the code I have tried, because nothing has even come close.
I have a Data range of about 10,000 that contains the building, department, user name and possibly other information. This information is in column B. The names are not in the same location of each cell and they can be any case and can contain up to 4 words.
I have a Named Range (Full Name) of about 14,000 names in a separate workbook named database.
I need to see if the names show up in the data range list and if so populate column C with the name.
Thanks in advance for any assistance.
Example code:
Sub Full_Name()
Dim iWs As Worksheet, iFn As Variant, lastrow As Long, iDB As Worksheet
iFn = Range("'[Shadow Datafie Database.xlsx]EMCP'!Full_Name").Value
Set iWs = ActiveWorkbook.Worksheets("EMCP")
lastrow = iWs.UsedRange.Rows.Count + 1
For i = 2 To lastrow
If InStr(iWs.Cells(i, 2), iFn) > 0 Then
iWs.Cells(i, 3) = iFn
End If
Next
End Sub
This code may work for you:
It assumes your list of names is in an Excel table called Table1.
Sub FindName()
'Open the csv file containing your information - building, department, etc.
Dim wrkBkSrc As Workbook
Set wrkBkSrc = Workbooks.Open("<path to your file>\Numplan(11).csv")
'A csv file will only contain a single sheet, so can reference it by sheet position - first and only.
With wrkBkSrc.Worksheets(1)
Dim DataRange As Range
Set DataRange = .Range(.Cells(2, 2), .Cells(.Rows.Count, 2).End(xlUp))
End With
' *** OLD CODE ***
' With ThisWorkbook.Worksheets("Sheet1")
' Dim DataRange As Range
' Set DataRange = .Range(.Cells(2, 2), .Cells(.Rows.Count, 2).End(xlUp))
' End With
'Open the database file and set reference to it.
Dim wrkBk As Workbook
Set wrkBk = Workbooks.Open("<path to your file>\Database.xlsx")
'Set reference to the names table.
'Note: This is an Excel table, not an Excel range.
' Press Ctrl+T to turn range into a table.
Dim NameTable As ListObject
Set NameTable = wrkBk.Worksheets("Database").ListObjects("Table1")
'Only continue if there's data in the table.
If Not NameTable.DataBodyRange Is Nothing Then
Dim NameItm As Range
Dim FoundItm As Range
For Each NameItm In NameTable.DataBodyRange
'Find the name within the DataRange.
Set FoundItm = DataRange.Find( _
What:=NameItm, _
After:=DataRange.Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
'If it's found place the name in the next column along.
If Not FoundItm Is Nothing Then
FoundItm.Offset(, 1) = NameItm
End If
Next NameItm
End If
End Sub
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."
I currently have code set up that will loop through all the worksheets in my workbook, paste a date in a cell which, when non blank, will have the remaining cells in the row populate with data.
At the beginning of each row - I have a formula that will say "Error" if any of the cells in that row has an error in it. like this:
I then have another loop which will go back through each worksheet and check to see if there is an error in that cell and if so, will go to the first sheet in the workbook to a specific cell and add "Error on xyz Tab". If there are multiple errors, it'll go to the next row down and paste it. So it looks like this:
I'm thinking instead of looping through each sheet again, could i store the text string in a variable/array and just paste it on the front sheet at the end of the loop in the same manner?
This is the code for the error loop that's currently set up:
For I = 1 To WS_Count
ActiveWorkbook.Worksheets(I).Activate
Cells.Find(What:="Date", After:=ActiveCell, LookIn:=xlFormulas, LookAt _
:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False).End(xlDown).Offset(0, -1).Activate
If ActiveCell.Value = "Error" Then
Application.Goto "ErrorCheck"
If ActiveCell.Offset(1, 0).Value = vbNullString Then
ActiveCell.Offset(1, 0).Value = "Error on " & ActiveWorkbook.Worksheets(I).Name & " " & Hour(Now) & "00"
Else
Selection.End(xlDown).Activate
ActiveCell.Offset(1, 0).Value = "Error on " & ActiveWorkbook.Worksheets(I).Name & " " & Hour(Now) & "00"
End If
Else
End If
Next I
So with this I personally wouldn't want to use an array. I would prefer using a collection. It is easier because you do not know the parameters for your array so it is tough to give it dimensions.
Nonetheless find below a possible solution. Work it to your needs. I have yet to test or debug myself. But should do the trick.
Sub ErrorCheck()
Dim x As Long, lRow1 As Long, lRow2 As Long
Dim myCollection As New Collection
Dim ws As Worksheet
Dim mySheet As Worksheet
Set mySheet = Sheets("ErrorCheckSheet")
'create the for loop to cycle through worksheets
For Each ws In ThisWorkbook.Worksheets
'set the lrow to iterate through column
'set the colum for your need - "Error" column
lRow1 = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
'IF lRow does not match your cell, use a static variable ie. 50
'assuming your data starts in row 2 as per picture
For x = 2 To lRow1
'check each cell for error text
If ws.Range("A" & x).Text = "Error" Then
'when found add to collection
'adjust to meet your cell you want to input into collection
myCollection.Add ws.Range("B" & x).Text
End If
Next x
Next ws
'once you have completely cycled through your workbook your collection will now be loaded
For x = 1 To myCollection.Count
'set the lrow on the sheet you want to enter the data in
lRow2 = mySheet.Range("U" & mySheet.Rows.Count).End(xlUp).Row + 1
'now set the variable
mySheet.Range("U" & lRow2).Value = "Error on" & myCollection(x)
Next x
Set myCollection = New Collection
Set mySheet = Nothing
End Sub
I'm a VBA newbie and I'm trying to make an array from some text (i.e. the names of the worksheets) I have listed out in a column ("B") - so I can save all my worksheets as a single PDF file, but with the option of adding or removing worksheets over time (as in, rewriting them under wksAllSheets over and over again).
So far I have:
Public Sub saveAsPDF()
Application.ScreenUpdating = False
Call print_reports 'a sub I created with the printing layours
Dim wksAllSheets As Variant
Dim wksSheet1 As Worksheet
Dim strFilename, strName As String, strFilepath As String
Set wksSheet1 = ThisWorkbook.Sheets("SheetCOVER") 'reference ws
wksAllSheets = Array("SheetCOVER", "Sheet1", "Sheet2", "Sheet3", "Sheet4", _
"Sheet5", _
"Sheet6", "Sheet7", "Sheet8")
ThisWorkbook.Sheets(wksAllSheets).Select
wksSheet1.ExportAsFixedFormat _
Type:=xlTypePDF, _
Filename:=strFilename, _
Quality:=xlQualityStandard, _
IncludeDocProperties:=True, _
IgnorePrintAreas:=False, _
OpenAfterPublish:=True
wksSheet1.Select
Sheets("Home").Select
End Sub
Any help would be extremely appreciated!!
Here is how to create an array from a table starting from "B1"
Dim n_rows as Long, n_cols as Long
Dim r as Range
Set r = Range("B1")
' Count non-empty cells
n_rows = Range(r, r.End(xlDown)).Rows.Count
n_cols = 1 ' Assume table has one column.
' Set the range variable 'r' to the entire table of cells
Set r = r.Resize(n_rows, n_cols)
Dim vals() as Variant ' This is the dynamic array spec
vals = r.Value ' Here you fill the array from the cells
Dim i as Long
For i=1 to n_rows
Debug.Pring vals(i,1) 'You access the array with (i,j): i=row, j=column
Next i
to dynamically build the list of sheets you can use the sheets collection
For Each ws In Sheets
Select Case ws.Name
Case "Home","COVER" ' exclude these sheets
Case Else 'include all others
wksAllSheets = wksAllSheets & IIf(wksAllSheets = "", "", ",") & ws.Name
End Select
Next
wksAllSheets = Split(wksAllSheets, ",")
to build the list from a specific column in a sheet of your workbook
wksallsheets=application.transpose(sheets("listpdf").range("B1:B" & sheets("listpdf").cells(rows.count,"B").end(xlup).row))
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