Apply formula to a range of cells - vba

I have some code which I know works, I am applying a SUMIF formula to a range of cells. It works but it add a load of extra row at the bottom that shouldn't be there. I tried adding in a do until loop but it gets stuck in an infinite loop and crashes.
This is my first lot of code which works but adds the extra row in only on the columns which have been copied over.
Dim z As Workbook 'Budget Workbook
Dim y As Workbook 'Formatted - current workbook
Dim lastRow As Integer
Dim budgLastRow As Integer
Dim rng As Range
Set y = Workbooks("DLT.xlsm")
Set z = Workbooks.Open("C:\Reports\Budget.xlsx")
'Apply function to columns to pull costing data
With y.Worksheets("DLT")
lastRow = Cells(Rows.Count, 5).End(xlUp).Row
For Each rng In .Range("AI22:AI" & lastRow)
rng.Formula = "=SUMIF('[Budget.xlsx]DynamicReport'!$C:$C,$E" & rng.Row & ",'[Budget.xlsx]DynamicReport'!H:H)"
rng.Value = rng.Value
Next rng
For Each rng In .Range("AJ22:AJ" & lastRow)
rng.Formula = "=SUMIF('[Budget.xlsx]DynamicReport'!$C:$C,$E" & rng.Row & ",'[Budget.xlsx]DynamicReport'!I:I)"
rng.Value = rng.Value
Next rng
For Each rng In .Range("AN22:AN" & lastRow)
rng.Formula = "=SUMIF('[Budget.xlsx]DynamicReport'!$C:$C,$E" & rng.Row & ",'[Budget.xlsx]DynamicReport'!E:E)"
rng.Value = rng.Value
Next rng
For Each rng In .Range("AO22:AO" & lastRow)
rng.Formula = "=SUMIF('[Budget.xlsx]DynamicReport'!$C:$C,$E" & rng.Row & ",'[Budget.xlsx]DynamicReport'!G:G)"
rng.Value = rng.Value
Next rng
End With
I think the other additional rows have been copied because the budget workbook contains more data then the formatted work book. I have know thought to possibly delete the other unnecessary row which have been copied cross.
So I have added this small piece of code in
With y.Worksheets("Formatted")
lastRow = Cells(Rows.Count, 5).End(xlUp).Row - 1
budgLastRow = Cells(Rows.Count, 35).End(xlUp).Row
Rows("AI" & lastRow & ":AO" & budgLastRow).EntireRow.Delete
End With
I get an application-defined error Object defined error on the line
Rows("AI" & lastRow & ":AO" & budgLastRow).EntireRow.Delete
This is probably not the most efficient way to do this, but its the only way I could think of. I am fairly new to VBA only been coding a couple of months so mostly just try out different ways and see what works. Can anyone help me please.

You didn't properly qualify the ranges for the lastRow variable:
lastRow = .Cells(.Rows.Count, 5).End(xlUp).Row
Note the dots before Cells and Rows.
A couple of additional points:
Always use Long rather than Integer for row variables as there are more rows in a sheet than an Integer can hold.
You don't need to loop to put the same formula in a column of cells.
Dim z As Workbook 'Budget Workbook
Dim y As Workbook 'Formatted - current workbook
Dim lastRow As Long
Dim budgLastRow As Long
Set y = Workbooks("DLT.xlsm")
Set z = Workbooks.Open("C:\Reports\Budget.xlsx")
'Apply function to columns to pull costing data
With y.Worksheets("DLT")
lastRow = .Cells(.Rows.Count, 5).End(xlUp).Row
With .Range("AI22:AJ" & lastRow)
.Formula = "=SUMIF('[Budget.xlsx]DynamicReport'!$C:$C,$E22,'[Budget.xlsx]DynamicReport'!H:H)"
.Value2 = .Value2
End With
With .Range("AN22:AN" & lastRow)
.Formula = "=SUMIF('[Budget.xlsx]DynamicReport'!$C:$C,$E22,'[Budget.xlsx]DynamicReport'!E:E)"
.Value2 = .Value2
End With
With .Range("AO22:AO" & lastRow)
.Formula = "=SUMIF('[Budget.xlsx]DynamicReport'!$C:$C,$E22,'[Budget.xlsx]DynamicReport'!G:G)"
.Value2 = .Value2
End With
End With

Related

Macro to copy-paste range in row to different sheets based on specific cell value

I have a workbook with 3 sheets: first one is the raw data sheet, then 2 target sheets. I would need a macro that would look at cell C in raw data sheet and based on the 2 values (YES or NO), will copy and paste the range A:Y in sheets 2, respectively 3.
Example: if on C2 in raw data sheet i have YES, copy A2:Y2 and paste into sheet 2, same range A2:Y2. If instead i have the value NO, copy A2:Y2 and paste into sheet 3.
Then go to next row and copy-paste A3:Y3 to sheet 2 if YES or A3:Y3 to sheet 3 if NO.
I wrote something that only works for the 2nd row, but i don't know how to make it loop... so basically when it passes to the next rows, it still copies the values from A2:Y2 to the target sheet, instead of copying A3:Y3, A4:Y4 etc..
Pasting my poor code below:
Sub IdentifyInfraction()
Dim rngA As Range
Dim cell As Range
Set rngA = Range("C2", Range("C65536").End(xlUp))
For Each cell In rngA
Worksheets("raw_data").Select
If cell.Value = "YES" Then
Range("A2:Y2").Copy
Worksheets("Value_YES").Select
Range("A2").PasteSpecial Paste:=xlPasteValues
ElseIf cell.Value = "NO" Then
Range("A2:Y2").Copy
Worksheets("Value_NO").Select
Range("A2").PasteSpecial Paste:=xlPasteValues
End If
Next cell
End Sub
Please help!!! :-s
Easiest solution would just be to replace the number 2 in each of your ranges to a variable which you then increment at the end your statement, before you go to the next cell.
For example:
Dim i = 2
Set rngA = Range("C2", Range("C65536").End(xlUp))
For Each cell In rngA
Worksheets("raw_data").Select
If cell.Value = "YES" Then
Range("A" & i & ":Y" & i).Copy
Worksheets("Value_YES").Select
Range("A" & i).PasteSpecial Paste:=xlPasteValues
ElseIf cell.Value = "NO" Then
Range("A" & i & ":Y" & i).Copy
Worksheets("Value_NO").Select
Range("A" & i).PasteSpecial Paste:=xlPasteValues
End If
i = i + 1
Next cell
So, originally we set i = 2, this is to go in line with your starting row of 2 mentioned in your question. Then, Range("A" & i & ":Y" & i).Copy is the same as saying Range("A2:Y2").Copy or Range("A3:Y3").Copy, etc.
This will go through any copy each row, a new row each time, and paste it to the respective row in the various sheets.
I hope this works for what you are trying to do, if not let me know.
There are a few things I'd also recommend looking into. There's a much better way to copy and paste, without going back and forward through the sheets.
ThisWorkbook.Sheets("raw_data").Rows(i).Copy Destination:=Worksheets("Value_YES").Range("A" & i)
Something like this would take the whole row from raw_data and transfer it to Value_YES. You'd have to mess around with it and change the range from Rows(i), but that's just an example.
I'd also recommend that you look into How to avoid using Select in Excel VBA to better understand why it's frowned upon to use Select and Activate in Excel VBA.
My version:
Sub GetR_Done()
Dim rng As Range, c As Range, LstRw As Long
Dim ws As Worksheet, Nr As Long, Yr As Long
Dim Ys As Worksheet, Ns As Worksheet
Set ws = Sheets("raw_data")
Set Ys = Sheets("Value_YES")
Set Ns = Sheets("Value_NO")
With ws
LstRw = .Cells(.Rows.Count, "C").End(xlUp).Row
Set rng = .Range("C2:C" & LstRw)
For Each c In rng.Cells
If c = "YES" Then
With Ys
Yr = .Cells(.Rows.Count, "A").End(xlUp).Row + 1
End With
.Range(.Cells(c.Row, "A"), .Cells(c.Row, "Y")).Copy Ys.Range("A" & Yr)
End If
If c = "NO" Then
With Ns
Nr = .Cells(.Rows.Count, "A").End(xlUp).Row + 1
End With
.Range(.Cells(c.Row, "A"), .Cells(c.Row, "Y")).Copy Ns.Range("A" & Nr)
End If
Next c
End With
End Sub
If you really require to paste values, then use this one
Sub GetR_Done()
Dim rng As Range, c As Range, LstRw As Long
Dim ws As Worksheet, Nr As Long, Yr As Long
Dim Ys As Worksheet, Ns As Worksheet
Set ws = Sheets("raw_data")
Set Ys = Sheets("Value_YES")
Set Ns = Sheets("Value_NO")
Application.ScreenUpdating = False
With ws
LstRw = .Cells(.Rows.Count, "C").End(xlUp).Row
Set rng = .Range("C2:C" & LstRw)
For Each c In rng.Cells
If c = "YES" Then
With Ys
Yr = .Cells(.Rows.Count, "A").End(xlUp).Row + 1
End With
.Range(.Cells(c.Row, "A"), .Cells(c.Row, "Y")).Copy
Ys.Range("A" & Yr).PasteSpecial xlPasteValues
End If
If c = "NO" Then
With Ns
Nr = .Cells(.Rows.Count, "A").End(xlUp).Row + 1
End With
.Range(.Cells(c.Row, "A"), .Cells(c.Row, "Y")).Copy
Ns.Range("A" & Nr).PasteSpecial xlPasteValues
End If
Next c
End With
Application.CutCopyMode = False
End Sub
you could try this:
Sub IdentifyInfraction()
Dim cell As Range
With Worksheets("raw_data") 'reference "raw data" sheet
For Each cell In .Range("C2", .cells(.Rows.Count, "C").End(xlUp)) ' loop through referenced sheet column C cells from row 2 down to last not empty one
Worksheets("Value_" & cell.Value).Range(cell.Address).Resize(, 25).Value = cell.Resize(, 25).Value 'have proper target sheet A:Y current cell row values as "raw data" sheet ones
Next
End With
End Sub

Only copy visible range in VBA?

I'm running into an issue where I'm unable to copy only visible cells to a new sheet. I'm able to get the lastrow, but I get #N/A on every cell except the first for each column. I want to just copy the visible cells. I'd also like to only put information on visible rows too, if possible?
Please see my code below:
Sub Importe()
lastRow = Worksheets("Sheet1").Cells(1, 1).SpecialCells(xlCellTypeVisible).End(xlDown).Row
Worksheets.Add
With ActiveSheet
Range("A1:A" & lastRow).Value2 = _
ActiveWorkbook.Worksheets("Sheet1").Range("H1:H" & lastRow).SpecialCells(xlCellTypeVisible).Value
Range("B1:B" & lastRow).Value2 = _
ActiveWorkbook.Worksheets("Sheet1").Range("E1:E" & lastRow).SpecialCells(xlCellTypeVisible).Value
End With
End Sub
Something like .Value2 = .Value doesn't work on special cells of type visible, because …
… e.g. if lastRow = 50 and there are hiddenRows = 10 then …
your source Range("H1:H" & lastRow).SpecialCells(xlCellTypeVisible)
has lastRow - hiddenRows = 40 rows
but your destination Range("A1:A" & lastRow).Value2
has lastRow = 50 rows.
On the first you subtract the visible rows, so they are different in size. Therefore .Value2 = .Value doesn't work, because you cannot fill 50 rows with only 40 source rows.
But what you can do is Copy and SpecialPaste
Option Explicit
Sub Importe()
Dim lastRow As Long
lastRow = Worksheets("Sheet1").Cells(1, 1).SpecialCells(xlCellTypeVisible).End(xlDown).Row
Worksheets.Add
With ActiveSheet
ActiveWorkbook.Worksheets("Sheet1").Range("H1:H" & lastRow).SpecialCells(xlCellTypeVisible).Copy
.Range("A1").PasteSpecial xlPasteValues
ActiveWorkbook.Worksheets("Sheet1").Range("E1:E" & lastRow).SpecialCells(xlCellTypeVisible).Copy
.Range("B1").PasteSpecial xlPasteValues
End With
End Sub
Nevertheless I recommend to avoid ActiveSheet or ActiveWorkbook if this is possible and reference a workbook eg by ThisWorkbook. My suggestion:
Option Explicit
Sub Importe()
Dim SourceWs As Worksheet
Set SourceWs = ThisWorkbook.Worksheets("Sheet1")
Dim DestinationWs As Worksheet
Set DestinationWs = ThisWorkbook.Worksheets.Add
Dim lastRow As Long
lastRow = SourceWs.Cells(1, 1).SpecialCells(xlCellTypeVisible).End(xlDown).Row
SourceWs.Range("H1:H" & lastRow).SpecialCells(xlCellTypeVisible).Copy
DestinationWs.Range("A1").PasteSpecial xlPasteValues
SourceWs.Range("E1:E" & lastRow).SpecialCells(xlCellTypeVisible).Copy
DestinationWs.Range("B1").PasteSpecial xlPasteValues
End Sub
To define whether a cell is visible or not, both its column and row should be visible. This means, that the .Hidden property of the column and the row should be set to False.
Here is some sample code of how to copy only the visible ranges between two worksheets.
Imagine that you have an input like this in Worksheets(1):
Then you manually hide column B and you want to get in Worksheets(2) every cell from the Range(A1:C4), without the ones in column B. Like this:
To do this, you should check each cell in the range, whether its column or row is visible or not.
A possible solution is this one:
Sub TestMe()
Dim myCell As Range
For Each myCell In Worksheets(1).Range("A1:C4")
If (Not Rows(myCell.Row).Hidden) And (Not Columns(myCell.Column).Hidden) Then
Dim newCell As Range
Set newCell = Worksheets(2).Cells(myCell.Row, myCell.Column)
newCell.Value2 = myCell.Value2
End If
Next myCell
End Sub
Just a general advise - whenever you use something like this Range("A1").Value2 = Range("A1").Value2 make sure that both are the same and not the left is Value2 and the right is .Value. It probably will not bring what you are expecting.
You cannot perform a direct value transfer without cycling though the areas of the SpecialCells(xlCellTypeVisible) collection.
Sometimes it is easier to copy everything and get rid of what you don't want.
Sub Importe()
Dim lr As Long
Worksheets("Sheet1").Copy after:=Worksheets("Sheet1")
With ActiveSheet
.Name = "xyz"
.Cells(1, 1).CurrentRegion = .Cells(1, 1).CurrentRegion.Value2
For lr = .Cells(.Rows.Count, "A").End(xlUp).Row To 1 Step -1
If .Cells(lr, "A").EntireRow.Hidden Then
.Cells(lr, "A").EntireRow.Delete
End If
Next lr
lr = .Cells(.Rows.Count, "A").End(xlUp).Row
.Cells(1, 1).CurrentRegion.Resize(lr, 1) = .Cells(1, 1).CurrentRegion.Resize(lr, 1).Offset(0, 7).Value2
.Cells(1, 1).CurrentRegion.Offset(0, 1).Resize(lr, 1) = .Cells(1, 1).CurrentRegion.Resize(lr, 1).Offset(0, 4).Value2
.Columns("C:XFD").EntireColumn.Delete
End With
End Sub
just to throw in an alternative version:
Sub Importe()
Dim sht1Rng As Range, sht1VisibleRng As Range
With Worksheets("Sheet1")
Set sht1Rng = .Range("A1", .Cells(.Rows.Count, 1).End(xlUp))
End With
Set sht1VisibleRng = sht1Rng.SpecialCells(xlCellTypeVisible)
With Worksheets.Add
.Range("A1").Resize(sht1Rng.Rows.Count).Value2 = sht1Rng.Offset(, 7).Value2
.Range("B1").Resize(sht1Rng.Rows.Count).Value2 = sht1Rng.Offset(, 4).Value2
.UsedRange.EntireRow.Hidden = True
.Range(sht1VisibleRng.Address(False, False)).EntireRow.Hidden = False
End With
End Sub
which may have the drawback of Address() maximum "capacity "

Combine specific columns (variable length) into one column - excel VBA

So the situation is that I would like to copy 4 columns with variable lengths into one column containing all the data from these 4. The problem is I loose data in the process.
As an example it should be 693 rows but I only get 648 rows all in all.
I'm relatively new to VBA and have come up with these lines of code.
Sub Copy()
Dim RngA As Range, RngB As Range, RngC As Range, RngD As Range, Rng As Range
Set RngA = Range(Range("I2"), Range("I" & Rows.Count).End(xlUp))
Set RngB = Range(Range("J2"), Range("J" & Rows.Count).End(xlUp))
Set RngC = Range(Range("K2"), Range("K" & Rows.Count).End(xlUp))
Set RngD = Range(Range("L2"), Range("L" & Rows.Count).End(xlUp))
Range("O2").Resize(RngA.Count).Value = RngA.Value
Range("O" & RngA.Count + 1).Resize(RngB.Count).Value = RngB.Value
Range("O" & RngB.Count + 1).Resize(RngC.Count).Value = RngC.Value
Range("O" & RngC.Count + 1).Resize(RngD.Count).Value = RngD.Value
With Sheets("Keywords")
Columns("O:O").Sort Key1:=.Range("=O1"), Order1:=xlAscending, Header:=xlYes
End With
End Sub
See my comment above, but here is an alternative approach using arrays which saves having to set up multiple similarly-named range variables.(Btw am assuming everything is on the Keywords sheet.)
Sub Copy()
Dim vRng(1 To 4) As Range, i As Long
With Sheets("Keywords")
For i = LBound(vRng) To UBound(vRng)
Set vRng(i) = .Range(.Cells(2, i + 8), .Cells(.Rows.Count, i + 8).End(xlUp))
Next i
For i = LBound(vRng) To UBound(vRng)
.Range("O" & Rows.Count).End(xlUp)(2).Resize(vRng(i).Count).Value = vRng(i).Value
Next i
.Columns("O:O").Sort Key1:=.Range("O1"), Order1:=xlAscending, Header:=xlYes
End With
End Sub

How to Copy and Paste Rows starting from a specific cell in another sheet

I want to copy all the rows from the Pipeline Report sheet that contain the text "AllScripts" in column T.
Then, I want to paste them onto another sheet starting from "A14" and go down.
Right now this code pastes them from "A2" but I need it to start from A14 and go down from there.
Sub extractAllscripts()
Dim myrange As Range
Dim lr as Long
Sheets("Pipeline Report").Select
Set myrange = Sheets("Pipeline Report").Range("T1", Range("T" & Rows.Count).End(xlUp))
For Each cell In myrange
If cell.Value = "Allscripts" Then
lr = Sheets("Macro Test Page").Range("T" & Rows.Count).End(xlUp).Row
cell.EntireRow.Copy Destination:=Sheets("Macro Test Page").Range("A" & lr + 1)
End If
Next cell
End Sub
This is a bad practice but as we don't know how your sheet is set up, then I would suggest this:
Sub extractAllscripts()
Dim myrange As Range
Dim lr as Long
Dim myoffset As Integer
Sheets("Pipeline Report").Select
Set myrange = Sheets("Pipeline Report").Range("T1", Range("T" & Rows.Count).End(xlUp))
myoffset = 13
For Each cell In myrange
If cell.Value = "Allscripts" Then
lr = Sheets("Macro Test Page").Range("T" & Rows.Count).End(xlUp).Row
cell.EntireRow.Copy Destination:=Sheets("Macro Test Page").Range("A" & lr + myoffset)
End If
myoffset = 1
Next cell
End Sub
Or another bad solution:
In your code substitute lr = Sheets("Macro Test Page").Range("T" & Rows.Count).End(xlUp).Row with lr = lr + 1 and define lr to be 13 outside of the loop and in the Range("A" & lr + 1) use lr instead of lr + 1.

Copy a row from one sheet to another using VBA

I have two sheets containing the employee records.
Sheet1 contains the Event Date, CardNo, Employee Name, Dept Id, Employee No, Entry and Exit Time, Total Working Hours, Status, ConcatinatedColumn and Remarks (copied through vlookup from sheet2)
Sheet2 contains ConcatinatedColumn, Event Date, Employee No, Name, Remarks.
If the data in the remarks column of sheet2 is "Sick Off" then that row should be inserted to sheet1 without effecting the previous records.
I've already written the code for it but it does not work.
Would be really grateful if anyone can help me out !
THANKS IN ADVANCE !
MY CODE :
Sub SickOff()
Dim objWorksheet As Sheet2
Dim rngBurnDown As Range
Dim rngCell As Range
Dim strPasteToSheet As String
'Used for the new worksheet we are pasting into
Dim objNewSheet As Sheet1
Dim rngNextAvailbleRow As Range
'Define the worksheet with our data
Set objWorksheet = ThisWorkbook.Worksheets("Sheet2")
'Dynamically define the range to the last cell.
'This doesn't include and error handling e.g. null cells
'If we are not starting in A1, then change as appropriate
Set rngBurnDown = objWorksheet.Range("G2:G" & objWorksheet.Cells(Rows.Count, "G").End(xlUp).Row)
'Now loop through all the cells in the range
For Each rngCell In rngBurnDown.Cells
objWorksheet.Select
If rngCell.Value = "Sick Off" Then
'select the entire row
rngCell.EntireRow.Select
'copy the selection
Selection.Copy
'Now identify and select the new sheet to paste into
Set objNewSheet = ThisWorkbook.Worksheets("Sheet1" & rngCell.Value)
objNewSheet.Select
'Looking at your initial question, I believe you are trying to find the next available row
Set rngNextAvailbleRow = objNewSheet.Range("A1:A" & objNewSheet.Cells(Rows.Count, "A").End(xlUp).Row)
Range("A" & rngNextAvailbleRow.Rows.Count + 1).Select
ActiveSheet.Paste
End If
Next rngCell
objWorksheet.Select
objWorksheet.Cells(1, 1).Select
'Can do some basic error handing here
'kill all objects
If IsObject(objWorksheet) Then Set objWorksheet = Nothing
If IsObject(rngBurnDown) Then Set rngBurnDown = Nothing
If IsObject(rngCell) Then Set rngCell = Nothing
If IsObject(objNewSheet) Then Set objNewSheet = Nothing
If IsObject(rngNextAvailbleRow) Then Set rngNextAvailbleRow = Nothing
End Sub
Let's say you have data in Sheet2 as shown below
Let's say the end of data in Sheet1 looks like this
Logic:
We are using autofilter to get the relevant range in Sheet2 which match Sick Off in Col G. Once we get that, we copy the data to the last row in Sheet1. After the data is copied, we simply shuffle data across to match the column headers. As you mentioned that the headers won't change so we can take the liberty of hardcoding the column names for shuffling this data.
Code:
Paste this code in a module
Option Explicit
Sub Sample()
Dim wsI As Worksheet, wsO As Worksheet
Dim lRow As Long, wsOlRow As Long, OutputRow As Long
Dim copyfrom As Range
Set wsI = ThisWorkbook.Sheets("Sheet1")
Set wsO = ThisWorkbook.Sheets("Sheet2")
'~~> This is the row where the data will be written
OutputRow = wsI.Range("A" & wsI.Rows.Count).End(xlUp).Row + 1
With wsO
wsOlRow = .Range("G" & .Rows.Count).End(xlUp).Row
'~~> Remove any filters
.AutoFilterMode = False
'~~> Filter G on "Sick Off"
With .Range("G1:G" & wsOlRow)
.AutoFilter Field:=1, Criteria1:="=Sick Off"
Set copyfrom = .Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow
End With
'~~> Remove any filters
.AutoFilterMode = False
End With
If Not copyfrom Is Nothing Then
copyfrom.Copy wsI.Rows(OutputRow)
'~~> Shuffle data
With wsI
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
.Range("A" & OutputRow & ":A" & lRow).Delete Shift:=xlToLeft
.Range("F" & OutputRow & ":F" & lRow).Copy .Range("K" & OutputRow)
.Range("F" & OutputRow & ":F" & lRow).ClearContents
.Range("B" & OutputRow & ":B" & lRow).Copy .Range("E" & OutputRow)
.Range("B" & OutputRow & ":B" & lRow).ClearContents
End With
End If
End Sub
Output: