I am attempting to edit my code because although it functions as needed, I know it is not efficient. I am copying 5 merged cells at a time and pasting copied data in the column to the left before clearing the copied range.
Every 6th cell is skipped because it does not apply to what needs to be moved. A snippet is below, I know there is a much more efficient way to do what I am doing here, but I am brand new and have basically no experience in declaring variables or utilizing loops, functions, etc.
Thanks in advance!
Option Explicit
Sub ShiftWeeks()
Dim answer As VbMsgBoxResult
answer = MsgBox("Are you sure you want to copy/paste this weeks data?", vbYesNo, "Press Button for Macro")
If answer = vbYes Then
Range("c3:c8").Copy
Range("b3:b8").PasteSpecial xlPasteValues
Range("c3:c8").ClearContents
Range("c10:c15").Copy
Range("b10:b15").PasteSpecial xlPasteValues
Range("c10:c15").ClearContents
Range("c17:c22").Copy
Range("b17:b22").PasteSpecial xlPasteValues
Range("c17:c22").ClearContents
Range("c24:c29").Copy
Range("b24:b29").PasteSpecial xlPasteValues
Range("c24:c29").ClearContents
End If
End Sub
I've copied the same 3 code blocks quite a few times and have just changed the range. The button I made works, but I know the code is junk and it's not that difficult, but I don't know how to clean it up.
Not to nit-pick but it looks like you're actually copying 6 rows at a time, not 5 (e.g. C3:C8 is six rows). Following the same pattern you have above, you could use a For x = y to z step a -style loop, like below. If the last row will always be the same, you could define it with a lastRow = y statement, otherwise you can determine it dynamically with something like lastRow = ws.Cells(ws.Rows.Count, 3).End(xlUp).Row.
For x = 3 To lastRow Step 7
ws.Range(ws.Cells(x, 3), ws.Cells(x + 5, 3)).Copy
ws.Range(ws.Cells(x, 2), ws.Cells(x + 5, 2)).PasteSpecial xlPasteValues
ws.Range(ws.Cells(x, 3), ws.Cells(x + 5, 3)).ClearContents
Next x
Looping
If you need to add a dozen more ranges, having to repeat lines of code per range is tedious and will quickly become unmanageable. To avoid this issue, you can change the code into a loop.
There are two ways to create the loop.
By blocks:
Sub Example()
For r = 3 To 24 Step 7
With Cells(r, 3).Resize(6)
.Offset(0, -1).Value = .Value
.ClearContents
End With
Next
End Sub
And by cells:
Sub Example()
Dim Cell As Range
For Each Cell In Range("c3:c8,c10:c15,c17:c22,c24:c29")
With Cell
.Offset(0, -1).Value = .Value
.ClearContents
End With
Next
End Sub
In the Block loop, we define the starting locations (r=3 and Cells ColumnIndex:=3) and then the block size (Step 7 and Resize(6)). In the Cells loop we simply define the range we want to operate within and execute the desired actions on every cell within that range.
In both methods, adding new locations to the macro would be as simple as changing the For loop statement line. Either by increasing the ending number from 24 or by adding more addresses to the Range.
Clipboard Copying
The clipboard is not a native feature of Excel and is actually a part of Windows. This means that when you use .Copy and .PasteSpecial in seperate lines, Excel has to communicate with windows and share the data. This is significantly slower than having the data stay within Excel. This issue is avoided by doing Range2.Value = Range1.Value and directly assigning the data without using the clipboard. You can also do Range1.Copy Destination:=Range2 but this will copy over the formatting as well as the values.
Additional Improvements
If you test out the cells loop, you'll notice that the application stutters and can be quite slow. To avoid this, you'll want to temporarily disable automatic actions by the application so that it doesn't pause during each loop. The application wants to re-calculate the sheet after every change and refresh the screen to display new values. Both of these are the cause of the stuttering and disabling them during the macro will speed things up significantly.
Sub Example()
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
''''''''''''''''
'Code goes here'
''''''''''''''''
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Copy Merged Ranges
Option Explicit
Sub ShiftWeeks()
Const wsName As String = "Sheet1"
Const fRow As Long = 3
Const sCol As String = "C"
Const cOffset As Long = -1
Const rOffset As Long = 7 ' needed only for the second solution
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
Dim answer As VbMsgBoxResult
answer = MsgBox("Are you sure you want to copy/paste this weeks data?", vbYesNo, "Press Button for Macro")
If answer = vbYes Then
Dim lRow As Long: lRow = ws.Cells(ws.Rows.Count, sCol).End(xlUp).Row
If lRow < fRow Then Exit Sub ' no data
' 1.) If the rows between the merged ranges are empty...
Dim srg As Range: Set srg = ws.Cells(fRow, sCol).Resize(lRow - fRow + 1)
srg.Offset(, cOffset).Value = srg.Value
srg.Value = Empty
' 2.) ... otherwise:
' Dim sCell As Range
' Dim r As Long
'
' For r = fRow To lRow Step rOffset
' Set sCell = ws.Cells(r, sCol)
' sCell.Offset(, cOffset).Value = sCell.Value
' sCell.Value = Empty
' 'sCell.MergeArea.ClearContents
' Next r
End If
End Sub
Related
I have VBA running on an Excel sheet that translates the data in the sheet so I can import it into another application.
The below function cleans the data and removes text wrapping. This function takes a long time to run when the sheets have a large cell count. Since I am normalizing data to import to a relational database, there are frequently a lot of cells across the seven different sheets I have.
Is there a more efficient way to remove the text wrap and clean the data in the cells?
Dim ws As Worksheet
Dim x, lrow, lcol, active As Long
Dim r, cel As Range
active = ActiveWorkbook.Worksheets.count
For x = 1 To active
Set ws = ThisWorkbook.Sheets(x)
ws.Select
Select Case ws.name
Case "Solution", "Description", "Problem", "Buyer", "ProjectType", "Process", "Feature"
lrow = ws.UsedRange.Rows(ActiveSheet.UsedRange.Rows.count).row
lcol = ws.UsedRange.Columns(ActiveSheet.UsedRange.Rows.count).Column
If lrow > 1 Then
Set r = ws.Range(Cells(2, 1), Cells(lrow, lcol))
For Each cel In r.Cells
cel.WrapText = False
cel.Value = Application.WorksheetFunction.Clean(cel.Value)
Next cel
End If
Case Else
End Select
ws.Cells(1, 1).Select
ThisWorkbook.Sheets("Solution").Activate
Next x
Your code can be reduced to
Sub Demo()
Dim ws As Worksheet
For Each ws In ActiveWorkbook.Worksheets
Select Case ws.Name
Case "Solution", "Description", "Problem", "Buyer", "ProjectType", "Process", "Feature"
With ws.UsedRange
.WrapText = False
.Value = ws.Evaluate("Clean(" & .Address & ")")
End With
End Select
Next
End Sub
On my hardware, a sheet with 100,000 rows 26 columns ran in about 6s
Note: OPs claim (in comment) that "it changes the value of every cell on the sheet to the first value in the first sheet it encounters." - tested this claim and it's not accurate. This code works as advertised.
To remove the text wrapping property (and it surprises me that has an affect on your external program), you should be able to do just:
r.WrapText = False
For the Clean, what, exactly, is in the cells that you are trying to remove?
It would be faster to read the cells into an array; process them, and write them back.
Something like: (not debugged)
Dim V, I as long, J as Long
v = R
for i = 1 to ubound(v)
for j = 1 to ubound(v,2)
`worksheetfunction.clean(v(i,j))
`or some other function to remove unwanted characters
next j
next i
r.clear
r.value = v
You should also be aware that UsedRange is not particularly reliable and may wind up with you processing many more cells than necessary.
There are a number of posts on this forum showing better methods of determing the Last Row and Last Column.
So I have a problem that this is generating random results with the Qty.
I am trying to make each qty (in their qty's) a new line on a new spreadsheet.
It creates the new sheet, and references the old sheet...
the code copies and pastes the lines...
It just doesn't loop the do while in the correct amount of times. I have tried different operands (>= 0) and altering the variable values to make this work.
It does not seem to be patternized as to why it is happening. Sometimes it does it in the correct amount of loop cycles, others it does not. This occurs on multiple values. Any help is greatly appreciated.
Sub copyPasta()
'
' copyPasta Macro
' This will take the qty, if greater than one in Column C and copy the row
'to a new sheet the amount of time the qty.
'
'
'Set Variable Types
Dim lineItemQty As Integer
Dim newLineItemQty As Integer
Dim LastRow As Integer
Dim strSheetName As String
Dim newSheetName As String
Dim i As Integer
Application.DisplayAlerts = False
'name a variable after the existing active sheet
strSheetName = ActiveSheet.Name
'add a sheet in addition to the current
Sheets.Add After:=ActiveSheet
'set a variable used in loops to the sheet being copied to
newSheetName = ActiveSheet.Name
'Return to first sheet
Sheets(strSheetName).Activate
' Set For Loop to max row
LastRow = Sheets(strSheetName).Range("C:C").Find("*", searchdirection:=xlPrevious).Row
'for loop to run through all rows
For i = 3 To LastRow Step 1
'initializing variable to Qty value in table
lineItemQty = Range("C" & i).Value
'initializing variable within in line of for looping
newLineItemQty = lineItemQty
'do while loop to keep copying/pasting while there are still qty's
Do While newLineItemQty > 0
'do while looped copy and paste
'copy the active row
Sheets(strSheetName).Activate
Rows(i).Select
Selection.Copy
'paste active row into new sheet
Sheets(newSheetName).Select
Rows("3:3").Select
Selection.Insert Shift:=xlDown
newLineItemQty = newLineItemQty - 1
Loop
Next i
Application.DisplayAlerts = True
End Sub
You can consider using (or taking parts from) the below alternative. A couple of note worthy notes are
You should avoid using .Select and .Activate. See here for details
Life is easier when you declare short variables. Here we just have ws for worksheet and ns for newsheet. You then need to actively state what sheet you are refferring to in your code (instead of using .Select or .Activate to do so by prefixing all objects with the appropriate worksheet variable)
You do not need to add Step 1 in your loop. This is the default - you only need to add this when you are deviating from the default!
There are a few ways to add sheets. Nothing wrong with the way you did - here is just an alternative (yay learning) that happens to be my preferred method.
To copy n many times, just create a nested loop and for 1 to n. Notice we never really use the variable n inside the loop which means the exact same operation will execute, we just want it to execute n times.
Sub OliveGarden()
Dim ws As Worksheet: Set ws = ThisWorkbook.ActiveSheet
Dim ns As Worksheet: Set ns = ThisWorkbook.Sheets.Add(After:=Sheets(ThisWorkbook.Sheets.Count))
ns.Name = ws.Name & " New"
Dim i As Long, c As Long
'Application.ScreenUpdating = False
For i = 3 To ws.Range("C" & ws.Rows.Count).End(xlUp).Row
If ws.Range("C" & i) > 0 Then
For c = 1 To ws.Range("C" & i)
LRow = ns.Range("A" & ns.Rows.Count).End(xlUp).Offset(1).Row
ws.Range("C" & i).EntireRow.Copy
ns.Range("A" & LRow).PasteSpecial xlPasteValues
Next c
End If
Next i
'Application.ScreenUpdating = True
End Sub
I am attempting to copy filtered data from one sheet to another. It copies everything to the same line.
How do populates all the rows instead of copying them over the same one?
Here is the code I modified:
Private Sub Workbook_Open()
Dim i, LastRow
LastRow = Sheets("Scheduled WO's").Range("A" & Rows.Count).End(xlUp).Row
Sheets("Branden").Range("A2:Q10000").ClearContents
For i = 2 To LastRow
If Sheets("Scheduled WO's").Cells(i, "G").Value = "Branden" Then
Sheets("Scheduled WO's").Cells(i, "G").EntireRow.Copy Destination:=Sheets("Branden").Range("A" & Rows.Count).End(xlUp).Offset(1)
End If
Next
End Sub
you have to take off the statement
Sheets("Branden").Range("A2:Q10000").ClearContents
which clears "Branden" worksheet cells at every opening of the workbook it resides in
furthermore, since your need is filtering, you may want to use Autofilter and avoid looping through cells
Private Sub Workbook_Open()
With Worksheets("Scheduled WO's")
With .Range("G1:G" & .Cells(.Rows.Count, 1).End(xlUp).Row)
.AutoFilter field:=1, Criteria1:="Branden"
If Application.WorksheetFunction.Subtotal(103, .Cells) - 1 > 0 Then .Offset(1).Resize(.Rows.Count - 1).SpecialCells(XlCellType.xlCellTypeVisible).EntireRow.Copy Worksheets("Branden").Range("A" & Rows.Count).End(xlUp).Offset(1)
End With
.AutoFilterMode = False
End With
End Sub
Copy Rows Based On Matching Criteria From One Sheet To Another
There's 2 ways we can go about this.
Code 1
The first is sticking with what you were doing, which may or may not be the slower way of accomplishing this (depending on how many cells you're moving through.)
Option Explicit
Private Sub Workbook_Open()
Dim wsWO As Worksheet: Set wsWO = ThisWorkbook.Sheets("Scheduled WO's")
Dim wsB As Worksheet: Set wsB = ThisWorkbook.Sheets("Branden")
Dim LastRow As Long: LastRow = wsWO.Cells(wsWO.Rows.Count, 1).End(xlUp).Row
Dim i As Long
wsB.Range("A2:Q10000").ClearContents
For i = 2 To LastRow
If wsWO.Cells(i, "G").Value = "Branden" Then _
wsWO.Cells(i, "G").EntireRow.Copy _
wsB.Range("A" & wsB.Cells(ws.Rows.Count, 1).End(xlUp).Row + 1)
Next i
End Sub
Code 2
The other way we can do this is by specifically finding only occurences of "Branden", and copying those rows over.
Option Explicit
Private Sub Workbook_Open()
Dim wsWO As Worksheet: Set wsWO = ThisWorkbook.Sheets("Scheduled WO's")
Dim wsB As Worksheet: Set wsB = ThisWorkbook.Sheets("Branden")
Dim findBranden As Range: Set findBranden = wsWO.Range("G:G") _
.Find(What:="Branden", LookIn:=xlValues, LookAt:=xlWhole)
Dim firstResult As String
wsB.Range("A2:Q10000").ClearContents
If Not findBranden Is Nothing Then
firstResult = findBranden.Address
Do
findBranden.EntireRow.Copy _
wsB.Range("A" & wsB.Cells(wsB.Rows.Count, 1).End(xlUp).Row + 1)
Set findBranden = wsWO.Range("G:G").FindNext(findBranden)
Loop While Not findBranden Is Nothing And findBranden.Address <> firstResult
Else: MsgBox "Nothing to move today.", vbInformation, ""
End If
End Sub
You'll notice there's a couple new things in both codes.
An important one is Option Explicit. Including this at the top of your code module will alert you at compile if you have any variables that aren't declared. This is incredibly useful because it will catch spelling mistakes and the like before your code runs. I dare say all experienced VBA coders use Option Explicit or have Require Variable Declaration turned on in the Tools > Options > Editor menu.
Another very important change was declaring the specific type of variables we are using. In your code, LastRow and i are assumed as Variant types because you never specified their use. It's good practice to be as specific as you can in coding, especially with variable declarations, because it will make troubleshooting your code a lot more simple.
I also declared your worksheets as variables to make the written code smaller, and easier to read.
Literature You May Find Useful
Why Option Explicit?
Why Require Variable Declaration?
Why declare the specific type of variable?
Both methods are viable and easy to manipulate. Let me know if I've managed to help :)
I have a rather silly problem. I have a macro (linked to a button) which copies cells A1:A2 from one worksheet (namedFP) to another worksheet (Log). I intend to copy these 2 cells on the log sheet every time I hit the macro button. The problem I am facing right now is that when I use the button multiple times, these cells are getting copied over each other instead of using the next available row to paste the cells.
This is what I have now, and I tried changing the 'Rowcount+1' to 'RowCount+2' but that did not work. Any help is appreciated.
DHRSheet.Select
Range("A1:A2").Select
Selection.Copy
LogSheet.Select
RowCount = LogSheet.UsedRange.Rows.Count
Dim r As Integer
r = RowCount + 1
Dim infocell As Range
Set infocell = Cells(r, 1)
infocell.Select
ActiveSheet.Paste
infocell.Value = DHRSheet.Name & "$" & infocell.Value
DHRSheet.Select
ActiveWorkbook.Save
Is this what you are trying?
Sub Sample()
Dim LogSheet As Worksheet, DHRSheet As Worksheet
Dim lrow As Long
'~~> Change this as applicable
Set LogSheet = Sheets("Sheet1")
Set DHRSheet = Sheets("Sheet2")
With LogSheet
lrow = LogSheet.Range("A" & .Rows.Count).End(xlUp).Row + 1
DHRSheet.Range("A1:A2").Copy .Range("A" & lrow)
End With
End Sub
Here's a function I use that is very reliable and always returns the last row of a sheet without fail:
(possibly excessive for your simple use, but I always recommend it)
Public Function LastRowOfSheet(ByVal TestSheetNumber As Variant)
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Input: Sheet index # or Sheet name
' Output: Last row of sheet.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim intNumberOfRowsInWorksheet As Long
intNumberOfRowsInWorksheet = Sheets(TestSheetNumber).UsedRange.Rows.Count
intNumberOfRowsInWorksheet = intNumberOfRowsInWorksheet + Sheets(TestSheetNumber).UsedRange.Row - 1
LastRowOfSheet = intNumberOfRowsInWorksheet
End Function
And I'd clean up your above code and use something like this:
Sub Move2RowsToEnd()
Dim iNextRowOfOutput As Long
Dim iRowNumber As Long
'- use the function to find the last row of the output sheet. we'll be pasting to the first row after.
iNextRowOfOutput = (LastRowOfSheet("Log") + 1)
'- you can adjust this for loop to loop through additional cells if you need to paste more than 2 rows in the future.
For iRowNumber = 1 To 2
'- for each row of input (2 total) set the value of the output sheet equal to it.
Sheets("Log").Range("A" & iNextRowOfOutput).Value = Sheets("namedFP").Range("A" & iRowNumber).Value
iNextRowOfOutput = iNextRowOfOutput + 1
Next iRowNumber
'- not sure which of these you want to save (one or both)
Sheets("namedFP").Save
Sheets("Log").Save
End Sub
Just paste the function above or below the Subroutine and let me know if you have any issues or questions regarding the 'Move2RowsToEnd' code.
I have an excel file which looks like this:
row1_cell1 row1_cell2 row1_cell3
row2_cell1 row2_cell2 row2_cell3
row3_cell1 row3_cell2 row3_cell3
How can i make three (or any number of) copies of each row that i have in the sheet, which i would like to be added after the row being copied? So, in the end i would like to have this kind of a result:
row1_cell1 row1_cell2 row1_cell3
row1_cell1 row1_cell2 row1_cell3
row1_cell1 row1_cell2 row1_cell3
row2_cell1 row2_cell2 row2_cell3
row2_cell1 row2_cell2 row2_cell3
row2_cell1 row2_cell2 row2_cell3
row3_cell1 row3_cell2 row3_cell3
row3_cell1 row3_cell2 row3_cell3
row3_cell1 row3_cell2 row3_cell3
This is how I would do that for all rows on the sheet:
Option Explicit
Sub MultiplyRows()
Dim RwsCnt As Long, LR As Long, InsRw As Long
RwsCnt = Application.InputBox("How many copies of each row should be inserted?", "Insert Count", 2, Type:=1)
If RwsCnt = 0 Then Exit Sub
LR = Range("A" & Rows.Count).End(xlUp).Row
Application.ScreenUpdating = False
For InsRw = LR To 1 Step -1
Rows(InsRw).Copy
Rows(InsRw + 1).Resize(RwsCnt).Insert xlShiftDown
Next InsRw
Application.ScreenUpdating = True
End Sub
There isn't a direct way to paste them interleaved like what you wanted. However, you can create a temporary VBA to do what you want.
For example, you can:-
Create a VBA procedure (like the one below) in your Excel file.
Assign a keyboard shortcut (eg. Ctrl+Q) to it.
To do this, press Alt+F8, then select the macro, then click 'Options'.
Select the cells you want to copy, then press Ctrl+C.
Select the cell you want to paste in, then press Ctrl+Q (or whatever keyboard shortcut you chose).
Enter the number of times you want to copy. (In your example, it would be 3.)
WHAMMO! :D
Now you can delete the VBA procedure. :)
VBA Code:
Sub PasteAsInterleave()
Dim startCell As Range
Dim endCell As Range
Dim firstRow As Range
Dim pasteCount As Long
Dim rowCount As Long
Dim colCount As Long
Dim i As Long
Dim j As Long
Dim inputValue As String
If Application.CutCopyMode = False Then Exit Sub
'Get number of times to copy.
inputValue = InputBox("Enter number of times to paste interleaved:", _
"Paste Interleave", "")
If inputValue = "" Then Exit Sub 'Cancelled by user.
On Error GoTo Error
pasteCount = CInt(inputValue)
If pasteCount <= 0 Then Exit Sub
On Error GoTo 0
'Paste first set.
ActiveSheet.Paste
If pasteCount = 1 Then Exit Sub
'Get pasted data information.
Set startCell = Selection.Cells(1)
Set endCell = Selection.Cells(Selection.Cells.count)
rowCount = endCell.Row - startCell.Row + 1
colCount = endCell.Column - startCell.Column + 1
Set firstRow = Range(startCell, startCell.Offset(0, colCount - 1))
'Paste everything else while rearranging rows.
For i = rowCount To 1 Step -1
firstRow.Offset(i - 1, 0).Copy
For j = 1 To pasteCount
startCell.Offset(pasteCount * i - j, 0).PasteSpecial
Next j
Next i
'Select the pasted cells.
Application.CutCopyMode = False
Range(startCell, startCell.Offset(rowCount * pasteCount - 1, colCount - 1)).Select
Exit Sub
Error:
MsgBox "Invalid number."
End Sub
Old thread, however someone might find this useful:
The below information was copied from here
I needed to do almost the opposite. I needed the formula to increment by 1 every 22 rows, leaving the 21 rows between blank. I used a modification of the formula above and it worked great. Here is what I used:
=IFERROR(INDIRECT("J"&((ROW()-1)*1/22)+1),"")
The information was in column "J".
The "IFERROR" portion handles the error received when the resulting row calculation is not an integer and puts a blank in that cell.
Hope someone finds this useful. I have been looking for this solution for a while, but today I really needed it.
Thanks.