I'm trying to loop through several worksheets that contain some source data that has to be copied to one main sheet, called "PriorityList" here.
First of all, the sub is not working and I think the error is somewhere in the "find"-method. Second, the sub takes quite long to run, and I think this is maybe because the "find"-method searches through the whole sheet instead of only the relevant range?
Thank you very much for your answers!
Patrick
Sub PriorityCheck()
'Sub module to actualise the PriorityList
Dim CurrWS As Long, StartWS As Long, EndWS As Long, ScheduleWS As Long
StartWS = Sheets("H_HS").Index
EndWS = Sheets("E_2").Index
Dim SourceCell As Range, Destcell As Range
For CurrWS = StartWS To EndWS
For Each SourceCell In Worksheets(CurrWS).Range("G4:G73")
On Error Resume Next
'Use of the find method
Set Destcell = Worksheets(CurrWS).Cells.Find(What:=SourceCell.Value, After:=Worksheets("PriorityList").Range("A1"), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
'Copying relevant data from source sheet to main sheet
If Destcell <> Nothing Then
Destcell.Offset(0, 2).Value = SourceCell.Offset(0, 5).Value + Destcell.Offset(0, 2).Value
If SourceCell.Offset(0, 3).Value = "x" Then Destcell.Offset(0, 3).Value = "x"
End If
End If
On Error GoTo 0
Next SourceCell
Next CurrWS
End Sub
here short sample how to use 'Find' method to find the first occurrence of the source.Value in the priorityList.
Source cell is one of the cells from the range "G4:G73" and priorityList is used range on "PriorityList" sheet. Hope this helps.
Public Sub PriorityCheck()
Dim source As Range
Dim priorityList As Range
Dim result As Range
Set priorityList = Worksheets("PriorityList").UsedRange
Dim i As Long
For i = Worksheets("H_HS").Index To Worksheets("E_2").Index
For Each source In Worksheets(i).Range("G4:G73")
Set result = priorityList.Find(What:=source.Value)
If (Not result Is Nothing) Then
' do stuff with result here ...
Debug.Print result.Worksheet.Name & ", " & result.Address
End If
Next source
Next i
End Sub
Here is an approach using arrays. You save each range into an array, then iterate through array to satisfy your if-else condition. BTW IF you want to find the exact line with code error, then you must comment On Error Resume Next line.. :) Further, you can simply store the values into a new array, dump everything else into the main sheet later after iterating through all the sheets instead of going back and forth to sheets, code, sheets..code..
Dim sourceArray as Variant, priorityArray as Variant
'-- specify the correct priority List range here
'-- if multi-column then use following method
priorityArray = Worksheets(CurrWS).Range("A1:B10").Value
'-- if single column use this method
' priorityArray = WorkSheetFunction.Transpose(Worksheets(CurrWS).Range("A1:A10").Value)
For CurrWS = StartWS To EndWS
On Error Resume Next
sourceArray = Worksheets(CurrWS).Range("G4:J73").Value
For i = Lbound(sourceArray,1) to UBound(sourceArray,1)
For j = Lbound(priorityArray,1) to UBound(priorityArray,1)
If Not IsEmpty(vArr(i,1)) Then '-- use first column
'-- do your validations here..
'-- offset(0,3) refers to J column from G column, that means
'---- sourceArray(i,3)...
'-- you can either choose to update priority List sheet here or
'---- you may copy data into a new array which is same size as priorityArray
'------ as you deem..
End If
Next j
Next i
Next CurrWS
PS: Not front of a MS Excel installed machine to try this out. So treat above as a code un-tested. For the same reason I couldn't run your find method. But it seems odd. Don't forget when using match or find it's important to do proper error handling. Try checking out [find based solutions provided here.
VBA in find function runtime error 91
Excel 2007 VBA find function. Trying to find data between two sheets and put it in a third sheet
I have edited the initial code to include the main logic using two array. Since you need to refer to values in J column of source sheets, you will need to adjust source array into a two-dimensional array. So you can do the validations using first column and then retrieve data as you desire.
For everyone maybe interested, this is the code version that I finally used (pretty similar to the version suggested by Daniel Dusek):
Sub PriorityCheck()
Dim Source As Range
Dim PriorityList As Range
Dim Dest As Range
Set PriorityList = Worksheets("PriorityList").UsedRange
Dim i As Long
For i = Worksheets("H_HS").Index To Worksheets("S_14").Index
For Each Source In Worksheets(i).Range("G4:G73")
If Source <> "" Then
Set Dest = PriorityList.Find(What:=Source.Value)
If Not Dest Is Nothing Then
If Dest <> "" Then
Dest.Offset(0, 2).ClearContents
Dest.Offset(0, 2).Value = Source.Offset(0, 5).Value + Dest.Offset(0, 2).Value
End If
If Source.Offset(0, 3).Value = "x" Then Dest.Offset(0, 3).Value = "x"
Debug.Print Dest.Worksheet.Name & ", " & Dest.Address
End If
End If
Next Source
Next i
MsgBox "Update Priority List completed!"
End Sub
Related
I have a problem...
I have two datasets in the same workbook on different sheets.
The first column in both datasets are identifiers. In Sheet1 I have my dataset, and want to fill it with data from Sheet2 (which is also containing data (rows+Columns) that I do not want to use.
I have a VBA that is working, BUT, it stops before it is done.
E.g. I have 1598 Rows in Sheet2, but it stops working already after 567 rows..
Sub Test()
Dim c As Range
Dim j As Integer
Dim Source As Worksheet
Dim Target As Worksheet
Set Source = ActiveWorkbook.Worksheets("Sheet2")
Set Target = ActiveWorkbook.Worksheets("Sheet1")
j = 2
For Each c In Source.Range("A2", Source.Range("A" & Source.Cells(Source.Rows.Count, "A").End(xlUp).Row))
If c = Target.Cells(j, 1).Value Then
Source.Range("D" & c.Row & ":AS" & c.Row).Copy Target.Cells(j, 26)
j = j + 1
End If
Next c
MsgBox "Done"
End Sub
Can someone help me and see if there is something obviously wrong with the code? I have tried it on smaller datasets, and then it works perfect.
If more information needed or you have some other tips, please ask/tell :D
Thanks!
VBA Solution
Try the following, it usese the WorksheetFunction.Match method to properly match the values of column A no matter which order they are.
It loops through all rows in Target, and tries to find a matching row in Source. If a match was found it copies it into the Target.
Option Explicit
Public Sub Test()
Dim Source As Worksheet
Set Source = ThisWorkbook.Worksheets("Sheet2")
Dim Target As Worksheet
Set Target = ThisWorkbook.Worksheets("Sheet1")
Dim LastRowTarget As Long
LastRowTarget = Target.Cells(Target.Rows.Count, "A").End(xlUp).Row
Dim tRow As Long
For tRow = 2 To LastRowTarget
Dim sRowMatch As Double
sRowMatch = 0 'reset match row
On Error Resume Next 'ignore if next line throws error
sRowMatch = Application.WorksheetFunction.Match(Target.Cells(tRow, 1).Value, Source.Columns("A"), 0)
On Error GoTo 0 're-enable error reporting
If sRowMatch <> 0 Then 'if matching does not find anything it will be 0 so <>0 means something was found to copy
Source.Range("D" & sRowMatch & ":AS" & sRowMatch).Copy Target.Cells(tRow, 26)
End If
Next tRow
MsgBox "Done"
End Sub
Formula Solution
Note that there is no need for VBA and this could actually also solved with formulas only. Either the VLOOKUP formula or a combination of INDEX and MATCH formula.
So in Sheet1 cell Z2 write =INDEX(Sheet2!D:D,MATCH($A2,Sheet2!$A:$A, 0)) and pull it down and right.
I have data regarding benefits. At the bottom of these worksheets are adjustments. These should not be used in my Macros and formulas.
Instead of manually deleting, I'd like VBA to find "Adjustments" and delete that row and all rows below this. I have 3 worksheets I needs to this to repeat on.
I've googled and tried various codes but cannot seem to get it to read properly. Can anyone help?
First step is to find the first row of your Adjustments. You'll use .Find method to get that. Below is a sample line of code. You can google for more details and examples.
Once you have that, you'll find the last row, and then delete rows from start of adjustment rows to last row. I've included functions below that should help.
Set foundCell = rng.Cells.Find(varSearchValue, celStartCell, enuXlFindLookIn, enuXlLookAt)
Public Function LastUsedRow(wks As Worksheet) As Long
Dim rng As Range: Set rng = wks.UsedRange ' Excel will recalc used range
LastUsedRow = rng.Row + rng.Rows.Count - 1
End Function
Public Sub DeleteRows(wks As Worksheet, lngRowStart As Long, Optional ByVal lngRowEnd As Long = 0)
If lngRowEnd = 0 Then lngRowEnd = lngRowStart
wks.Rows(lngRowStart & ":" & lngRowEnd).Delete
End Sub
I've inferred that Adjustments is some sort of sub-level header row label. I'll assume that it is always in column A.
sub ScrubAdjustments()
dim w as long, wss as variant, m as variant
wss = array("sheet1", "sheet2", "sheet3")
for w = lbound(wss) to ubound(wss)
with worksheets(wss(w))
m = application.match("adjustments", .range("a:a"), 0)
if not iserror(m) then
.range(.cells(m, "A"), .cells(.rows.count, "A")).entirerow.delete
end with
end with
next w
end sub
I am currently using the below code to add a line break to cell data in column C and copy it to column K. I need to apply line breaks to a range of data. I have 1000 rows of data in column C.
Any help will be much appreciated.
Sub Macro
Dim Stem As Variant
Stem = ThisWorkbook.Worksheets ("Sheet1").Range("C2")
Range ("K2").Select
Range("K2").FormulaR1C1 = Stem & Chr(10) & ""
End Sub
Thanks
Try this:
Sub copyAndNewLine()
'copy column C to K
Columns("C").Copy Destination:=Columns("K")
'loop through all cells in K and add new line
For i = 2 To Cells(Rows.Count, "K").End(xlUp).Row
Cells(i, "K").Value = Cells(i, "K").Value & vbCrLf
Next i
End Sub
A couple of things:
Better to early bind your variables than late (better memory
management, take advantage of intellisense, etc.)
Usually best
practice to avoid using "select" if possible.
Your Stem variable is an object (Range Object) and thus needs to be "Set"
Try this:
Sub Macro
Dim WS As Worksheet
Dim Stem As Range
Dim R2 As Range
Dim Rng as Range
Set WS = ActiveWorkbook.Sheets("Sheet1")
Set Stem = WS.Range("C2", Cells(WS.Range("C2").End(xlDown).Row, WS.Range("C2").Column))
Set R2 = WS.Range("K2", Cells(Stem.End(xlDown).Row, WS.Range("K2").Column))
R2.Value = Stem.Value
'New Code
For Each Rng In R2
Rng.Value = Rng.Value & Chr(10) & ""
Next Rng
'Old Code: R2.End(xlDown) = R2.End(xlDown) & Chr(10) & ""
End Sub
What this does is first sets the worksheet you're using. Then, you set your working range (Stem) using the Range(cell1, cell2) format. Cell1 I defined as "C2". The next expression there is using the Cells() function. It is the VBA equivalent of being in "C2" and hitting Ctl+Down, then seeing what row you're in.
Then, I set your destination range, R2, in a similar manner, but I used the Stem range to determine how large it should be.
Finally, to get an accurate copy your destination range must be the same size as your from range. The .value to .value expression pastes the data. Then, your extra characters are added on to your new data field.
Something to keep in mind with .End(xlDown)... if you have blank rows in the middle of your data it will stop there, not go all the way down to the end. Hope this helps!
EDIT:
The For Each loop will go through every range (i.e. cell) in your destination range, R2, and add your new characters. Hope that fits better for you.
Thanks to all for your answers. I atlas was able to write my first script and got the code to add line break inside the cell.
Sub AddLineBreak()
Dim Stem As Variant
Stem = ThisWorkbook.Worksheets("Sheet1").Range("C2")
Dim i As Integer
i = 2
'this will terminate the loop if an empty cell occurs in the middle of data
Do While Cells(i, "C").Value <> ""
Cells(i, "K").Value = Cells(i, "C").Value & vbCrLf
i = i + 1
Loop
End Sub
This question already has answers here:
Aggregate, Collate and Transpose rows into columns
(3 answers)
Closed 6 years ago.
I'm pretty new to stack overflow but I've been on here as a lurker before.
So I'm having trouble reorganizing this excel output. The original output is below. I've modified the output to preserve the confidentiality of the dataset and also in the interest of time as the dataset has over 10k cells, but the ideas should be clear.
Before
As you can see, there's a lot of duplicates and useless stuff and in general annoying bits. Basically I need to reorganize the data into column headers and repopulate the spreadsheet so that the data stays with the proper code number. The current column headers of supercatagory and subcategory are worthless. I've attached what I think would be the ideal here. After
I've tried using pivot tables and that kind of serves as a half measure but that would still require me to go through the output and copy and paste by hand for over 2 hours. I've also tried using transpose in excel and while that is good for the first part of the problem, making new column headers, but it doesn't solve the problem of repopulating the spreadsheet and keeping everything straight.
Thank you so much.
Without knowing more, the below code works for me in testing with the data provided in your images. The big question of course is where the column headers in the After data came from. It appeared to come from column B of the Before data. I assumed these would be duplicated for each unique value from column A. As such, in the below code, only the first set of values is used to set the headers of the newly created sheet.
Option Explicit
Sub TransposeWithUniques()
Dim SourceSheet As Worksheet
Dim TargetSheet As Worksheet
Dim Uniques As Collection
Dim Unique As Variant
Dim UniqueData() As Variant
Dim FormulaColumn As Range
Dim CriteriaColumn As Range
Dim DataRange As Range
Dim FoundRange As Range
Dim ValueIndex As Long
Dim LastRow As Long
Dim LastColumn As Long
Dim NewRow As Long
Dim ErrorFound As Boolean
Set SourceSheet = ActiveSheet '!!! This will need to be the currently active sheet housing your data
' If sheet is protected, exit
If SourceSheet.ProtectContents Then
MsgBox "Please unprotect the worksheet first.", vbExclamation, "Transpose with Uniques"
Exit Sub
End If
' Get last row/column
LastRow = SourceSheet.Cells(SourceSheet.Rows.Count, 1).End(xlUp).Row
LastColumn = SourceSheet.Cells(1, SourceSheet.Columns.Count).End(xlToLeft).Column
Set DataRange = SourceSheet.Range("A1", SourceSheet.Cells(LastRow, LastColumn))
NewRow = 1
' Get unique UniqueData from column A
UniqueData = SourceSheet.Range("A2:A" & LastRow).Value2
Set Uniques = New Collection
For ValueIndex = LBound(UniqueData, 1) To UBound(UniqueData, 1)
If InCollection(Uniques, CStr(UniqueData(ValueIndex, 1))) = False Then
Uniques.Add UniqueData(ValueIndex, 1), CStr(UniqueData(ValueIndex, 1))
End If
Next ValueIndex
' Set application properties for better code running experience
Application.DisplayAlerts = False
Application.EnableEvents = False
Application.ScreenUpdating = False
' Add helper columns
On Error GoTo TransposeWithUniques_Error
SourceSheet.Cells(1, LastColumn).Offset(0, 1).Resize(LastRow, 2).Insert
Set CriteriaColumn = SourceSheet.Cells(1, LastColumn).Offset(0, 1).Resize(LastRow, 1)
Set FormulaColumn = SourceSheet.Cells(1, LastColumn).Offset(0, 2).Resize(LastRow, 1)
FormulaColumn(1, 1).Value = "FORMULA"
CriteriaColumn(1, 1).Value = "CRITERIA"
FormulaColumn(2, 1).Resize(LastRow - 1, 1).Formula = "=ROW(A1)"
FormulaColumn(2, 1).Resize(LastRow - 1, 1).Value = FormulaColumn(2, 1).Resize(LastRow - 1, 1).Value
' Loop through all uniques, get data and move it
For Each Unique In Uniques
CriteriaColumn(2, 1).Resize(LastRow - 1, 1).Formula = "=1/(A2=" & Chr(34) & Unique & Chr(34) & ")"
CriteriaColumn(2, 1).Resize(LastRow - 1, 1).Value = CriteriaColumn(2, 1).Resize(LastRow - 1, 1).Value
DataRange.Resize(, DataRange.Columns.Count + 2).Sort Key1:=CriteriaColumn(1, 1), Order1:=xlAscending, Key2:=SourceSheet.Range("B1"), Order2:=xlAscending, Header:=xlYes
On Error Resume Next
Set FoundRange = CriteriaColumn.SpecialCells(xlCellTypeConstants, xlNumbers)
On Error GoTo 0
If Not FoundRange Is Nothing Then
If TargetSheet Is Nothing Then
Set TargetSheet = ActiveWorkbook.Worksheets.Add(After:=SourceSheet)
TargetSheet.Range("A1").Value = SourceSheet.Range("A1").Value
TargetSheet.Range("B1").Resize(1, FoundRange.Cells.Count).Value = Application.Transpose(Intersect(SourceSheet.Range("B:B"), FoundRange.EntireRow).Value)
End If
NewRow = NewRow + 1
TargetSheet.Cells(NewRow, 1).Value = Unique
TargetSheet.Cells(NewRow, 2).Resize(1, FoundRange.Cells.Count).Value = Application.Transpose(Intersect(SourceSheet.Range("C:C"), FoundRange.EntireRow).Value)
Set FoundRange = Nothing
End If
Next Unique
' Reset data to original state
DataRange.Resize(, DataRange.Columns.Count + 2).Sort Key1:=FormulaColumn(1, 1), Order1:=xlAscending, Header:=xlYes
FormulaColumn.Delete xlToLeft
CriteriaColumn.Delete xlToLeft
TransposeWithUniques_Exit:
Application.DisplayAlerts = True
Application.EnableEvents = True
Application.ScreenUpdating = True
If Not ErrorFound Then
MsgBox "Process completed successfully.", vbInformation, "Transpose with Uniques"
End If
Exit Sub
TransposeWithUniques_Error:
ErrorFound = True
MsgBox "Something went wrong.", vbExclamation, "Transpose with Uniques"
GoTo TransposeWithUniques_Exit
End Sub
Public Function InCollection(CheckCollection As Collection, CheckKey As String) As Boolean
'
' Returns True if the specified key is found in the specified collection.
'
' Syntax: InCollection(CheckCollection,CheckKey)
'
' Parameters: CheckCollection. Collection. Required. The collection to search in.
' CheckKey. String. Required. The string key to search in collection for.
'
On Error Resume Next
InCollection = CBool(Not IsEmpty(CheckCollection(CheckKey)))
On Error GoTo 0
End Function
To use the above code, in your file you want to run this on, press ALT+F11 to open the Visual Basic Editor (VBE). Press CTRL+R to show the Project Explorer (PE), generally this shows by default. Find your project in the PE and right-click it, select Insert, Module. Double click the newly inserted module (should be named Module1). Copy/paste the above code into this module. Click anywhere inside the top routine (for example, click on the text near the top "TransposeWithUniques" so your cursor is on that line, or just below it). Press F5 to run the routine.
CAUTION: Make sure you save a backup copy of your file prior to running this. It resets the data to its original state, but this is always good practice. Check the newly created sheet to ensure it's what you're looking for. If this isn't what you're looking for, please be as specific as possible in explaining the input versus output.
Regards,
Zack Barresse
I have a quandary, and I don't know if it will work better using excel VBA or not. Thinking about it I believe VBA will work best, but I don't know how to make it work.
I have two pages in a workbook, one is the form, the other is the database, I want the pulldown menu from the form to populate the rest of the form. It does... what I want then is to be able to change the value of the form press submit, and the new data will overwrite the old data.
Is this possible?
Here is the link to the sheet I'm talking about.
http://dl.dropbox.com/u/3327208/Excel/Change.xlsx
Here is the script I am working with now...it takes the sheet, copies everything to a row takes that row, moves it to the NCMR Data tab and then clears the data on the new row from the original sheet.
This code technically could work, but what I need to do is make it use the same concept, but instead of creating a new row at the end of the sheet find the original line and replace the data from B to U in whatever row it was originally in.
I know it's possible, I just don't know how.
'Copy Ranges Variable
Dim c As Variant
'Paste Ranges Variable
Dim p As Range
'Setting Sheet
Set wsInt = Sheets("Form")
Set wsNDA = Sheets("Data")
Set p = wsInt.Range("A14")
With wsInt
c = Array(.Range("B11"))
End With
For i = LBound(c) To UBound(c)
p(i + 1).Value = c(i).Value
Next
With wsNDA
Dim Lastrow As Long
Lastrow = .Range("B" & Rows.Count).End(xlUp).Row + 1
wsInt.Rows("14").Copy
With .Rows(Lastrow)
.PasteSpecial Paste:=xlPasteFormats
.PasteSpecial Paste:=xlPasteValues
.Interior.Pattern = xlNone
End With
With .Range("A" & Lastrow)
If Lastrow = 3 Then
.Value = 1
Else
.Value = Val(wsNDA.Range("A" & Lastrow - 1).Value) + 1
End If
.NumberFormat = "0#######"
End With
End With
End Sub
I found this code:
Sub CopyTest()
Dim selrow As Range, rngToCopy As Range
With Worksheets("PD DB")
Set selrow = .Range("B:B").Find(.Range("BA1").Value)
'find the cell containing the value
Set rngToCopy = Union(selrow.Offset(0, 9), selrow.Offset(0, 12))
'use offset to define the ranges to be copied
rngToCopy.Copy Destination:=Worksheets("Edit Sheet").Range("B50")
'copy and paste (without Select)
End With
End Sub
As far as I can tell this will do what I want mostly, but I can't seem to figure out where to break it up to add it where I need to to make it work the way I want it to.
What I can tell is this, it will copy and paste, but I want to make sure it will paste the data into row it finds, and not overwrite the number of said row.
Can someone help make that possible with the two scripts I have here?
Not tested, but should get you started. I added a 3rd sheet (shtMap) to hold the mmapping between the cell addresses on your form and the column numbers on the "Data" sheet. Useful to name your sheets directly in the VB editor: select the sheet and set the name in the property grid.
*EDIT:*If you want to trigger the transfer on selecting a record id from a list in Range AG3 then place this code in the code module for that worksheet:
Private Sub Worksheet_Change(ByVal Target As Range)
Static bProcessing As Boolean
Dim rng As Range
If bProcessing Then Exit Sub
Set rng = Target.Cells(1)
If Not Application.Intersect(rng, Me.Range("AG3")) Is Nothing Then
bProcessing = True
'this is where you call your macro to transfer the record
bProcessing = False
End If
End Sub
You could use something like this for the transfer:
Public Enum XferDirection
ToForm = 1
ToDataSheet = 2
End Enum
Sub FetchRecord()
TransferData XferDirection.ToForm
End Sub
Sub SaveRecord()
TransferData XferDirection.ToDataSheet
End Sub
Sub TransferData(Direction As XferDirection)
Dim rngMap As Range, rw As Range, f As Range, dataCell As Range
Dim formCell As Range, dataCol As Long, dataRow As Long
Dim sId As String
sId = shtForm.Range("AG3").Value
Set f = shtData.Columns(1).Find(sId, LookIn:=xlValues, lookat:=xlWhole)
If Not f Is Nothing Then
dataRow = f.Row
Else
'what do you want to do here?
' record doesn't exist on data sheet
MsgBox "Record '" & sId & "' not found on '" & shtForm.Name & "' !"
Exit Sub
End If
Set rngMap = shtMap.Range("A2:B10")
For Each rw In rngMap.Rows
'the cell on the edit form
Set formCell = shtForm.Range(rw.Cells(1).Value)
'column # on datasheet
Set dataCell = shtData.Cells(dataRow, rw.Cells(2).Value)
If Direction = XferDirection.ToDataSheet Then
dataCell.Value = formCell.Value
Else
formCell.Value = dataCell.Value
End If
Next rw
End Sub
Matt, there are two approaches I would take. The first is use find(), which returns a range object, then append ".row" so that you'll be able to modify the row on Sheet2 (wsNDA, I think). You may want to test that find() doesn't return Nothing.
Dim foundRow as Long
Dim foundRng as Range
set foundRng = wsNDA.find(wsInt.Range("B11").Value, ...)
If Not foundRng is Nothing Then
foundRow = foundRng.row
End If
'method without check: foundRow = wsNDA.find(wsInt.Range("B11").Value, ...).Row
The other is to use a Dictionary object. I'm not sure what you'd want for the key, but the item could be the row on the data sheet. When you make the change to what's on the form, check against the key and grab its item (the corresponding row) to determine where you need to replace the values.