Copying Multiple Ranges to Next Available Row - excel

I'm copying rows of data from one spreadsheet to another on a button press when cell I says "Yes" and deleting the original row of data. I have multiple ranges I'm copying from the same row, because the second spreadsheet doesn't need all the data held in the first. (first spreadsheet has over 20 columns worth of data but the second has half that). Is there an easy way to make sure this all gets copied to the same row in the new spreadsheet?
Basically what I'm currently doing is copying each of the ranges to the corresponding column in the new spreadsheet with the row number set to being the last used row offset by 1. Which works fine if the previous cells actually have data in, but sometimes they don't (the data is on households and some have more data than others so not all columns are always filled) so the data is placed in a different row from the rest of my data for that particular household.
Private Sub CommandButton1_Click()
Dim c As Range
Dim r As Integer
Dim LastRowD
Dim LastRowR
Dim Database As Worksheet
Dim DeReg As Worksheet
'Set worksheet deignation as needed
Set Database = ActiveWorkbook.Worksheets("Fostering Households")
Set DeReg = ActiveWorkbook.Worksheets("De-Registrations")
LastRowD = Database.Cells(Database.Rows.Count, "A").End(xlUp).Row
'Searches all rows in I
For Each c In Database.Range("I1:I" & LastRowD)
'Catches cases where "Yes" is present in column I
If c = "Yes" Then
LastRowR = Database.Cells(Database.Rows.Count, "A").End(xlUp).Offset(1, 0)
r = c.Row
'Copies the desired column data from rows containing "Yes" from Database tab and pastes it in DeReg tab
Database.Range("A" & r & ":G" & r).Copy DeReg.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
Database.Range("H" & r).Copy DeReg.Range("AJ" & Rows.Count).End(xlUp).Offset(1, 0)
Database.Range("J" & r & ":X" & r).Copy DeReg.Range("H" & Rows.Count).End(xlUp).Offset(1, 0)
Database.Range("AN" & r).Copy DeReg.Range("W" & Rows.Count).End(xlUp).Offset(1, 0)
Database.Range("AS" & r).Copy DeReg.Range("X" & Rows.Count).End(xlUp).Offset(1, 0)
Database.Range("AZ" & r & ":BH" & r).Copy DeReg.Range("Y" & Rows.Count).End(xlUp).Offset(1, 0)
End If
Next c
For i = 250 To 1 Step -1
If Database.Range("I" & i) = "Yes" Then
Database.Rows(i).EntireRow.Delete
End If
Next i
End Sub
I've tried defining the last row based on whether "A" has data in (this is the only cell that is always used) with the code:
LastRowR = Database.Cells(Database.Rows.Count, "A").End(xlUp).Offset(1, 0) and then replacing my copy past code with:
Database.Range("A" & r & ":G" & r).Copy DeReg.Range("A" & LastRowR & ":B" & LastRowR).Row
But this didn't work at all - it copied the first row it found with "Yes" in infinitely and overwrote all the data already present.
I also tried:
Database.Range("A" & r & ":G" & r).Copy DeReg.Range("A" & LastRowR).PasteSpecial
which also came with a world of problems and errors.
What I want is to search for the last used Row based on what's in column A, offset by 1, and then past the data in the column I designate, rather than the last row used being defined by the column I'm trying to paste in - is this even doable? I can't seem to find any information on this particular issue.
Also, if there is a better way of handing multiple ranges that would be great as it seems rather convoluted currently!

DeReg.Range("A" & Rows.Count).End(xlUp).Offset(1, 0) evaluates to:
DeReg.Range("A" & ActiveSheet.Rows.Count).End(xlUp).Offset(1, 0), so unless DeReg is the activesheet, you will get the wrong range.
See if this helps:
.... other code
'LastRowR = Database.Cells(Database.Rows.Count, "A").End(xlUp).Offset(1, 0)
r = c.Row
'Copies the desired column data from rows containing "Yes" from Database tab and pastes it in DeReg tab
With Database
LastRowR = DeReg.Range("A" & DeReg.Rows.Count).End(xlUp).Row + 1
.Range("A" & r & ":G" & r).Copy DeReg.Range("A" & LastRowR)
.Range("H" & r).Copy DeReg.Range("AJ" & LastRowR)
.Range("J" & r & ":X" & r).Copy DeReg.Range("H" & LastRowR)
.Range("AN" & r).Copy DeReg.Range("W" & LastRowR)
.Range("AS" & r).Copy DeReg.Range("X" & LastRowR)
.Range("AZ" & r & ":BH" & r).Copy DeReg.Range("Y" & LastRowR)
End With
End If
... other code

Some helpful tips:
i is not declared. Declare as Long.
LastRowR, r & LastRowD should be declared as Long.
Replace ActiveWorkbook with ThisWorkbook.
The copy paste method used may slow down the program, because this method copies and pastes both values and formatting.
When you want to paste in the line after last row, use +1 (Example: LastRow = sht.Cells(sht.Rows.Count, "A").End(xlUp).Row +1

Just a couple thoughts. Firstly, you have declared multiple things that are unnecessary (IMO). I have adjusted your for loop to simply loop through a value that then references the range you want. This way you can use the i value a lot more efficiently than first setting a range and then looping through and referencing the row etc.
Additionally, based on the understanding I get from your post, if you use the .UsedRanged method your outputs will start at the last row on the new sheet, irregardless of your previously chosen method by column. I have not tested the below code, but it should guide you in a clearer way.
Option Explicit
Private Sub CommandButton1_Click()
Dim i As Long
Dim LastRowD As Long
Dim LastRowR As Long
Dim Database As Worksheet
Dim DeReg As Worksheet
'Set worksheet deignation as needed
Set Database = ActiveWorkbook.Worksheets("Fostering Households")
Set DeReg = ActiveWorkbook.Worksheets("De-Registrations")
LastRowD = Database.Cells(Database.Rows.Count, "A").End(xlUp).Row
'Searches all rows in I
For i = 1 To LastRowD
'Catches cases where "Yes" is present in column I
If Database.Range("I" & i) = "Yes" Then
LastRowR = Database.UsedRange.Rows.Count + 1
'Copies the desired column data from rows containing "Yes" from Database tab and pastes it in DeReg tab
Database.Range("A" & i & ":G" & i).Copy DeReg.Range("A" & LastRowR)
Database.Range("H" & i).Copy DeReg.Range("AJ" & LastRowR)
Database.Range("J" & i & ":X" & i).Copy DeReg.Range("H" & LastRowR)
Database.Range("AN" & i).Copy DeReg.Range("W" & LastRowR)
Database.Range("AS" & i).Copy DeReg.Range("X" & LastRowR)
Database.Range("AZ" & i & ":BH" & i).Copy DeReg.Range("Y" & LastRowR)
End If
Next i
For i = 250 To 1 Step -1
If Database.Range("I" & i) = "Yes" Then
Database.Rows(i).EntireRow.Delete
End If
Next i
End Sub

Related

Change Reference

I am still working on streamlining our Attendance worksheet and I decided to add a macro for copying the last three rows and pasting them at the end; the only problem is I need the formula in the first two cells to only change their cell references by 1. The code I have so far is
Sub New_Weekly_Row()
Range("A" & Rows.Count).End(xlUp).Offset(-2).Resize(3).EntireRow.Copy
Range("A" & Rows.Count).End(xlUp).Offset(1).Resize(3).EntireRow.PasteSpecial
Range("A" & Rows.Count).End(xlUp).Offset(-2).Resize(2, 1).Select
End Sub
I have been trying to see if there is a way to either lock the references (both cells that would be copied would just be something like =U34 and =W34 respectively) to only increasing by one or subtracting two from the cells that are pasted to.
The closest thing I have found so far is
Dim r As Range, b As Integer
Set r = Range("A" & Rows.Count).End(xlUp).Offset(-2)
b = Val(Right(r.Formula, Len(r.Formula) - 3))
b = b - 2
r.Formula = Range("U" & Columns.Count).End(xlUp) + CStr(b)
Range("A" & Rows.Count).End(xlUp).Offset(-1).Select
But that just enters it as plain text.
This worked a few times, but stopped
Sub Update_Reference()
Dim myCell As Range
Range("A" & Rows.Count).End(xlUp).Offset(-2).Resize(2).Select
For Each myCell In Selection
If myCell.HasFormula Then myCell.Formula = Left(myCell.Formula, _
Len(myCell.Formula) - 1) & 4
Next myCell
End Sub
I don't know if this information helps any, but this will be used in conjunction with this
Sub New_Weekly_Row()
Range("A" & Rows.Count).End(xlUp).Offset(-2).Resize(3).EntireRow.Copy
Range("A" & Rows.Count).End(xlUp).Offset(1).Resize(3).EntireRow.PasteSpecial
Range("U" & Columns.Count).End(xlUp).Offset(1).Resize(1, 2).Merge (Across)
Range("W" & Columns.Count).End(xlUp).Offset(1).Resize(1, 2).Merge (Across)
Range("Y" & Columns.Count).End(xlUp).Offset(1).Resize(1, 2).Merge (Across)
Range("AA" & Columns.Count).End(xlUp).Offset(1).Resize(1, 2).Merge (Across)
Range("U" & Rows.Count).End(xlUp).Resize(1, 8).Copy
Range("U" & Rows.Count).End(xlUp).Resize(1, 8).Offset(1).PasteSpecial
End Sub

How to copy data without also copying the row/column width

I am using VBA to copy and paste a variable number of rows from one sheet to another when they meet a criteria.
This is working. However, when the data is pasted into the target sheet the column and row width change to be the same as the source sheet.
How can I stop this from happening? So that just the data is pasted, without the cell formatting.
If anyone knows it would be much appreciated.
Heres the code I'm using.
Sub copyOverdue()
Dim cell As Range
Dim j As Integer
Dim Source As Worksheet
Dim Target As Worksheet
Set Source = ActiveWorkbook.Worksheets("Action Register")
Set Target = ActiveWorkbook.Worksheets("Sheet1")
j = 36 ' Paste to this number row
For Each cell In Source.Range("A7:A243")
If c = "Overdue" Then
Source.Range("A" & c.row & "," & "G" & c.row).Copy Target.Range("AD" & j)
Source.Range("C" & c.row & "," & "F" & c.row & "," & "H" & c.row & "," & "K" & c.row).Copy Target.Range("AF" & j)
j = j + 1
End If
Next cell
End Sub
You can pastespecial values only
For Each cell In Source.Range("A7:A243")
If cell.value = "Overdue" Then
cell.Resize(, 7).Copy
Target.Range("AD" & j).PasteSpecial xlValues
Source.Range("C" & cell.Row & "," & "F" & cell.Row & "," & "H" & cell.Row & "," & "K" & cell.Row).Copy
Target.Range("AF" & j).PasteSpecial xlValues
j = j + 1
End If
Next cell
You can also avoid the clipboard altogether and transfer the values directly, which is more efficient but it can get a bit mess you are dealing with large ranges.
Target.Range("AD" & j).Resize(,7).Value=cell.Resize(, 7).value

How to stop every time cells changing to value after coping data to another sheet in EXCEL

I would like to write perfectly working code but am faced with this issue. I want to transfer the data as values to another sheet. To fill out the data I use a form with formulas inside cells.
Then every time I click to transfer the data to another sheet it replaces the source data with its values in both sheets, but for me need that forms cells in the sheet1 stay unchanged. I use a form with formula to put data in the sheet1 from different sources, so it should work every time then I use it (now, after each click I have to recover formulas in the sheet1).
Here is the code:
Sub Button4_Click()
Dim x As Long
Dim erow as Long
'Calculate starting rows
x = 15
With Worksheets("Sheet2")
erow = .Cells(.Rows.Count, 1).End(xlUp).Offset(1, 0).Row
End With
With Worksheets("Sheet1")
Do While .Cells(x, 1) <> ""
'Current code replaces the source data with its values'
.Range("A" & x & ":Y" & x).Value = .Range("A" & x & ":Y" & x).Value
'The next line copies values to Sheet2
Worksheets("Sheet2").Range("A" & erow & ":Y" & erow).Value = .Range("A" & x & ":Y" & x).Value
'increment row counters
x = x + 1
erow = erow + 1
Loop
End With
End Sub
You can't write to a cell's .Formula and then add unrelated data to the same cell's .Value - writing to .Value deletes the formula.
So the following code line:
.Range("A" & x & ":Y" & x).Value = .Range("A" & x & ":Y" & x).Value
is unnecessary (and damaging).

VBA to Copy 3 Sheets in a Fouth One

I have a problem making a little VBA to copy/paste some datas. I looked around and didn't really find any post who talk of my problem.
Here is my problem: I have 3 worksheets who need to be copied on a fourth worksheet. Each worksheet have between 200 and 650 lines. On the three sheets, it's the columns A, I, J, K, L, M,N who need to be copied on the columns A, C, D, H, I, M, N. The copy paste action need to start on the first blank line of the fourth sheet. This is the last constraint who make it a lot more difficult than I expected. I tried two ways and haven't managed to make it works.
Here is the code (one way is in comments form)
Dim Sh as Worksheet
Dim i as Integer
For Each Sh In Sheets(Array("Janvier", "Février", "Mars"))
For i = 4 To 650
Worksheets("Sh").Range("A & i").Copy Destination:=Worksheets("Calculs").Range("A" & Sheets("Calculs").UsedRange.Rows.Count + 1)
Worksheets("Sh").Range("I & i:J & i").Copy Destination:=Worksheets("Calculs").Range("I" & Sheets("Calculs").UsedRange.Rows.Count + 1)
Worksheets("Sh").Range("K & i:L & i").Copy Destination:=Worksheets("Calculs").Range("K" & Sheets("Calculs").UsedRange.Rows.Count + 1)
Worksheets("Sh").Range("M & i:N & i").Copy Destination:=Worksheets("Calculs").Range("M" & Sheets("Calculs").UsedRange.Rows.Count + 1)
'Sheets("Calculs").Range("A" & Rows.Count).End(xlUp).Offset(1).Value = Sheets(Sh).Range("A4:A650").Value
'Sheets("Calculs").Range("C" & Rows.Count).End(xlUp).Offset(1).Value = Sheets(Sh).Range("I4:J650").Value
'Sheets("Calculs").Range("H" & Rows.Count).End(xlUp).Offset(1).Value = Sheets(Sh).Range("K4:L650").Value
'Sheets("Calculs").Range("M" & Rows.Count).End(xlUp).Offset(1).Value = Sheets(Sh).Range("M4:n650").Value
Next i
Next Sh
My error after executing the code not in comments form is "Subscript out of range". Can you propose me a better way to code this.
Thank you for your help, Olivier
Try using the .Cells method instead of .Range. Like so:
Worksheets("Sh").Cells(i, 1) ...
Where the first parameter is your row and the second is your columns (A=1, B=2, ect).
Try this:
Sub Tester()
Dim Sh As Worksheet, ws As Worksheet, rw As Range
Dim i As Integer
Set ws = Worksheets("Calculs")
'get first empty row
Set rw = ws.Cells(ws.Rows.Count, 1).End(xlUp).Offset(1, 0).EntireRow
Application.ScreenUpdating = False
For Each Sh In Sheets(Array("Janvier", "Février", "Mars"))
For i = 4 To 650
Sh.Range("A" & i).Copy rw.Cells(1, "A")
Sh.Range("I" & i & ":J" & i).Copy rw.Cells(1, "I")
Sh.Range("K" & i & ":L" & i).Copy rw.Cells(1, "K")
Sh.Range("M" & i & ":N" & i).Copy rw.Cells(1, "M")
Set rw = rw.Offset(1, 0)
Next i
Next Sh
End Sub

How to bold highlight duplicate rows excel vba

I have the following code which picks up duplicate rows, however I can not get the code to highlight the duplicates in bold as well as deleting them at the same time.
Public Sub ProcessData()
Const TEST_COLUMN As String = "A" '<=== change to suit
Dim i As Long
Dim iLastRow As Long
Dim rng As Range
With ActiveSheet
iLastRow = .Cells(.Rows.Count, TEST_COLUMN).End(xlUp).Row
For i = 1 To iLastRow
If .Evaluate("SUMPRODUCT(--(A" & i & ":A" & iLastRow & "=A" & i & ")," & _
"--(D" & i & ":D" & iLastRow & "=D" & i & ")," & _
"--(F" & i & ":F" & iLastRow & "=F" & i & ")," & _
"--(J" & i & ":J" & iLastRow & "=J" & i & ")," & _
"--(K" & i & ":K" & iLastRow & "=K" & i & "))") > 1 Then
If rng Is Nothing Then
Set rng = .Cells(i, "A").Resize(, 11)
Else
Set rng = Union(rng, .Cells(i, "A").Resize(, 11))
End If
End If
Next i
**If Not rng Is Nothing Then rng.Delete.font.bold = true**
End With
End Sub
the example of the dataset and desired output can be seen in the following downloadable link below:
https://www.dropbox.com/s/7rhktg6b4nk6ig0/Bold_highlight_Duplicate%20.xlsm
any help would be very much appreciated. Thank you.
Edit:
to clarify, this is how it should look like, just that input shall be deleted and the bold highlighting should appear in the output section:
Instead of **If Not rng Is Nothing Then rng.Delete.font.bold = true** use the following:
If Not rng Is Nothing Then
with rng
.Offset(.Areas(.Areas.Count).Rows(.Areas(.Areas.Count).Rows.Count).Row + 1).Font.Bold = True
.Delete
end with
End If
How this works?
Well, you could have setup the bold indicator while testing for duplicates, however you took a different approach, which does not allow that.
So, your rng is a multiarea selection.
You have to get to the last area, then to the last row of that area, and then retrieve the actual row you're in. Then add +1 for the space between. Now you know how many rows are covered by the input section + the gap to the output and you offset your selection by this count onto the output section.
However, there might be complications, depenting on your input/output - I tested this briefly on your example - worked. Still, I think it would be better to use a different kind of loop & duplicate detection.

Resources