VBA Dual Loops Contiguous and Non Contiguous - vba

Rewriting this for clarity, I think the original was too confusing and too long.
I'm trying to grab contiguous time series data off a sheet named "Files", process it through a series of calculations on a sheet named "Data", copy those results and paste them as static data in a non-contiguous range on "Data" and then repeating that process until all data has been handled.
The issue I've been struggling with for the last five days is the placement of the second function. If I nest it inside of "i" it writes each single result 25 times to the non-contiguous range. If I place it outside of "i" it finishes "i" then writes only the last result to each of the 25 locations.
I'm pretty sure at this point I'm using the wrong structure, I'm guessing a "For" loop isn't the way to go for the second function, but I'm so new to this I can't really get my head wrapped around how to implement it otherwise. I've also tried to structure "n" as an array but was never able to get that debugged and wasn't sure it was the right approach either.
Sub getData()
' Process individual time series
Dim Data As Worksheet, Files As Worksheet
Dim fLastRow As Long, dLastRow As Long
Dim i As Long, n As Long
Application.ScreenUpdating = False
Set Data = ActiveWorkbook.Sheets("Data")
Set Files = ActiveWorkbook.Sheets("Files")
fLastRow = Files.Range("A" & Files.Rows.Count).End(xlUp).Row
dLastRow = Data.Range("F" & Data.Rows.Count).End(xlUp).Row
' Process three column data
Files.Range("A1:C" & fLastRow).Copy
Data.Range("A3").PasteSpecial xlPasteValuesAndNumberFormats
Data.Range("F202:P" & dLastRow).Copy
Data.Range("T202").PasteSpecial xlPasteValuesAndNumberFormats
' Process single column data
For i = 4 To 26
Files.Activate
Range(Cells(1, i), Cells(3509, i)).Copy
Data.Range("C3").PasteSpecial xlPasteValuesAndNumberFormats
Data.Range("F202:P" & dLastRow).Copy
For n = 32 To 296 Step 12 ' <~~ this is the problem. inside or outside "i" doesn't work.
Data.Activate
Range(Cells(202, n), Cells(3511, n)).PasteSpecial xlPasteValuesAndNumberFormats
Next n ' <~~ i know this is the problem just not sure what the answer is.
Next i
' Post processing
Data.Cells.Columns.AutoFit
Application.CutCopyMode = False
Application.ScreenUpdating = True
Data.Activate
Data.Range("A1").Select
End Sub

You could use one variable; n. Continue stepping by 12, but where you want to place the i variable, use n and divide by 12 and add 4

Until I get better at the code I'll have to settle for this... I stepped the data on the "files" sheet to coincide with the step on the data sheet. To keep the data readable on the "files" tab I just hid the added columns. This allowed me to use "i" for both copy paste operations. I'm sure there is a better way but this meets my needs and is much faster than the original code I started with.
Sub getData()
' Process individual time series
Dim Data As Worksheet, Files As Worksheet
Dim fLastRow As Long, dLastRow As Long, i As Long
Set Data = ActiveWorkbook.Sheets("Data")
Set Files = ActiveWorkbook.Sheets("Files")
fLastRow = Files.Range("A" & Files.Rows.Count).End(xlUp).Row
dLastRow = Data.Range("F" & Data.Rows.Count).End(xlUp).Row
Application.ScreenUpdating = False
' Process data
Files.Range("A1:C" & fLastRow).Copy
Data.Range("A3").PasteSpecial xlPasteValuesAndNumberFormats
Data.Range("F202:P" & dLastRow).Copy
Data.Range("T202").PasteSpecial xlPasteValuesAndNumberFormats
Files.Range("D1:JH" & fLastRow).Copy
Data.Range("AF3520").PasteSpecial xlPasteValuesAndNumberFormats
For i = 32 To 296 Step 12
Data.Range(Cells(3520, i), Cells(7103, i)).Copy
Data.Range("C3").PasteSpecial xlPasteValuesAndNumberFormats
Data.Range("F202:P" & dLastRow).Copy
Data.Range(Cells(202, i), Cells(3511, i)).PasteSpecial xlPasteValuesAndNumberFormats
Next i
' Post processing
Data.Range(Cells(3520, 32), Cells(7103, 296)).ClearContents
Data.Cells.Columns.AutoFit
Application.CutCopyMode = False
Application.ScreenUpdating = True
Range("A1").Select
End Sub

Related

Column and Row Indexing - VBA

I am trying to write something that does the following in excel macro (VBA):
For 'column X' of 'spreadsheet'
Copy range(row(1):row(5)
Paste to 'other spreadsheet' in range (row(1):row(5)) and column(Y)
And I want that to loop through the first spreadsheet for every column in the spreadsheet. This is what I have for 1 column:
Sheets("Info").Range("B3:B6").Value = Worksheets("Temp").Range("HK5:HK8").Value
Sheets("Info").Range("C3:C6").Value = Worksheets("Temp").Range("HK10:HK13").Value
This is what I want to do, however for every column within the first spreadsheet (there is 300 columns, manually would be tedious).
EDIT: This is another way i have found that may help explain the comments left below:
For i = 2 To 3
Worksheets("Info").Range(Cells(3, i), Cells(6, i)).Value = Worksheets("Temp").Range(Cells(5, i), Cells(8, i)).Value
Next i
I hoping this loops over the columns (2 - 290) currently its only from column 2 to 3 for testing purposes. I want the cells from TEMP worksheet from every column ('i') from row 5-8 and I want to put that into the INFO worksheet in column ('i') rows 3-6. Hope this helps!
Your description isn't consistent and I am not sure what is wrong with your final code. I have left a commented out debug statement so you can see what ranges are being worked with.
But
Use Option Explicit at the top of your code
Make sure to declare i as Long
Switch of ScreenUpdating to speed up performance
Use variables to hold the worksheets
Fully qualify Cells references with their worksheet
Public Sub test()
Dim i As Long
Dim wsInfo As Worksheet
Dim wsTemp As Worksheet
Set wsInfo = ThisWorkbook.Worksheets("Info")
Set wsTemp = ThisWorkbook.Worksheets("Temp")
Application.ScreenUpdating = False
For i = 2 To 290
' Debug.Print wsInfo.Name & "!" & wsInfo.Range(wsInfo.Cells(3, i), wsInfo.Cells(6, i)).Address & " = " & wsTemp.Name & "!" & wsTemp.Range(wsTemp.Cells(5, i), wsTemp.Cells(8, i)).Address
wsInfo.Range(wsInfo.Cells(3, i), wsInfo.Cells(6, i)).Value = wsTemp.Range(wsTemp.Cells(5, i), wsTemp.Cells(8, i)).Value
Next i
Application.ScreenUpdating = True
End Sub

Creating a macro that properly filters data and puts it on another sheet

I have a large dataset that is ordered in a weird way, as in the picture:
This is how my data looks currently
This is what i want it to be like
So mainly I want to do 2 things, first i want to cut the two other columns that display data, and paste them underneath the first column, but only for the first weeks period, and then sort the data, macro recording doesn't work very well since weeks are really months, therefore the amount of days changes per month, hence the height of each column.
My idea is to use a while loop to scroll through the first column (the first one displaying "Day", for each non-number entry (say the first no-greater than zero input), and then cut the whole three block array and paste it somewhere else, say a new sheet called Week "n", given it's the n'th week.
Then properly order this array, copying the two right blocks underneath the first one, and sort them by day and hour.
This I want to do for each data period of a week, but I'm not that well versed on vba's syntax to achieve this, mostly i do not know how to order the array the way im looking to once they are copied to new sheets, neither do i know how to do it if i were not to add new sheets and instead reformat it in place.
Any help is welcome.
Considering your data is set up as per the following image...
Place the following code on a Standard Module like Module1...
Sub TransformWeekData()
Dim sws As Worksheet, dws As Worksheet
Dim lr As Long, dlr As Long, i As Long
Dim Rng As Range
Application.ScreenUpdating = False
Set sws = Sheets("Sheet1") 'Source data sheet
lr = sws.Cells(Rows.Count, 1).End(xlUp).Row
On Error Resume Next
Set dws = Sheets("Combined Data") 'Output Sheet
dws.Cells.Clear
On Error GoTo 0
If dws Is Nothing Then
Set dws = Sheets.Add(after:=sws)
dws.Name = "Combined Data"
End If
On Error Resume Next
For Each Rng In sws.Range("A2:A" & lr).SpecialCells(xlCellTypeConstants, 1).Areas
If dws.Range("A1").Value = "" Then
dlr = 1
Else
dlr = dws.Range("A" & Rows.Count).End(3)(2).Row
End If
dws.Range("A" & dlr).Value = Rng.Cells(1).Offset(-2, 0).Value
dws.Range("A" & dlr + 1 & ":C" & dlr + 1).Value = Array("Day", "Amount", "Hour")
For i = 1 To 9 Step 3
dlr = dws.Range("A" & Rows.Count).End(3)(2).Row
Rng.Offset(, i - 1).Resize(Rng.Cells.Count, 3).Copy dws.Range("A" & dlr)
Next i
Next Rng
dlr = dws.Range("A" & Rows.Count).End(xlUp).Row
For Each Rng In dws.Range("A2:A" & dlr).SpecialCells(xlCellTypeConstants, 1).Areas
Rng.Resize(Rng.Cells.Count, 3).Sort key1:=Rng.Cells(1), order1:=xlAscending, key2:=Rng.Cells(1, 3), order2:=xlAscending, Header:=xlNo
Next Rng
Application.ScreenUpdating = True
End Sub
The code above will insert a sheet called Combined Data if doesn't exist in the workbook with the data in the desired format as shown in the image below...
You may change the output sheet's name as per your requirement.

Applying VBA RIGHT to an entire column - Infinite Loop Issue

I have data that I am working to Parse Out that I have imported from approval emails sent in Outlook. At this point I am just importing the CreationTime and the SubjectLine.
For the subject line I am able to use the Split function to separate out most of the data. I then am left with Job Codes in Column B and Position numbers in Column C which includes the text: "Job Codes: XXXX" and the four digit job code number and "PN XXXX" and either a four digit or 6 digit position number. I am trying to use the Right functionality to loop through the entire column and reformat the column just to show only the four digit job code number for Column B and either just the 4 digit or 6 digit position number (the actual numbers) for Column C
For Job Code Column B:
Currently my code works for Shortening the Job Codes but it involves adding a column, putting the RIGHT formula in that column for the shortened Job Code, then copying and pasting the formula as values back into the column and then deleting the original column.
The problem- Works but perhaps not the most efficient with a larger data set (currently 200 rows but will have 2000 or more)
Code:
Sub ShortenJobCodes()
Application.ScreenUpdating = False
Const R4Col = "=RIGHT(RC3,4)"
Dim oRng As Range
Dim LastRow As Long
Range("B1").EntireColumn.Insert
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
Set oRng = Range("B:B")
Range(oRng, Cells(LastRow, "B")).FormulaR1C1 = R4Col
Set oRng = Nothing
Columns("B").Select
Selection.Copy
Selection.PasteSpecial Paste:=xlPasteValues
Range("C1").EntireColumn.Delete
Application.ScreenUpdating = True
End Sub
For Position Numbers Column C:
Currently I have mirrored the above code but added in an if statement using LEN to count if the characters are less than 8, if so then insert one RIGHT function if not insert the other RIGHT function. This also involves adding an additional column putting the RIGHT formula in that column for the shortened Position Number(Eliminating all but just the number), then copying and pasting the formula as values back into the column and then deleting the original column.
Problem - This works but seems to take forever to process and in fact looks like it is in an infinite loop. When I Esc out of it, it does add the column and then input the proper RIGHT formula (leaving just the numeric values) but the sub never seems to end, nor does it copy and paste the formulas as values or delete the original column. As noted above I realize this is likely a more efficient way to do this but I have tried a bunch of options without any luck.
I am realizing part of the loop might be due to the range itself being an entire column but I cannot find a way to stop that with the last row (even though I have a count in there).
Code:
Sub ShortenPositionNumbers()
Application.ScreenUpdating = False
Const R4Col = "=RIGHT(RC4,4)"
Const R6Col = "=RIGHT(RC4,6)"
Dim oRng As Range
Dim rVal As String
Dim y As Integer
Dim selCol As Range
Dim LastRow As Long
Range("C1").EntireColumn.Insert
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
Set selCol = Range("D:D")
For Each oRng In selCol
oRng.Select
rVal = oRng.Value
If Len(oRng.Value) > 8 Then
oRng.Offset(0, -1).FormulaR1C1 = R6Col
Else
oRng.Offset(0, -1).FormulaR1C1 = R4Col
End If
Next
Set oRng = Nothing
Columns("C").Select
Selection.Copy
Selection.PasteSpecial Paste:=xlPasteValues
Range("D1").EntireColumn.Delete
Application.ScreenUpdating = True
End Sub
Major Question: Is there a way to use RIGHT/TRIM/LEN/LEFT functions to do this within a cell without having to add columns/delete columns and insert functions?
There are a few things you can do here to speed up your code. I'm only going to reference the second code block as you can apply similar logic to the first.
The first issue is that you create a LastRow variable but never reference it again. It looks like you meant to use this in the selCol range. You should change that line to Set selCol = Range("C1:C" & lastRow). This way, when you loop through the rows you only loop through the used rows.
Next, in the For-Each loop you Select every cell you loop through. There really isn't any reason to do this and takes substantially longer. You then create the variable rVal but never use it again. A better way to set up the loop is as follows.
For Each oRng in selCol
rVal = oRng.Value
If Len(rVal) > 8 Then
oRng.Value = Right(rVal, 6)
Else
oRng.Value = Right(rVal, 4)
End If
Next
This is much cleaner and no longer requires creating columns or copying and pasting.
Try this, it uses Evaluate and no loops or added columns.
Sub ShortenPositionNumbers()
Application.ScreenUpdating = False
Dim selCol As Range
Dim LastRow As Long
With ActiveSheet
LastRow = .Cells(Rows.Count, "A").End(xlUp).Row
Set selCol = .Range(.Cells(1, 3), .Cells(LastRow, 3))
selCol.Value = .Evaluate("INDEX(IF(LEN(" & selCol.Address(0, 0) & ")>8,RIGHT(" & selCol.Address(0, 0) & ",6),RIGHT(" & selCol.Address(0, 0) & ",4)),)")
End With
Application.ScreenUpdating = True
End Sub
Or work with arrays
Sub ShortenPositionNumbers()
Dim data As Variant
Dim i As Long
With Range("C3:C" & Cells(Rows.Count, "A").End(xlUp).Row)
data = Application.Transpose(.Value)
For i = LBound(data) to UBound(data)
If Len(data(i)) > 8 Then
data(i) = RIGHT(data(i),6)
Else
data(i) = RIGHT(data(i),4)
End If
Next
.Value = Application.Transpose(data)
End With
End Sub

VBA Transpose Dataset (Guidance) [duplicate]

This question already has answers here:
How to "flatten" or "collapse" a 2D Excel table into 1D?
(9 answers)
Closed 6 years ago.
Currently I have a data-set of 4000 rows with data arranged below:
The format it needs to be in is like this:
I have ignored the dates field or the X,Y,Z fields at the moment and just want to focus on the rows. I'm new to VBA still so please bear with my explanations.
My understanding of this is that I should use a variant to store the data as 1-dimensional arrays and then cycle through this via a for-loop.
This is what my code attempts to do (albeit clumsily):
Sub TransposeData()
Dim Last As Variant
Application.ScreenUpdating = False
prevCalcMode = Application.Calculation
Application.Calculation = xlCalculationManual
Last = Cells(Rows.Count, "L").End(xlUp).Row
'Go to the very bottom of row L and get the count
'For i = row Count - 1 from this and check what the value of L is
'If the value of L is greater than 0 Then ...
For i = Last To 1 Step -1
If (Cells(i, "L").Value) > 0 Then
range("D" & i & ":L" & i).Copy
Sheets("test").Select
Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=True
Sheets("CVM").Select
End If
Next i
Application.Calculation = prevCalcMode
Application.ScreenUpdating = True
End Sub
However I am stuck at setting my 'range' variable as I don't know how to make it specific to each iteration. i.e. Range(i,L) This will not work obviously but I can't seem to think of another way around this.
Could you please point me in the right direction? I did look at a few other VBA questions regarding this but I couldn't apply the same methodology to my issue.
(Transpose a range in VBA)
Thank you!
EDIT: I now have my macro starting to work (yay!), but the loop keeps over-writing the data. Is there a way to check where the data was last pasted and make sure you paste in the next blank part of the column?
Seeing as you are new to VBA, as you said.
A few things:
Always use indexed based reference, like you used for range("D" & i & ":L" & i).Copy but then you did not use it for the PasteSpecial
Make sure you use referencing to the specific sheet you are wanting to operate out of, this way VBA doesnt need to assume anything
Try use descriptive variables this helps the next user really understand your code.
Also Use Option Explicit ALWAYS, I did no like it in the beginning but once I was used to typing correct variables for everything, like we should, its not an issue anymore. To have the Option Explicit on every module just go
Tool >> Options >> Require Variable Declaration
See answer below
Option Explicit
Sub TransposeData()
Application.ScreenUpdating = False
Dim PrevCalcMode As Variant
PrevCalcMode = Application.Calculation
Application.Calculation = xlCalculationManual
Dim DataSheet As Worksheet
Set DataSheet = ThisWorkbook.Sheets("CVM")
Dim DestinationSheet As Worksheet
Set DestinationSheet = ThisWorkbook.Sheets("test")
Dim DataSheetLastCell As Variant
With DataSheet
DataSheetLastCell = .Cells(.Rows.Count, "L").End(xlUp).Row
End With
Dim DataSheetRowRef As Long
Dim DestinationSheetNextFreeRow As Long
For DataSheetRowRef = 2 To DataSheetLastCell
If Not DataSheet.Cells(DataSheetRowRef, "L") = Empty Then
DataSheet.Range("D" & DataSheetRowRef & ":L" & DataSheetRowRef).Copy
With DestinationSheet
DestinationSheetNextFreeRow = .Cells(.Rows.Count, "B").End(xlUp).Row + 1
.Cells(DestinationSheetNextFreeRow, "B").PasteSpecial Transpose:=True
End With
End If
Next DataSheetRowRef
Application.ScreenUpdating = True
PrevCalcMode = Application.Calculation
End Sub

Copy multiple rows from one worksheet to another worksheet using macro/vba

I've looked around the forum and played with various options but not found a clear match for my problem:
My task is to copy data from a worksheet (called “workorders”) to a second worksheet (called “Assignments”). The data to be copied is from the “workorders” worksheet starting at cell range “E2, P2:S2”; and also copied from each row (same range) until column “P” is empty – (the number of rows to be copied can vary each time we need to run this macro so we can’t select a standard range) . Then pasted into the “Assignments” worksheet, starting at cell “A4”. I’ve used the forum so far to successfully copy a single row of date (from row 2) – I admit that’s the easy part, and I’ve used various versions of code to achieve this.
I’ve also tried some code (which I found via watching a youtube clip and modifying http://www.youtube.com/watch?v=PyNWL0DXXtQ )to allow me to run a loop which repeats the copy process for each required row in the “workorders” worksheet and then pastes the data into the “assignments” worksheet- but this is where I am not getting it right, I think I’m along the right lines and think I’m not far off but any help would be very useful.
Code examples below (first 2 only copy first row, 3rd example is where I’ve tried to loop and copy multiple rows:
Sub CopyTest1()
' CopyTest1 Macro
'copy data from workorders sheet
'Worksheets("workorders").Range("E2,P2,Q2,R2,S2").Copy
Worksheets("workorders").Range("E2, P2:S2").Copy
'paste data to assignments sheet
'sheets("assigments dc").Range("A4").Paste
Sheets("Assigments DC").Select
Range("A4").Select
ActiveSheet.Paste
Application.CutCopyMode = False
End Sub
Sub CopyTest2()
Sheets("workorders").Range("e2,p2,q2,r2,s2").Copy Sheets("assigments dc").Range("a4")
End Sub
Sub CopyTest3()
Dim xrow As Long
'Dim xrow As String
xrow = 2
Worksheets("workorders").Select
Dim lastrow As Long
lastrow = Cells(Rows.Count, 16).End(xlUp).Row
Do Until xrow = lastrow + 1
ActiveSheet.Cells(xrow, 16).Select
If ActiveCell.Text = Not Null Then
'Range("E2,P2,Q2,R2,S2").Copy
'Selection = Range("E2,P2,Q2,R2,S2").Copy
'Cells(xrow, 5).Copy
Cells(xrow, 5).Copy
Sheets("Assigments DC").Select
Range("A4").Select
ActiveSheet.Paste
Application.CutCopyMode = False
Sheets("workorders").Select
End If
xrow = xrow + 1
Loop
End Sub
Try this:
Sub LoopCopy()
Dim shWO As Worksheet, shAss As Worksheet
Dim WOLastRow As Long, Iter As Long
Dim RngToCopy As Range, RngToPaste As Range
With ThisWorkbook
Set shWO = .Sheets("Workorders") 'Modify as necessary.
Set shAss = .Sheets("Assignments") 'Modify as necessary.
End With
'Get the row index of the last populated row in column P.
'Change accordingly if you want to use another column as basis.
'Two versions of getting the last row are provided.
WOLastRow = shWO.Range("P2").End(xlDown).Row
'WOLastRow = shWO.Range("P" & Rows.Count).End(xlUp).Row
For Iter = 2 to WOLastRow
Set RngToPaste = shAss.Range("A" & (Iter + 2))
With shWO
Set RngToCopy = Union(.Range("E" & Iter), .Range("P" & Iter & ":S" & Iter))
RngToCopy.Copy RngToPaste
End With
Next Iter
End Sub
Read the comments first and test.
Let us know if this helps.
From what I see, you are only copying the cell in Column E. You could correct this by replacing Cells(xrow, 5).Copy with
Union(Sheets("workorders").Cells(xrow,5),Sheets("workorders").Range(Cells(xrow,"P"),Cells(xrow,"S")).Copy
However, using Select and Copy are not ideal. Instead, you can assign the value of the range directly:
Sheets("Assignments DC").Range("A4").Value = Union(Sheets("workorders").Cells(xrow,5),Sheets("workorders").Range(Cells(xrow,"P"),Cells(xrow,"S")).Value
More info on the Union method and why using Select is bad.
Is it even possible to run a line like this?
Worksheets("workorders").Range("E2, P2:S2").Copy
Each time I try different ways to copy/select a range which contains in my case, A3 and the range A34:C40 ("A3, A34:C40").Copy i get an error saying theres to many parameters.. Could this be because I'm running excel 2007?
Any tips or help would be greatly apreciated! :)