Ok, this might take a bit to explain. I have a single MS Excel setting log sheet for a machine in our plant that contains setting specs for over 100 different part numbers built on that machine (each row is a different part number). We're switching to a new format that will have a separate sheet for each part number, with each sheet following a certain format (I set up a template beforehand and copied it for each part number). The first column of the old log sheet contains all of the part numbers, and the sheet names correspond to these part numbers.
So I'm trying to set up a program that will repeat for each part number (each sheet), and will find that number in the first column of the old log sheet. Then it will pull the value from the cell in, let's say, column B of that row, and will place that value in a specific cell in the sheet for that part number. Now, this will need to pull data from several cells for each part number, but if I can get it to work for one, I can go from there.
Here's what I've got so far:
Sub EditSettings()
Dim i As Integer
Dim j As Integer
Dim lastrow As Integer
lastrow = Sheets("M200 46mm Setting Log").Range("A" & Rows.Count).End(xlUp).Row
For i = 5 To Worksheets.Count
For j = 4 To lastrow
If Sheets("M200 46mm Setting Log").Cells(j, "A").Value = "" Then
ElseIf Sheets("M200 46mm Setting Log").Cells(j, "A").Value = Sheets(i).Name Then
Sheets("M200 46mm Setting Log").Range(j, "B").Copy _
Destination:=Sheets(i).Range("D11")
End If
Next
Next
End Sub
When I run this however, it gives me an error saying "Run-time error '1004': Application-defined or object-defined error".
This is easier than you might imagine. You don't need to loop through each of the worksheets because you can assign the target worksheet by its name: Set ws = WorkSheets([name as a string]). So you really only need to loop through the rows and pick up each sheet name.
I'd also recommend creating an address map of each old sheet cell and its new sheet cell address. In your example, column "B" goes to "D11", so create a collection of all of these and simply loop through them for the copying. There are quicker ways of doing it but with only 100 or so different parts it's not worth worrying about.
The code below shows you how to do both. From your question you mention that you have created templates for each new sheet. Presumably, then, the format is correctly set, so you've no need to do a copy/paste, just write each cell value to the new cell.
By the way, the most obvious error in your code (and probably the reason for your error) is that this line: Sheets("M200 46mm Setting Log").Range(j, "B").Copy ... should be Sheets("M200 46mm Setting Log").Cells(j, "B").Copy ...
Sub RunMe()
Dim wsLog As Worksheet
Dim wsPart As Worksheet
Dim sheetName As String
Dim addressMap As Collection
Dim map As Variant
Dim lastRow As Long
Dim r As Long
Set addressMap = New Collection
' Map the transfer cell addresses
addressMap.Add SetRangeMap("B", "D11")
' 1st item is old log sheet column, 2nd is address of new sheet
' ...
' ... repeat for all the address maps
' ...
'Loop through the rows in the source sheet
Set wsLog = ThisWorkbook.Worksheets("M200 46mm Setting Log")
lastRow = wsLog.Cells(wsLog.Rows.Count, "A").End(xlUp).Row
For r = 4 To lastRow
' Acquire the sheet name from the part number cell
sheetName = CStr(wsLog.Cells(r, "A").Value2)
' Try to assign the parts number worksheet
Set wsPart = Nothing
On Error Resume Next
Set wsPart = ThisWorkbook.Worksheets(sheetName)
On Error GoTo 0
' If assigment worked then transfer data
If Not wsPart Is Nothing Then
For Each map In addressMap
wsPart.Range(map(1)).Value = wsLog.Cells(r, map(0)).Value2
Next
End If
Next
End Sub
Private Function SetRangeMap(sourceCol As String, partAddress As String) As Variant
Dim map(1) As String
map(0) = sourceCol
map(1) = partAddress
SetRangeMap = map
End Function
Related
I am working with data where the only consistency is the layout and the bold headings to distinguish between a new date.
I am trying to find the cells in between these cells in bold, find the value "Individual" (in column A) in the selected rows, then sum the values of the given rows in column D (as there can be more then 1 row with "Individual"), and copy this new value to a different cell.
Since the cells between the bold is one date, if the value is not there, the output cell needs to shift down one without filling in anything.
Here is what I have so far:
Sub SelectBetween()
Dim findrow As Long, findrow2 As Long
findrow = range("A:A").Find("test1", range("A1")).Row
findrow2 = range("A:A").Find("test2", range("A" & findrow)).Row
range("A" & findrow + 1 & ":A" & findrow2 - 1).Select
Selection.Find("Individual").Activate
range("D" & (ActiveCell.Row)).Select
Selection.copy
sheets("Mix of Business").Select
range("C4").Select
ActiveSheet.Paste
Exit Sub
errhandler:
MsgBox "No Cells containing specified text found"
End Sub
How can I loop through the data and each time it loops through a range, no matter if it finds the value (e.g. individual) or not, shifts down one row on the output cell? Also, how can I change the findrow to be a format (Bold) rather then a value?
Here is some data for reference:
This is what I am trying to get it to look like:
So you have a good start to trying to work through your data. I have a few tips to share that can hopefully help get you closer. (And please come back and ask more questions as you work through it!)
First and foremost, try to avoid using Select or Activate in your code. When you look at a recorded macro, I know that's all you see. BUT that is a recording of your keystrokes and mouseclicks (selecting and activating). You can access the data in a cell or a range without it (see my example below).
In order to approach your data, your first issue is to figure out where your data set starts (which row) and where it ends. Generally, your data is between cells with BOLD data. The exception is the last data set, which just has a many blank rows (until the end of the column). So I've created a function that starts at a given row and checks each row below it to find either a BOLD cell or the end of the data.
Private Function EndRowOfDataSet(ByRef ws As Worksheet, _
ByVal startRow As Long, _
Optional maxRowsInDataSet As Long = 50) As Long
'--- checks each row below the starting row for either a BOLD cell
' or, if no BOLD cells are detected, returns the last row of data
Dim checkCell As Range
Set checkCell = ws.Cells(startRow, 1) 'assumes column "A"
Dim i As Long
For i = startRow To maxRowsInDataSet
If ws.Cells(startRow, 1).Font.Bold Then
EndRowOfDataSet = i - 1
Exit Function
End If
Next i
'--- if we make it here, we haven't found a BOLD cell, so
' find the last row of data
EndRowOfDataSet = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
End Function
To show you how to use that with your specific data, I've created a test subroutine indicating how to loop through all the different data sets:
Option Explicit
Public Sub DataBetween()
Dim thisWB As Workbook
Dim dataWS As Worksheet
Set thisWB = ThisWorkbook
Set dataWS = thisWB.Sheets("YourNameOfSheetWithData")
'--- find the first bold cell...
'Dim nextBoldCell As Range
'Set nextBoldCell = FindNextBoldInColumn(dataWS.Range("A1"))
'--- now note the start of the data and find the next bold cell
Dim startOfDataRow As Long
Dim endOfDataRow As Long
Dim lastRowOfAllData As Long
startOfDataRow = 3
lastRowOfAllData = dataWS.Cells(ws.Rows.Count, "A").End(xlUp).Row
'--- this loop is for all the data sets...
Loop
endOfDataRow = EndRowOfDataSet(dataWS, startOfDataRow)
'--- this loop is to work through one data set
For i = startOfDataRow To endOfDataRow
'--- work through each of the data rows and copy your
' data over to the other sheet here
Next i
startOfDataRow = endOfDataRow + 1
Do While endOfDataRow < lastRowOfAllData
End Sub
Use both of those together and see if that can get you closer to a full solution.
EDIT: I should have deleted that section of code. It was from an earlier concept I had that didn't completely work. I commented out those lines (for the sake of later clarity in reading the comments). Below, I'll include the function and why it didn't completely work for this situation.
So here's the function in question:
Public Function FindNextBoldInColumn(ByRef startCell As Range, _
Optional columnNumber As Long = 1) As Range
'--- beginning at the startCell row, this function check each
' lower row in the same column and stops when it encounters
' a BOLD font setting
Dim checkCell As Range
Set checkCell = startCell
Do While Not checkCell.Font.Bold
Set checkCell = checkCell.Offset(1, 0)
If checkCell.Row = checkCell.Parent.Rows.Count Then
'--- we've reached the end of the column, so
' return nothing
Set FindNextBoldInColumn = Nothing
Exit Function
End If
Loop
Set FindNextBoldInColumn = checkCell
End Function
Now, while this function works perfectly well, the situation is DOES NOT account for is the end of the last data set. In other words, a situation like this:
The function FindNextBoldInColumn will return nothing in this case and not the end of the data. So I (should have completely) deleted that function and replaced it with EndRowOfDataSet which does exactly what you need. Sorry about that.
Concept:
Entire Rows are deleted through a macro based off parameters which are represented as an excel formula by the user. The idea here is that a user can use Boolean formulas that they're already familiar with to evaluate values in a range (read the "Process" below for further clarification).
Process:
A user clicks on a button which shows a form. This form contains two input fields (or parameters); "Column" and "Formula". The "Column" is the range for which the macro will be cycling through (let's say $A:$A). The "Formula" is an Excel based formula represented as such, in the user parameter field ie =OR(A1="X",A1="Y"). However, I've instructed the user to replace any instance of A1 with rng. I've requested the user to do this because the idea here is that I would replace rng with a changing variable in VBA that cycles through all the cells specified in the "Column" parameter.
Problem:
I'm not aware of any way to replace the rng representation within the Excel formula with a range variable in VBA.
Update 4-7-17
Thank you all for your responses but I'm pretty certain my problem is getting lost in translation. I'm aware this is my fault, since I didn't provide any code for analysis. Unfortunately, therein lies the issue. I don't know what to write. I'm going to do my best to write some code (that I know is wrong) which will hopefully convey what I'm trying to accomplish.
Sample Code 4-7-17
Sub SampleCode()
Dim wRng As Range
Set wRng = Range("A1:A26") 'Let's assume that the values in this range are the
' letters of the alphabet
Dim Counter As Integer
'Cell "B2" will contain a formula that the user has entered
'which is: =OR(rng="X",rng="M")
'Obviously the formula returns an error in excel (#NAME? to be
'exact) but that's understood.
Dim wFormula As String
wFormula = Range("B2").Formula
Dim rng As Range 'This variable "rng" is what is represented in the
'formula that was written in Range("B2")
'*** This is where I get stuck. I know I'm missing code here to
'be able to proceed with my routine below.
'code
'code
'code
Counter = wRng.Rows.Count
For i = 1 To wRng.Rows.Count
Set rng = Cells(Counter, 1)
If wFormula = True Then
rng.EntireRow.Delete
End If
Counter = Counter - 1
Next i
'The ending result should be that row 24 was deleted because it contained
'the letter "X" and row 13 was deleted because it contained the letter "M"
'
'The objective of this code is to use any Excel formula which evaluates out
'to a True or False value.
End Sub
Hey Jon first you need to declare a Variable of relevant data type, then pass value from Range & finally use where you wish to, like,
Dim Src As Variant
Src= Sheets( "Sheet3" ).Range( "A2:A9" ).Value
Hey John this code will help you to get the solution,
Public Sub ProcessData()
Const TEST_COLUMN As String = "A"
Dim Lastrow As Long Dim i As Long
Application.ScreenUpdating = False
With ActiveSheet
Lastrow = .Cells(.Rows.Count, TEST_COLUMN).End(xlUp).Row
For i = Lastrow To 1 Step -1
If Cells(i, TEST_COLUMN).Value2 Like "AU" Or _ Cells(i, TEST_COLUMN).Value2 Like "AZ" Then
.Rows(i).Delete
End If
Next i
End With
Application.ScreenUpdating = True
End Sub
I'm new to VBA and am trying to design a program that will go through a column with Strings in it and for every unique String name create a new worksheet object with that String value as its name and then copy and paste the values in that row to the new sheet. All identical Strings should then also have the values in their row copied over to the new sheet. The data is not sorted based on the Strings so I might have String a, String b, String a, in a column and I want both String a's to be a part of the same new sheet. Before I added a few lines of code to account for this everything was working fine, but now I'm getting an application defined or object defined error at an if statement that shouldn't be related to the added code. Here it is:
Sub FilterByClass()
Dim i As Long
Dim j As Long
Dim sheetName As String
Dim sheet As Worksheet
Dim book As Workbook
Dim k As Integer
ActiveSheet.Name = "AllClasses"
sheetName = Worksheets("AllClasses").Cells(2, 1).Value
Worksheets.Add
ActiveSheet.Name = sheetName
Worksheets("AllClasses").Activate
ActiveSheet.Rows("1:2").Copy
Worksheets(sheetName).Paste
j = 3
k = 0
For i = 3 To Rows.Count
If Worksheets("AllClasses").Cells(i, 1).Value <> Worksheets("AllClasses").Cells(i - 1, 1).Value Then //site of error
Worksheets("AllClasses").Range("1:1," & j & ":" & (i - 1)).Copy
Worksheets(Worksheets("AllClasses").Cells((i - 1), 1).Value).Paste
j = i
sheetName = Worksheets("AllClasses").Cells(i, 1).Value
For Each sheet In ActiveWorkbook //new added code block
If sheetName = sheet.Name Then k = 1
Next sheet
If k = 1 Then k = 0
Else
Worksheets.Add
ActiveSheet.Name = sheetName
Worksheets("AllClasses").Activate
End If
Next i
End Sub
Any help would be greatly appreciated.
ΒΈ
I've noticed a few things wrong with your code that are easier to point out in an answer rather than a comment.
1. Code not valid --Ignore this--
You seem to be missing 2 End Ifs in the code you posted. I can only assume it's just a copy-paste error, so I'll swiftly move on.
2. Line with comment "new added code block"
Your code says:
For Each sheet In ActiveWorkbook
you should replace that with this:
For Each sheet In ActiveWorkbook.Worksheets
The Workbook is not a collection of sheets, the workbook's .Worksheets function is.
3. Termination of the outer For loop
In the comments, you said that the original error happens in the first iteration of your loop inside the If statement, but I'm not convinced. Having had a quick play with your code, I think the error you're seeing ('1004' : "Application-defined or object-defined error") is a result of a different problem.
In my run-through of your code, this line in the Else block of If k = 1:
ActiveSheet.Name = sheetName
caused the error. That is because this line:
sheetName = Worksheets("AllClasses").Cells(i, 1).Value
returned sheetName = "".
This situation happens when a cell in position Cells(i, 1) is empty, which is entirely possible in your code since your outer For loop is iterating over all rows in the "AllClasses" sheet -- all 1048576 of them (in Excel 2007 and later versions). Unless you've got a value in every single row's column 1 (which I doubt), then at some point you'll meet a cell that is blank. Assigning that blank string to ActiveSheet.Name will cause the error you're seeing.
You can either hardcode the value of your outer For loop's terminating condition or you can use the various "tricks" to dynamically determine that value, e.g. Sheet.UsedRange.Rows.Count or Sheet.Cells(1048576, col).End(xlUp).row.
I am working on a macro that will consolidate two different sources of order data. The first source will contain old orders as well as some new, the second source will contain only the old orders and will have additional data in columns that were updated manually.
My idea for this is to take the order totals from the second source, paste them in a sheet after the order totals from the first source, and then search all the order numbers from the new file against the order numbers from the existing tracker. I have a for loop that is supposed to find the order numbers from the new file that are not already in the tracker and then insert a row with that order detail. I am receiving a Type mismatch error on the if statement that checks if the string exists in the array. Please take a look at this code:
Dim r As Integer
For r = 1 To 1000
Dim NewOrd As String
NewOrd = Range(Cells(r, 1), Cells(r, 1)).Value
Dim ExistArray As Variant
ExistArray = Range("a1", Range("a1").End(xlUp))
Sheets("Sheet2").Select
If IsEmpty(NewOrd) Then
Exit For
End If
If Not UBound(Filter(ExistArray, NewOrd)) >= 0 And NewOrd <> "" Then
Rows(r).Select
Selection.Copy
Sheets("Sheet3").Select
Rows(r).Select
Selection.Insert Shift:=xlDown
Application.CutCopyMode = False
End If
r = r + 1
Next r
I have tried a few different ways of setting the array, tried adding option explicit, and tried nesting for loops (not my brightest efficiency moment). Would greatly appreciate another set of eyes!
Thanks!
Assigning a Range object to an array always results in a two-dimensional array, which is causing the error.
Do this:
ExistArray = Application.Transpose(Range("a1", Range("a1").End(xlUp)))
I think that should resolve it for you.
Updates
You may need to:
Dim ExistArray() As Variant
Your range object is also problematic, being a single cell:
ExistArray = Application.Transpose(Array(Range("A1")))
Change the sheet names from "Sheet1" and "Sheet2" as necessary:
Sub tgr()
Dim wsNew As Worksheet
Dim wsTracker As Worksheet
Dim rIndex As Long
'This is the sheet that contains the new data that needs to be added
Set wsNew = Sheets("Sheet1")
'This sheet contains the old data
Set wsTracker = Sheets("Sheet2")
'Go through each row in the new data
For rIndex = 1 To wsNew.Cells(Rows.Count, "A").End(xlUp).Row
'Verify that the row isn't blank and that it doesn't already exist in wsTracker
If Len(wsNew.Cells(rIndex, "A").Value) > 0 And WorksheetFunction.CountIf(wsTracker.Columns("A"), wsNew.Cells(rIndex, "A").Value) = 0 Then
'This is a new item that needs to be added
'Copy the row to the next available row in wsTracker
wsNew.Rows(rIndex).Copy wsTracker.Cells(Rows.Count, "A").End(xlUp).Offset(1)
End If
Next rIndex
Set wsNew = Nothing
Set wsTracker = Nothing
End Sub
my data is as below .
Updated Question
Sub Solution()
Dim shData As Worksheet
Set shData = Sheets("Sheet1") 'or other reference to data sheet
Dim coll As Collection, r As Range, j As Long
Dim myArr As Variant
Dim shNew As Worksheet
shData.Activate
'get unique values based on Excel features
Range("a1").AutoFilter
Set coll = New Collection
On Error Resume Next
For Each r In Range("A1:A10")
coll.Add r.Value, r.Value
Next r
On Error GoTo 0
'Debug.Print coll.Count
For j = 1 To coll.Count
MsgBox coll(j)
myArr = coll(j)
Next j
Range("a1").AutoFilter
Dim i As Long
For i = 0 To UBound(myArr)
shData.Range("$A$1").AutoFilter Field:=1, Criteria1:=myArr(i), _
Operator:=xlAnd
On Error Resume Next
Sheets(myArr(i)).Range("A1").CurrentRegion.ClearContents
If Err.Number = 0 Then
Range("A1").CurrentRegion.Copy Sheets(myArr(i)).Range("A1")
Else
Set shNew = Sheets.Add(After:=Sheets(Sheets.Count))
shData.Range("A1").CurrentRegion.Copy shNew.Range("A1")
shNew.Name = myArr(i)
Err.Clear
End If
Next i
'removing filter in master sheet
shData.Range("a1").AutoFilter
End Sub
When I run above macro I don't know why it is giving Type Mismatch Error after MsgBox coll(j) , simply I want to store data in Array and I'm passing that data , Here I am using For Each r In Range("A1:A10") Where A10 length is static how can I find last written column?
When you add something to collection the key needs to be a string so use:
coll.Add r.Value, CStr(r.Value)
instead of:
coll.Add r.Value, r.Value
You are still assigning coll(j) to a Variant which is not an array.
You need to:
ReDim myArr(1 to coll.Count)
Before your for loop and then in the loop:
myArr(j) = coll(j)
Before attempting to respond to this question, I would like to write what I believe you are trying to accomplish; when you confirm this is what you are trying to do, I will try to help you get working code to achieve it. This would normally be done with comments, but the threads of comments so far are a bit disjointed, and the code is quite complex...
You have data in a sheet (called "sheet1" - it might be something else though)
The first column contains certain values that might be repeated
You don't know how many columns there might be... you would like to know that though
You attempt to find each unique value in column A (call it the "key value"), and display it (one at a time) in a message box. This looks more like a debug step than actual functionality for the final program.
You then turn on the autofilter on column A; selecting only rows that match a certain value
Using that same value as the name of a sheet, you see if such a sheet exists: if it does, you clear its contents; if it does not, then you create it at the end of the workbook (and give it the name of the key)
You select all rows with the same (key) value in column A on sheet1, and copy them to the sheet whose name is equal to the value in column A that you filtered on
You want to repeat step 5-8 for each of the unique (key) values in column A
When all is done, I believe you have (at least) one more sheet than you had key values in column A (you also have the initial data sheet); however you do not delete any "superfluous" sheets (with other names). Each sheet will have only rows of data corresponding to the current contents of sheet1 (any earlier data was deleted).
During the operation you turn autofiltering on and off; you want to end up with auto filter disabled.
Please confirm that this is indeed what you are attempting to do. If you could give an idea of the format of the values in column A, that would be helpful. I suspect that some things could be done rather more efficiently than you are currently doing them. Finally I do wonder whether the whole purpose of organizing your data in this way might be to organize the data in a specific way, and maybe do further calculations / graphs etc. There are all kinds of functions built in to excel (VBA) to make the job of data extraction easier - it's rare that this kind of data rearranging is necessary to get a particular job done. If you would care to comment on that...
The following code does all the above. Note the use for For Each, and functions / subroutines to take care of certain tasks (unique, createOrClear, and worksheetExists). This makes the top level code much easier to read and understand. Also note that the error trapping is confined to just a small section where we check if a worksheet exists - for me it ran without problems; if any errors occur, just let me know what was in the worksheet since that might affect what happens (for example, if a cell in column A contains a character not allowed in a sheet name, like /\! etc. Also note that your code was deleting "CurrentRegion". Depending on what you are trying to achieve, "UsedRange" might be better...
Option Explicit
Sub Solution()
Dim shData As Worksheet
Dim nameRange As Range
Dim r As Range, c As Range, A1c As Range, s As String
Dim uniqueNames As Variant, v As Variant
Set shData = Sheets("Sheet1") ' sheet with source data
Set A1c = shData.[A1] ' first cell of data range - referred to a lot...
Set nameRange = Range(A1c, A1c.End(xlDown)) ' find all the contiguous cells in the range
' find the unique values: using custom function
' omit second parameter to suppress dialog
uniqueNames = unique(nameRange, True)
Application.ScreenUpdating = False ' no need for flashing screen...
' check if sheet with each name exists, or create it:
createOrClear uniqueNames
' filter on each value in turn, and copy to corresponding sheet:
For Each v In uniqueNames
A1c.AutoFilter Field:=1, Criteria1:=v, _
Operator:=xlAnd
A1c.CurrentRegion.Copy Sheets(v).[A1]
Next v
' turn auto filter off
A1c.AutoFilter
' and screen updating on
Application.ScreenUpdating = True
End Sub
Function unique(r As Range, Optional show)
' return a variant array containing unique values in range
' optionally present dialog with values found
' inspired by http://stackoverflow.com/questions/3017852/vba-get-unique-values-from-array
Dim d As Object
Dim c As Range
Dim s As String
Dim v As Variant
If IsMissing(show) Then show = False
Set d = CreateObject("Scripting.Dictionary")
' dictionary object will create unique keys
' have to make it case-insensitive
' as sheet names and autofilter are case insensitive
For Each c In r
d(LCase("" & c.Value)) = c.Value
Next c
' the Keys() contain unique values:
unique = d.Keys()
' optionally, show results:
If show Then
' for debug, show the list of unique elements:
s = ""
For Each v In d.Keys
s = s & vbNewLine & v
Next v
MsgBox "unique elements: " & s
End If
End Function
Sub createOrClear(names)
Dim n As Variant
Dim s As String
Dim NewSheet As Worksheet
' loop through list: add new sheets, or delete content
For Each n In names
s = "" & n ' convert to string
If worksheetExists(s) Then
Sheets(s).[A1].CurrentRegion.Clear ' UsedRange might be better...?
Else
With ActiveWorkbook.Sheets
Set NewSheet = .Add(after:=Sheets(.Count))
NewSheet.Name = s
End With
End If
Next n
End Sub
Function worksheetExists(wsName)
' adapted from http://www.mrexcel.com/forum/excel-questions/3228-visual-basic-applications-check-if-worksheet-exists.html
worksheetExists = False
On Error Resume Next
worksheetExists = (Sheets(wsName).Name <> "")
On Error GoTo 0
End Function