I have a procedure to create a new sheet based on available data. Basically, it creates a sheet based on the name of the data. The code is written as follows. It does work actually if I assign the procedure one by one.
Sub new_profile(tankname)
Sheets.Add After:=ActiveSheet
Range("B4").Select
ActiveCell.FormulaR1C1 = tankname
ActiveSheet.Name = Range("b4").Value
end sub
Due to the fact that I will use this code for another workbook (which means there is no exact amount of data), I try to assign an array to automatically run the procedure all in one without call it one by one. The code is as follow:
Sub calculate_all()
Dim cel As Range
Dim tank_name() As String
Dim i As Integer, j As Integer
Dim n As Integer
i = 11
n = Range("B6").Value
ReDim tank_name(i)
For Each cel In ActiveSheet.Range(Cells(11, 2), Cells(11 + n, 2))
tank_name(i) = cel.Value
i = i + 1
new_profile tank_name(i)
ReDim Preserve tank_name(i)
Next cel
End Sub
Unfortunately, it becomes error and shows the message "subscript out of range". How could I solve this problem?
For Each Element in Array Run a Procedure
Let's say that creating a new profile means adding a new sheet, renaming it and writing the name to a cell.
The 1st, main procedure createProfiles does the previously mentioned only if a worksheet with the current name in the TankNames array doesn't exist.
The 2nd procedure deleteProfiles deletes all sheets if their names exist in the TankNames array.
The 3rd and the 4th procedure are called by both previously mentioned procedures, while the 5th is obviously only called by the main procedure.
Before running any of the first two procedures, adjust the constants in them to fit your needs.
The Code
Option Explicit
Sub createProfiles()
' Source
Const wsName As String = "Sheet1" ' Tab Name
Const FirstRow As Long = 11
Const NameCol As Variant = "B" ' e.g. 1 or "A", 2 or "B"...
' Target
Const CellAddress As String = "B4"
' Other
Dim wb As Workbook: Set wb = ThisWorkbook
' Define Source Worksheet.
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
' Write tank names from Source Worksheet to TankNames array.
Dim TankNames As Variant
getColumn TankNames, ws, NameCol, FirstRow
Dim i As Long
' Loop through elements of TankNames array.
For i = 1 To UBound(TankNames)
' For each tank name create a new profile.
If Not foundSheetName(wb, TankNames(i, 1)) Then
Call createProfile wb, TankNames(i, 1), CellAddress
End If
Next i
End Sub
Sub deleteProfiles()
' Source
Const wsName As String = "Sheet1" ' Tab Name
Const FirstRow As Long = 11
Const NameCol As Variant = "B" ' e.g. 1 or "A", 2 or "B"...
' Other
Dim wb As Workbook: Set wb = ThisWorkbook
' Define Source Worksheet.
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
' Write tank names from Source Worksheet to TankNames array.
Dim TankNames As Variant
getColumn TankNames, ws, NameCol, FirstRow
Dim i As Long
' Loop through elements of TankNames array.
For i = 1 To UBound(TankNames)
' For each tank name delete profile (sheet).
If foundSheetName(wb, TankNames(i, 1)) Then
Application.DisplayAlerts = False
wb.Worksheets(TankNames(i, 1)).Delete
Application.DisplayAlerts = True
End If
Next i
End Sub
Sub getColumn(ByRef Data As Variant, _
Sheet As Worksheet, _
Optional ByVal ColumnID As Variant = 1, _
Optional ByVal FirstRow As Long = 1)
Data = Empty
If Sheet Is Nothing Then Exit Sub
Dim rng As Range
Set rng = Sheet.Columns(ColumnID).Find("*", , xlValues, , , xlPrevious)
If rng Is Nothing Then Exit Sub
If rng.Row < FirstRow Then Exit Sub
Set rng = Sheet.Range(Sheet.Cells(FirstRow, ColumnID), rng)
If rng.Cells.Count > 1 Then
Data = rng.Value
Else
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rng.Value
End If
End Sub
Function foundSheetName(Book As Workbook, _
Optional ByVal SheetName As String = "Sheet1") _
As Boolean
If Book Is Nothing Then Set Book = ActiveWorkbook
On Error Resume Next
Dim ws As Worksheet: Set ws = Book.Worksheets(SheetName)
If Err.Number = 0 Then foundSheetName = True
End Function
Sub createProfile(Book As Workbook, _
ByVal NewName As String, _
ByVal NameCellAddress As String)
Dim ws As Worksheet
Set ws = Book.Worksheets.Add(After:=Book.Sheets(Book.Sheets.Count))
With ws
.Name = NewName
.Range(NameCellAddress) = NewName
End With
End Sub
Related
After all of these years, I've seen several questions that asks how to populate an Array with a Recordset, but none that addresses how to write the output onto a worksheet all in one procedure. So, I decided to ask the Question in order to provide an answer that I've developed over time.
'Goes on Top
Option Explicit
Option Compare Text
Option Base 1
Public Sub Recordset_to_Array_to_Worksheet()
Dim MyArray() As Variant 'unbound Array with no definite dimensions'
Dim db as DAO.Database
Dim rst as DAO.Recordset
Dim strSQL as String, Fieldname as String
Dim i as Integer, j as Integer, colcnt as Integer, rowcnt as Integer
Dim wb as Workbook
Dim ws as Worksheet
Dim Dest as Range
'------------------------RECORDSET------------------------'
Set db = Opendatabase("URL link") 'or Set db = Currentdb()
strSQL = "SQL Statement Here"
Set rst = db.OpenRecordset(strsQL, dbOpenDynaset)
If rst.recordcount <> 0 then '///Do NOT Use "Do While Not rst.EOF" Can cause Problems///'
colcnt = rst.Fields.Count-1
rowcnt = rst.recordcount
Else
Exit Sub
End IF
'-----------------------------WRITE RECORDSET TO MYARRAY----------------------------'
ReDim MyArray (rowcnt, colcnt) 'Redimension MyArray parameters to fit the SQL returned'
rst.MoveFirst
'Populating Array with Headers from Recordset'
For j = 0 To colcnt
MyArray(0,j) = rst.Fields(j).name
Next
'Populating Array with Record Data
For i = 1 to rowcnt
For j = 0 to colcnt
MyArray(i,j) = rst(j)
Next J
rst.movenext
Next i
'---------------------------------WORKSHEET OUTPUT---------------------------------'
Set wb = ThisWorkbook
Set ws = wb.Worksheets("Insert Worksheet Name")
Set Dest = ws.Range("A1") 'Destination Cell
Dest.Resize(UBound(MyArray, 1) + 1, UBound(MyArray, 2) + 1).value =
Application.Transpose(MyArray) 'Resize (secret sauce)
End Sub
As shown in the image, I want to first filter by column A for "Yes".
The above image shows after the filter and I want to save each unique "ID" in columns B and put them into an array called myArr. Ideally, myArr = [101, 5137, 97] and I would be able to call each value in the array using myArr(1), myArr(2), myArr(3)
Below is the code I had, but there are 2 problems:
my arr doesn't seem to be an actual array
it doesn't print the correct answers 101, 5137, 97. Instead, it only prints out 101, 5137
With [a1].CurrentRegion
.AutoFilter 1, "Yes"
'first create arr which include duplicated data
arr = .Offset(1, 1).Resize(.Rows.Count - 1, 1).SpecialCells(xlVisible)
'myArr is an array with unique values
myArr = Application.Unique(arr)
'print out each value of myArr to check if myArr is correct
For Each num In myArr
Debug.Print num
Next num
.AutoFilter
End With
Please give me some ideas on what's wrong with my code above.
Your code is failing because once you apply the filter, the range is no longer contiguous. Your method will only capture a contiguous range.
Because you are setting the Autofilter value from within your routine, lets just check the values inside of an array, and then add the correct values to a dictionary, which will only accept unique values anyways.
Public Sub testUniqueArray()
Dim arrTemp As Variant, key As Variant
Dim dict As Object
Dim i As Long
arrTemp = [a1].CurrentRegion.Value
Set dict = CreateObject("Scripting.Dictionary")
For i = LBound(arrTemp) To UBound(arrTemp)
If arrTemp(i, 1) = "Yes" Then
dict(arrTemp(i, 2)) = 1
End If
Next i
For Each key In dict.Keys
Debug.Print key
Next key
End Sub
Unique Values from Filtered Column to Array
Option Explicit
Sub PrintUniqueValues()
Const CriteriaColumn As Long = 1
Const ValueColumn As Long = 2
Const CriteriaString As String = "Yes"
Dim ws As Worksheet: Set ws = ActiveSheet
' You better improve e.g. by using the worksheet (tab) name...
'Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
'Dim ws As Worksheet: Set ws = wb.Worksheets("Sheet1")
' ... or by using the code name:
'Dim ws As Worksheet: Set ws = Sheet1
Application.ScreenUpdating = False
If ws.AutoFilterMode Then
ws.AutoFilterMode = False
End If
Dim rg As Range: Set rg = ws.Range("A1").CurrentRegion
rg.AutoFilter CriteriaColumn, CriteriaString
Dim Arr As Variant: Arr = ArrUniqueFromFilteredColumn(rg, ValueColumn)
ws.AutoFilterMode = False
Application.ScreenUpdating = True
If IsEmpty(Arr) Then Exit Sub
' Either (preferred when dealing with arrays)...
Dim n As Long
For n = LBound(Arr) To UBound(Arr)
Debug.Print Arr(n)
Next n
' ... or:
' Dim Item As Variant
' For Each Item In Arr
' Debug.Print Item
' Next Item
End Sub
Function ArrUniqueFromFilteredColumn( _
ByVal rg As Range, _
ByVal ValueColumn As Long) _
As Variant
If rg Is Nothing Then Exit Function
If ValueColumn < 1 Then Exit Function
If ValueColumn > rg.Columns.Count Then Exit Function
Dim crg As Range
Set crg = rg.Columns(ValueColumn).Resize(rg.Rows.Count - 1).Offset(1)
Dim CellsCount As Long
CellsCount = WorksheetFunction.Subtotal(103, crg) ' 103 - CountA
If CellsCount = 0 Then Exit Function ' no match or only empty cells
'Debug.Print "CellsCount = " & CellsCount
Dim scrg As Range: Set scrg = crg.SpecialCells(xlCellTypeVisible)
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare ' ignore case
Dim cCell As Range
Dim Key As Variant
For Each cCell In scrg.Cells
Key = cCell.Value
If Not IsError(Key) Then
If Len(Key) > 0 Then
dict(Key) = Empty
' The previous line is a short version of:
'If Not dict.Exists(Key) Then dict.Add Key, Empty
End If
End If
Next cCell
If dict.Count = 0 Then Exit Function ' only errors and blanks
'Debug.Print "dict.Count = " & dict.Count
ArrUniqueFromFilteredColumn = dict.Keys
End Function
I want to use an array of strings that will replace the Worksheet object inside my loop, but I cant seem to figure it out.
If I declare SheetX as Variant, then I get the Object Required Error
If I declare SheetX as Object, then I get Compile Error: For Each variable on arrays must be variant
Sub DeleteAllData()
'SheetsArray = ["BalanceSheetTransposed", "IncomeStatementTransposed", "CashflowStatement"]
Dim SheetsArray(0 To 2) As Variant
Dim SheetX As Object
SheetsArray(0) = "BalanceSheetTransposed"
SheetsArray(1) = "IncomeStatementTransposed"
SheetsArray(2) = "CashflowStatement"
For Each SheetX In SheetsArray
lastrow = SheetX.Cells(Rows.Count, 1).End(xlUp).Row
lastcolumn = SheetX.Cells(1, Columns.Count).End(xlToLeft).Column
SheetX.Range("A2", Cells(lastrow, lastcolumn)).ClearContents
Next SheetX
End Sub
Out of my head 'cause I don't have Excel in this machine. Loop through the strings and set worksheet object.
Sub DeleteAllData()
Dim SheetsArray(0 To 2) As String
Dim SheetX As Worksheet
Dim name as String
SheetsArray(0) = "BalanceSheetTransposed"
SheetsArray(1) = "IncomeStatementTransposed"
SheetsArray(2) = "CashflowStatement"
For Each name In SheetsArray
set SheetX = ActiveWorkbook.worksheets(name)
lastrow = SheetX.Cells(Rows.Count, 1).End(xlUp).Row
lastcolumn = SheetX.Cells(1, Columns.Count).End(xlToLeft).Column
SheetX.Range("A2", Cells(lastrow, lastcolumn)).ClearContents
Next
End Sub
Your major problem was that you were trying to treat the strings stored in the array as if they were worksheets, but they are just strings.
The simplest way to get around it is to use Worksheets(SheetsArray) to return the worksheets that have the names you want to use, and then loop through those worksheets:
Sub DeleteAllData()
Dim SheetX As Worksheet
Dim lastRow As Long
Dim lastColumn As Long
Dim SheetsArray(0 To 2) As Variant
SheetsArray(0) = "BalanceSheetTransposed"
SheetsArray(1) = "IncomeStatementTransposed"
SheetsArray(2) = "CashflowStatement"
'An alternative to the previous 4 lines would be
'Dim SheetsArray As Variant
'SheetsArray = Array("BalanceSheetTransposed", _
' "IncomeStatementTransposed", _
' "CashflowStatement")
'Loop through the worksheets referred to in the array
For Each SheetX In Worksheets(SheetsArray)
With SheetX ' avoids some typing
lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
lastColumn = .Cells(1, .Columns.Count).End(xlToLeft).Column
'Existing code would have had issue with the unqualified Cells
'reference in the following line. You should always qualify Cells
'to specify which sheet you mean, because it defaults to
'ActiveSheet.Cells
.Range("A2", .Cells(lastRow, lastColumn)).ClearContents
End With
Next SheetX
End Sub
The Array has to be passed to the Sheets object :
Sub DeleteAllData()
Dim ws As Worksheet
For Each ws In Sheets(Array("BalanceSheetTransposed", "IncomeStatementTransposed", _
"CashflowStatement"))
s.UsedRange.Offset(1).ClearContents
Next
End Sub
I'm trying to copy worksheets from a master workbook to a target workbook but the sheets that I copy are different depending on if the value in rngCurrent is present in the worksheet name. For some reason I keep getting a subscript out or range error on the last line. Can anyone help me understand what's going on?
Sub test2()
Dim wb As Workbook
Dim master As Workbook
Dim wbCurrent As Workbook
Dim wbAdjustments As Workbook
Dim wsName As Worksheet
Dim rngEntityList As Range
Dim rngCurrentEntity As Range
Dim rngCurrent As Range
Dim arrWorksheets As Variant
Dim i As Integer
Dim wsCount As Integer
Set master = ThisWorkbook
Set rngCurrentEntity = master.Sheets("File Info").Range("rng_Entity") 'named range of single entity
Set rngEntityList = master.Sheets("Global").Range("rng_EntityList") 'list or entities
Set rngCurrent = rngEntityList.Find(rngCurrentEntity.Value, LookIn:=xlValues) ' find single entity in the list
If rngCurrent.Offset(, 4).Value = "FRP" Then 'find if it's FRP
Set wb = Application.Workbooks("Foreign.xlsx")
Else
Set wb = Application.Workbooks("Domestic.xlsx")
End If
Dim ws() As String ' declare string array
ReDim ws(wb.Worksheets.Count) As String ' set size dynamically
Dim counter As Long ' running counter for ws array
counter = 1
For i = 1 To wb.Worksheets.Count
If InStr(1, wb.Worksheets(i).Name, rngCurrent.Value) <> 0 Then
ws(counter) = wb.Worksheets(i).Name
counter = counter + 1
End If
Next
ReDim Preserve ws(counter) As String ' Get rid of empty array entries
wb.Worksheets(ws).Copy After:=master.Worksheets(master.Worksheets.Count)
End Sub
EDIT
The reason I need to do it this way is because I don't want the external links to the source notebook.
Complete and tested example
Sub Tester()
Dim wb As Workbook, i As Long
Set wb = ThisWorkbook
Dim ws() As String ' declare string array
ReDim ws(1 To wb.Worksheets.Count) As String ' set size dynamically
Dim counter As Long ' running counter for ws array
counter = 0
For i = 1 To wb.Worksheets.Count
If InStr(1, wb.Worksheets(i).Name, "test") <> 0 Then
counter = counter + 1
ws(counter) = wb.Worksheets(i).Name
End If
Next
ReDim Preserve ws(1 To counter)
wb.Worksheets(ws).Copy 'just makes a copy in a new workbook
End Sub
do this:
ReDim ws(1 To wb.Worksheets.count) As String ' set size dynamically, start from 1
Dim counter As Long ' running counter for ws array
For i = 1 To wb.Worksheets.count
If InStr(1, wb.Worksheets(i).name, rngCurrent.Value) <> 0 Then
counter = counter + 1 '<--| update counter
ws(counter) = wb.Worksheets(i).name
End If
Next
I have an Excel sheet having 3000 columns and I need to convert this sheet in such a way that one tab will contain 254 columns only and remaining will go to the next tab. So I need a VBA code (Macro) which can perform the same.
As of now I wrote the following code only which is creating 3000 tabs with one column in each, also it is going to infinite loop as I did not put any condition there for blank column.
Sub SpliteIntoMultipleTab()
'
' createtemplates Macro
Dim WS As Worksheet
Dim SS As Worksheet
Dim TemplateName As String
Dim tempstr As String
'
Dim CurCol As String
Dim Template As String
Dim xColIndex As Integer
Dim xRowIndex As Integer
Dim WSCount As Integer
'==========================================================================
'Declarations
CurCol = 1
Template = "Sheet1"
'==========================================================================
Set SS = Worksheets(Template)
If WS Is Nothing Then
Start:
With ActiveWorkbook
Set WS = .Sheets.Add(After:=ActiveSheet)
WSCount = Sheets.Add(After:=Sheets(Worksheets.Count))
On Error Resume Next
Set WS = Worksheets("temp")
WS.Name = SS.Range("A1").Value
End With
Else
End If
SS.Activate
xIndex = Application.ActiveCell.Column
xRowIndex = Application.ActiveSheet.Cells(Rows.Count, xIndex).End(xlUp).Row
Range(Cells(1, xIndex), Cells(xRowIndex, xIndex)).Select
Selection.Copy
WS.Select
WS.Range("A1").Select
ActiveSheet.Paste
SS.Columns(1).EntireColumn.Delete
CurCol = CurCol + 1
GoTo Start
End Sub
Use integer division and modulus, so for example taking the 1000th column
1000 \ 254 = 3
1000 mod 254 = 238
gives the 3rd sheet and the 238th column.
So loop through from 1 to 3000 using \ and mod.
You code is very non-standard and I cannot get my head around it, I suggest you start from my code, this is an illustrative example of breaking a block of data into separate sheets. Copy the code into a new workbook then
Run CreateSheetAndPopulateWithBlockOfData once only to create a block of data.
Run Test to run the BreakBlockIntoChunks routine, you can experiment with the chunk size.
Option Explicit
Private Const csSHEETNAME As String = "Source"
Sub TestCreateSheetAndPopualteWithBlockOfData()
Dim wsSource As Excel.Worksheet
Set wsSource = CreateSheetAndPopulateWithBlockOfData(ThisWorkbook, csSHEETNAME, 20, 100)
End Sub
Sub Test()
Dim wsSource As Excel.Worksheet
Set wsSource = ThisWorkbook.Worksheets.Item(csSHEETNAME)
'Stop
Dim wbResults As Excel.Workbook
Set wbResults = Workbooks.Add
BreakBlockIntoChunks wsSource, 5, wbResults
End Sub
Function BreakBlockIntoChunks(ByVal wsSource As Excel.Worksheet, ByVal lColumnChunkSize As Long, ByVal wbDestinationWorkbook As Excel.Workbook)
Dim rngDataBlock As Excel.Range
Set rngDataBlock = wsSource.Cells(1, 1).CurrentRegion
Dim lSourceColumnCount As Long
lSourceColumnCount = rngDataBlock.Columns.Count
Dim lSourceRowCount As Long
lSourceRowCount = rngDataBlock.Rows.Count
Dim lColumnLoop As Long
For lColumnLoop = 1 To lSourceColumnCount
Dim lCurrentSheet As Long
lCurrentSheet = ((lColumnLoop - 1) \ lColumnChunkSize) + 1
Dim wsCurrentSheet As Excel.Worksheet
If lCurrentSheet > wbDestinationWorkbook.Worksheets.Count Then Set wsCurrentSheet = wbDestinationWorkbook.Worksheets.Add
If wsCurrentSheet Is Nothing Then Set wsCurrentSheet = wbDestinationWorkbook.Worksheets.Item(lCurrentSheet) '* runs first loop
'**ADD your sheet naming logic here perhaps
Dim lCurrentColumn As Long
lCurrentColumn = ((lColumnLoop - 1) Mod lColumnChunkSize) + 1
Dim rngSource As Excel.Range
Set rngSource = wsSource.Range(wsSource.Cells(1, lColumnLoop), wsSource.Cells(lSourceRowCount, lColumnLoop))
Dim rngDestination As Excel.Range
Set rngDestination = wsCurrentSheet.Range(wsCurrentSheet.Cells(1, lCurrentColumn), wsCurrentSheet.Cells(lSourceRowCount, lCurrentColumn))
rngDestination.Value2 = rngSource.Value2 '* <---Copies without using clipboard
Next lColumnLoop
End Function
Function CreateSheetAndPopulateWithBlockOfData(ByVal wb As Excel.Workbook, ByVal sSheetName As String, ByVal lRowsDeep As Long, ByVal lColumnsWide As Long) As Excel.Worksheet
Dim ws As Excel.Worksheet
Set ws = wb.Worksheets.Add
ws.Name = sSheetName
Dim rngBlock As Excel.Range
Set rngBlock = ws.Range(ws.Cells(1, 1), ws.Cells(lRowsDeep, lColumnsWide))
rngBlock.Formula = "=RANDBETWEEN(1,100000)"
rngBlock.Value2 = rngBlock.Value2
Set CreateSheetAndPopulateWithBlockOfData = ws
End Function
you could try this:
Sub SpliteIntoMultipleTab()
Dim colNum As Long, iCol As Long
With Worksheets("Sheet1").UsedRange
colNum = .Columns.count
Do
Worksheets.Add(After:=Worksheets(Worksheets.count)).Range("A1:IT1").Resize(.Rows.count).Value = .Columns(iCol + 1).Resize(, 254).Value
iCol = iCol + 254
colNum = colNum - 254
Loop While colNum > 0
End With
End Sub
which copies values only and speed up things considerably