I am now trying to creating several worksheets and copying data from an existing worksheet to the worksheet that I just created.
This is what I have tried so far:
Sub CreateTemplate()
Worksheets.Add(After:=Worksheets(Worksheets.Count)).Name = "CUST001"
Worksheets("Template").Cells.Copy Worksheets("CUST001").Cells
Worksheets("CUST001").Select
Range("C4") = "='CDE Information'!R[-2]C[-2]"
Range("C5") = "='CDE Information'!R[-3]C[-1]"
Range("C6") = "1111"
Range("C7") = "2222"
End Sub
This is an example of a table that I want to copy.
Table
I also want to create the worksheets and name them by the values of each row in column A.
So, it seems to me that I should do something with loops but I have no idea about that.
Can anyone help me out? Thank you in advance!
Welcome to stack. Try this:
Option Explicit
Sub copyWs()
Dim arr, j As Long
With Sheet1
arr = .Range("A1").CurrentRegion.Value2 'get all data in memory
For j = 1 To UBound(arr) 'traverse rows
.Copy After:=ActiveWorkbook.Sheets(Worksheets.Count) 'add ws after the last ws
Sheets(ActiveWorkbook.Sheets(Worksheets.Count).Index).Name = arr(j, 1) 'name the last added ws
Next j
End With
End Sub
Now that we already have an array with all data we can also copy only part of our data to a new sheet instead of copying the whole sheet. To achieve this we'll just create a blank sheet first:
Sheets.Add After:=ActiveWorkbook.Sheets(Worksheets.Count) 'add ws after the last ws
When iterating an array we'll use 2 "counter" variables. 1 to go trough the lines, 1 to go trough the columns.
Dim j As Long, i As Long 'initiate our counter vars
For j = 1 To UBound(arr) 'traverse rows
For i = 1 To UBound(arr, 2) 'traverse columns
'here we can access each cell by referencing our array(<rowCounter>, <columnCounter>
'e.g. arr(j,i) => if j = 1 and i = 1 we'll have the values of Cell A1
Next i
Next j
The "Ubound" function allows us to get the total nr of rows and columns.
Dim arr2
ReDim arr2(1 To 1, 1 To UBound(arr)) '=> we only need 1 line but all columns of the source, as we cannot dynamically size an array with the "dim", we redim
For j = 1 To UBound(arr) 'traverse rows
For i = 1 To UBound(arr, 2) 'traverse columns
'here we can access each cell by referencing our array(<rowCounter>, <columnCounter>
'e.g. arr(j,i) => if j = 1 and i = 1 we'll have the values of Cell A1
'we can dump these values anywhere in the activesheet, other sheet, other workbook, .. but to limit the number of interactions with our sheet object we can also create new, intermediant arrays
'e.g. we could now copy cel by cel to the new sheet => Sheets(arr(j,1).Range(... but this would create significant overhead
'so we'll use an intermediant array to store the full line
arr2(1, i) = arr(j, i)
Next i
'when we have all the columns we dumb to the sheet
With Sheets(arr(j, 1)) 'the with allows us the re-use the sheet name without typing it again
.Range(.Cells(1, 1), .Cells(UBound(arr2), UBound(arr2, 2))).Value2 = arr2 'the ubound function allows us to size the "range" to the same size as our array, once that's done we can just dumb it to the sheet
End With
Next j
Related
I am new to VBA and am trying to copy the column from Row 2 onwards where the column header (in Row 1) contains a certain word- "Unique ID".
Currently what I have is:
Dim lastRow As Long
lastRow = ActiveWorkbook.Worksheets("Sheets1").Range("A" & Rows.Count).End(xlUp).Row
Sheets("Sheets1").Range("D2:D" & lastRow).Copy
But the "Unique ID" is not always in Column D
You can try following code, it loops through first row looking for a specified header:
Sub CopyColumnWithHeader()
Dim i As Long
Dim lastRow As Long
For i = 1 To Columns.Count
If Cells(1, i) = "Unique ID" Then
lastRow = Cells(Rows.Count, i).End(xlUp).Row
Range(Cells(2, i), Cells(lastRow, i)).Copy Range("A2")
Exit For
End If
Next
End Sub
When you want to match info in VBA you should use a dictionary. Additionally, when manipulating data in VBA you should use arrays. Although it will require some learning, below code will do what you want with minor changes. Happy learning and don't hesitate to ask questions if you get stuck:
Option Explicit
'always add this to your code
'it will help you to identify non declared (dim) variables
'if you don't dim a var in vba it will be set as variant wich will sooner than you think give you a lot of headaches
Sub DictMatch()
'Example of match using dictionary late binding
'Sourcesheet = sheet1
'Targetsheet = sheet2
'colA of sh1 is compared with colA of sh2
'if we find a match, we copy colB of sh1 to the end of sh2
'''''''''''''''''
'Set some vars and get data from sheets in arrays
'''''''''''''''''
'as the default is variant I don't need to add "as variant"
Dim arr, arr2, arr3, j As Long, i As Long, dict As Object
'when creating a dictionary we can use early and late binding
'early binding has the advantage to give you "intellisense"
'late binding on the other hand has the advantage you don't need to add a reference (tools>references)
Set dict = CreateObject("Scripting.Dictionary") 'create dictionary lateB
dict.CompareMode = 1 'textcompare
arr = Sheet1.Range("A1").CurrentRegion.Value2 'load source, assuming we have data as of A1
arr2 = Sheet2.Range("A1").CurrentRegion.Value2 'load source2, assuming we have data as of A1
'''''''''''''''''
'Loop trough source, calculate and save to target array
'''''''''''''''''
'here we can access each cell by referencing our array(<rowCounter>, <columnCounter>
'e.g. arr(j,i) => if j = 1 and i = 1 we'll have the values of Cell A1
'we can write these values anywhere in the activesheet, other sheet, other workbook, .. but to limit the number of interactions with our sheet object we can also create new, intermediant arrays
'e.g. we could now copy cel by cel to the new sheet => Sheets(arr(j,1).Range(... but this would create significant overhead
'so we'll use an intermediate array (arr3) to store the results
'We use a "dictionary" to match values in vba because this allows to easily check the existence of a value
'Together with arrays and collections these are probably the most important features to learn in vba!
For j = 1 To UBound(arr) 'traverse source, ubound allows to find the "lastrow" of the array
If Not dict.Exists(arr(j, 1)) Then 'Check if value to lookup already exists in dictionary
dict.Add Key:=arr(j, 1), Item:=arr(j, 1) 'set key if I don't have it yet in dictionary
End If
Next j 'go to next row. in this simple example we don't travers multiple columns so we don't need a second counter (i)
'Before I can add values to a variant array I need to redim it. arr3 is a temp array to store matching col
'1 To UBound(arr2) = the number of rows, as in this example we'll add the match as a col we just keep the existing nr of rows
'1 to 1 => I just want to add 1 column but you can basically retrieve as much cols as you want
ReDim arr3(1 To UBound(arr2), 1 To 1)
For j = 1 To UBound(arr2) 'now that we have all values to match in our dictionary, we traverse the second source
If dict.Exists(arr2(j, 1)) Then 'matching happens here, for each value in col 1 we check if it exists in the dictionary
arr3(j, 1) = arr(j, 2) 'If a match is found, we add the value to find back, in this example col. 2, and add it to our temp array (arr3).
'arr3(j, 2) = arr(j, 3) 'As explained above, we could retrieve as many columns as we want, if you only have a few you would add them manually like in this example but if you have many we could even add an additional counter (i) to do this.
End If
Next j 'go to the next row
'''''''''''''''''
'Write to sheet only at the end, you could add formatting here
'''''''''''''''''
With Sheet2 'sheet on which I want to write the matching result
'UBound(arr2, 2) => ubound (arr2) was the lastrow, the ubound of the second dimension of my array is the lastcolumn
'.Cells(1, UBound(arr2, 2) + 1) = The startcel => row = 1, col = nr of existing cols + 1
'.Cells(UBound(arr2), UBound(arr2, 2) + 1)) = The lastcel => row = number of existing rows, col = nr of existing cols + 1
.Range(.Cells(1, UBound(arr2, 2) + 1), .Cells(UBound(arr2), UBound(arr2, 2) + 1)).Value2 = arr3 'write target array to sheet
End With
End Sub
Hi all! I have posted the same question previously but this one time is more explained with screenshots of the file.
I have 85 sheets (Sheet 1 screenshot for reference) and a specified range for each sheet (I12:N42). But this range consists formulas & cell references. What I am trying to do is:
Copying the data from all the 85 sheets with this range (I12:N42) except if "Qty = 0".
Pasting the copied data by PasteValues only to a master sheet.
PS: I tried to do the same thing using Power Query but it is quite slow so maybe VBA code will work faster on this.
Appreciate you guys!
Please, try the next code. It assumes that there are no other sheets except the master one and the mentioned 85 to copy from. If others, except them adding new conditions where the master one is skipped:
Sub copyNonZeroRowsInMaster()
Dim sh As Worksheet, shM As Worksheet, rng As Range, lastRow As Long, boolOK As Boolean
Dim arrRows, arrCopy, arr, arrSlice, count0 As Long, i As Long, k As Long
lastRow = 2 'the initial row where to paste
Set shM = ActiveWorkbook.Sheets("MASTER") 'please, use here the appropriate sheet name
shM.Range("A2:F10000").ClearContents
For Each sh In ActiveWorkbook.Sheets
If sh.Name <> shM.Name And sh.Name <> "TRACKING" Then 'if other sheets needs to be excepted, add them in the condition
Set rng = sh.Range("K13:K42") 'the range being the reference for non zero values
arrCopy = sh.Range("I13:N42").Value 'place all the range to be processed in an array, to make code faster
count0 = Application.CountIf(rng, 0) 'count the zero values (even from formulas) to redim in the next row
arr = rng.Value 'place the reference range in an array (also, to make the code faster)
If rng.Count - count0 = 0 Then GoTo OverProcessing
ReDim arrRows(1 To rng.Count - count0, 1 To 1) 'redim the array to keep the row numbers without 0 in K:K
k = 1: boolOK = False 'initialize the variable based on what the array keeping the rows to be copied is loaded
For i = 1 To UBound(arr) 'iterate beteen the array elements
If arr(i, 1) <> 0 Then
arrRows(k, 1) = i: k = k + 1 'fill the rows to be copied number in the array
boolOK = True
End If
Next i
If Not boolOK Then GoTo OverProcessing 'if there are only zero in all processed K:K range
arrSlice = Application.Index(arrCopy, arrRows, Array(1, 2, 3, 4, 5, 6)) 'create a slice array keeping only the non zero rows
'drop the slice array content at once:
shM.Range("A" & lastRow).Resize(IIf(k = 2, UBound(arrRows), UBound(arrSlice)), 6).Value = arrSlice
lastRow = shM.Range("A" & shM.Rows.Count).End(xlUp).Row + 1 'recalculate the last empty row
End If
OverProcessing:
Next
MsgBox "Ready..."
End Sub
The code is not tested (except the principle of working) and it should be very fast. Please, send some feedback after testing it.
I've been running this script for a while with not issues, and then today it broke. It's very basic as in I'm just filtering values from one tab and then copying and pasting them onto another tab in the top row. Suddenly though, it will paste the values and then repeat paste the values 19 more times for a total of 20 copy pastes.
Sheets("BSLOG").Select
Range("Q1").Select
Selection.AutoFilter Field:=17, Criteria1:="1"
Range("A1:Q5000").Select
Range("A1:Q5000").Activate
Selection.Copy
Sheets("PENDG TRADES").Select
Range("A1:Q300").Select
ActiveSheet.Paste
Try the next code, please. No need to select, activate anything. In this case, these selections do not bring any benefit, they only consume Excel resources:
Sub testFilterCopy()
Dim shB As Worksheet, shP As Worksheet
Set shB = Sheets("BSLOG")
Set shP = Sheets("PENDG TRADES")
shB.Range("Q1").AutoFilter field:=17, Criteria1:="1"
shB.Range("A1:Q5000").Copy shP.Range("A1")
End Sub
If you want to make the range dynamic (in terms of rows) I can show you how to initially calculate the existing number of rows and set the range to be copied according to it.
FaneDuru is right.
You can also try this code, which I prefer more:
Option Base 1 'This means all array starts at 1. It is set by default at 0. Use whatever you prefer,depending if you have headers or not, etc
Sub TestFilter()
Dim shBSLOG As Worksheet
Dim shPENDG As Worksheet
Dim rngBSLOG As Range
Dim arrBSLOG(), arrCopy()
Dim RowsInBSLOG&
Dim i&, j&, k&
Set shBSLOG = Worksheets("BSLOG")
Set shPENDG = Worksheets("PENDG TRADES")
With shBSLOG
Set rngBSLOG = .Range(.Cells(1, 1), .Cells(5000, 17))
End With
RowsInBSLOG = rngBSLOG.Rows.Count
arrBSLOG = rngBSLOG
ReDim arrCopy(1 To RowsInBSLOG, 1 To 17) 'set the size of the new array as the original array
k = 1 'k is set to 1. This will be used to the row of the new array "arrCopy"
For i = 1 To RowsInBSLOG 'filter the array. From the row "i" = 1 to the total of rows "RowsinBSLOG
If arrBSLOG(i, 1) = 1 Then 'if the first column of the row i is equal to 1, then...
For j = 1 To 17
arrCopy(k, j) = arrBSLOG(i, j) 'copy the row
Next j
k = k + 1 'next copy will be in a new row
End If
Next i 'repeat
With shPENDG
.Range(.Cells(1, 1), .Cells(k, 17)) = arrCopy() 'place the new array in the new sheet
End With
End Sub
All -
I have a 2 sheet excel.
Sheet 1 is three columns (name, date, value)
Sheet 2 is name.
I want to write a VBA script that displays all of Sheet 1 data that does NOT have any of the name field listed in Sheet 2 anywhere in sheet 1 (name can appear in different columns so ideally it would search all cells in Sheet 1) to appear in sheet 3
See the sample image for a rough idea of what I"m hoping to accomplish. I have searched but have not had luck.
If you have Excel 365 you can use the Dynamic Array formulas
=LET(Names,FILTER(Sheet1!$C:$E,Sheet1!$C:$C<>""),FILTER(Names,ISERROR(MATCH(INDEX(Names,,1),Sheet2!$G:$G,0))))
Example:
Data (Sheet1)
Exclusion List (Sheet2)
Result
Note: this excludes the headers because the header label Name is present in both the Data column and the Exclusion column so be sure to maintain that
Without Excel 365. I'd recommend a UDF
Function FilterList(ByVal Data As Range, ByVal Exclusion As Range) As Variant
Dim Res As Variant
Dim Dat As Variant
Dim Excl As Variant
Dim rw As Long
Dim idx As Long
Dim cl As Long
Dim ExcludeIt As Variant
Dim Cols As Long
Dim TopRow As Long
ReDim Res(1 To Application.Caller.Rows.Count, 1 To Application.Caller.Columns.Count)
If IsEmpty(Data.Cells(1, 1)) Then
TopRow = Data.Cells(1, 1).End(xlDown).Row
Set Data = Data.Resize(Data.Rows.Count - TopRow).Offset(TopRow - 1)
End If
If IsEmpty(Data.Cells(Data.Rows.Count, 1)) Then
Set Data = Data.Resize(Data.Cells(Data.Rows.Count, 1).End(xlUp).Row - Data.Row + 1)
End If
Dat = Data.Value
Excl = Exclusion.Columns(1).Value
Cols = Application.Min(UBound(Dat, 2), UBound(Res, 2))
idx = 0
For rw = 1 To UBound(Dat, 1)
ExcludeIt = Application.Match(Dat(rw, 1), Excl, 0)
If IsError(ExcludeIt) Then
idx = idx + 1
For cl = 1 To Cols
Res(idx, cl) = Dat(rw, cl)
Next
End If
Next
For rw = 1 To UBound(Res, 1)
For cl = IIf(rw <= idx, UBound(Dat, 2) + 1, 1) To UBound(Res, 2)
Res(rw, cl) = vbNullString
Next
Next
FilterList = Res
End Function
Enter it as an Array Formula (complete it with Ctrl+Shift+Enter) in a range large enough to hold the returned data (can be larger), and pass it your input Data range and Exclusion range (both as whole columns)
=FilterList(Sheet1!$C:$E,Sheet2!$G:$G)
Welcome to Stack Overflow!
You did not say where the source table and criteria table begin, or where to place the result of the "anti-filter". I wrote this code on the assumption that they all start at the first cell of the worksheet, A1:
Sub AntiFilter()
Dim aSource As Range, aCriteria As Range, oCell As Range, oTarget As Range, countCells As Long
Set aSource = Worksheets("Sheet1").Range("A1").CurrentRegion
countCells = aSource.Columns.Count
Set aCriteria = Worksheets("Sheet2").Range("A1").CurrentRegion
Set oTarget = Worksheets("Sheet3").Range("A1")
aSource.AdvancedFilter Action:=xlFilterInPlace, CriteriaRange:=aCriteria, Unique:=False
For Each oCell In Application.Intersect(aSource, aSource.Columns(1))
If oCell.RowHeight < 1 Then
oCell.Resize(1, countCells).Copy Destination:=oTarget
Set oTarget = oTarget.Offset(1, 0)
End If
Next oCell
On Error Resume Next
aSource.Worksheet.ShowAllData
On Error GOTO 0
End Sub
Workbook with macro, test data and examples of selection criteria on Sheet2
If the macro does not work as expected, make sure that you have sheets named Sheet1, Sheet2, and Sheet3 in your workbook, and that the source data range and criteria range start with cells A1. If this is not the case, make the necessary changes to the text of the macro:
I have a large dataset with 60 sheets from which I need to extract three values per sheet individually. These values are to be summarized in one sheet so I can have an overview of the data.
I took an internet crash course in VBA to try and write a macro to avoid having to do this manually. My idea (that I was able to translate to code) was to copy the three cells per sheet to the rows of a 2D array, so I would end up with a [60x3] matrix that could be copied to a newly created sheet 'Means'. (I understand that this is very inefficient however it's the best I could come up with for now.)
The code runs but produces undesirable results: the matrix only consists out of a triplet of values from the final sheet. What I actually need from my macro is that it copies three values from sheet1 and pastes them to MeanTable(1,:), then copies three values from sheet2 and pastes them to MeanTable(2,:) and so on. I am convinced this is happening because my first nested loop is rubbish so I have been trying different loops and adding loops (and web searching of course) but so far I haven't been able to solve it.
Sub copy_to_one_sheet() 'copy sample means from each sheet to Means
Application.ScreenUpdating = False
Dim ws As Worksheet
Dim NumSheets As Integer
Dim NumSamples As Integer
Dim MeanTable() As Long 'store sample means in this 2D array, its size defined by number of sheets and samples per sheet
NumSheets = Application.Sheets.Count 'count number of sheets
NumSamples = 3 'number of samples per sheet (hardcoded for now)
ReDim MeanTable(NumSheets, 1 To NumSamples) 'MeanTable will be filled with sample means
'============================================
'= copy sample means per sheet to MeanTable =
'============================================
For i = 1 To UBound(MeanTable, 1) 'copy sample means from fixed columns per sheet to individual rows of Table array
For Each ws In ThisWorkbook.Worksheets 'go through sheets
MeanTable(i, 1) = ws.Cells(Rows.Count, 3).End(xlUp).Offset(-3, 0).Value
MeanTable(i, 2) = ws.Cells(Rows.Count, 10).End(xlUp).Offset(-3, 0).Value
MeanTable(i, 3) = ws.Cells(Rows.Count, 17).End(xlUp).Offset(-3, 0).Value
Next ws
Next i
'=============================================
'= create Sheet("Means") and paste MeanTable =
'=============================================
With ThisWorkbook
Set Dst = .Sheets.Add(After:=.Sheets(.Sheets.Count)) 'create new worksheet
Dst.Name = "Means" 'worksheet name
With Sheets("Means")
For k = 1 To UBound(MeanTable, 1)
For l = 1 To NumSamples
Cells(k, l).Value = MeanTable(k, l) 'paste Table variable with sample means to new worksheet ("Means")
Next l
Next k
End With
End With
End Sub
My question is: how can I make my loops cycle through each sheet in the workbook and copy a triplet of values to the corresponding rows of MeanTable, before moving on to the next sheet?
Any help would be greatly appreciated!
For i = 1 To UBound(MeanTable, 1) needed to be replaced with a counter i = i + 1`. Use Range.Resize to fill a range from an array.
Sub copy_to_one_sheet() 'copy sample means from each sheet to Means
Application.ScreenUpdating = False
Dim ws As Worksheet
Dim NumSheets As Integer
Dim NumSamples As Integer
Dim MeanTable() As Long 'store sample means in this 2D array, its size defined by number of sheets and samples per sheet
NumSheets = Application.Sheets.Count 'count number of sheets
NumSamples = 3 'number of samples per sheet (hardcoded for now)
ReDim MeanTable(1 To NumSheets, 1 To NumSamples) 'MeanTable will be filled with sample means
For Each ws In ThisWorkbook.Worksheets 'go through sheets
i = i + 1
MeanTable(i, 1) = ws.Cells(Rows.Count, 3).End(xlUp).Offset(-3, 0).Value
MeanTable(i, 2) = ws.Cells(Rows.Count, 10).End(xlUp).Offset(-3, 0).Value
MeanTable(i, 3) = ws.Cells(Rows.Count, 17).End(xlUp).Offset(-3, 0).Value
Next ws
With ThisWorkbook
Set Dst = .Sheets.Add(After:=.Sheets(.Sheets.Count)) 'create new worksheet
Dst.Name = "Means" 'worksheet name
With Sheets("Means")
.Range("A1").Resize(UBound(MeanTable, 1), UBound(MeanTable, 2)).Value = MeanTable
End With
End With
End Sub