How to set zero in the cells if the cells are empty using VBA - excel

I have an excel file with 3 sheets inside:
Starting Date and Ending Date in Sheet1 (1st screenshot)
A database in Sheet2 (2nd screenshot)
Filtered data in Sheet3 based on the date in Sheet1 (3rd screenshot)
I have a script that copy the data from Sheet2 to Sheet3 based on the date in Sheet1. I want the script to be able to put a zero in the empty space if it is detected instead of leaving it as blank.
For example, in the 2nd screenshot above, there is no value in Row 8 for Group 4 and Group 6, so I want the script to be able to put a zero in Group 4 and Group 6 for Row 8 when the data is copied to Sheet3.
I managed to achieve this but some how there will be zero in Row 9 as well (shown in the 3rd screenshot). There should be no zero in Row 9 since the date in Sheet1 is only from December-20 to February-21 (3 months). May I know how should I modified my script so that I'm able to eliminate the zero in Row 9? Any help will be greatly appreciated!
This is my script:
Sub extractdata()
Dim LastRow As Long, erow As Long, i As Long, j As Long, LastRow2 As Long
Dim mydate As Date
LastRow2 = Sheet3.Cells(Rows.Count, 2).End(xlUp).Offset(1, 0).Row
Sheet3.Range("B6:H" & LastRow2).ClearContents
LastRow = Sheet2.Cells(Rows.Count, 2).End(xlUp).Row
For i = 6 To LastRow
mydate = Sheet2.Cells(i, 2)
If mydate >= Sheet1.Cells(7, 19) And mydate <= Sheet1.Cells(8, 19) Then
erow = Sheet3.Cells(Rows.Count, 2).End(xlUp).Offset(1, 0).Row
Sheet2.Range(Sheet2.Cells(i, 2), Sheet2.Cells(i, 57)).Copy Destination:=Sheet3.Cells(erow, 2)
End If
For j = 3 To 8
If IsEmpty(Sheet3.Cells(i, j).Value) = True Then
Sheet3.Cells(i, j) = 0
End If
Next j
Next i
End Sub
Edit
Sub extractdata()
Dim LastRow As Long, erow As Long, i As Long, j As Long, LastRow2 As Long
Dim mydate As Date
LastRow2 = Sheet3.Cells(Rows.Count, 2).End(xlUp).Offset(1, 0).Row
Sheet3.Range("B6:H" & LastRow2).ClearContents
LastRow = Sheet2.Cells(Rows.Count, 2).End(xlUp).Row
For i = 6 To LastRow
mydate = Sheet2.Cells(i, 2)
If mydate >= Sheet1.Cells(7, 19) And mydate <= Sheet1.Cells(8, 19) Then
erow = Sheet3.Cells(Rows.Count, 2).End(xlUp).Offset(1, 0).Row
Sheet2.Range(Sheet2.Cells(i, 2), Sheet2.Cells(i, 57)).Copy Destination:=Sheet3.Cells(erow, 2)
End If
Next i
ZeroIsteadBlank
End Sub
I call the function after For loop, but this is what I get:
Row 9 still exist with bunch of zero with additional date in Cell B9

Please, use this code at the end. Iterating during the copying code is waste of time and Excel resources:
Sub ZeroIsnteadBlank()
Dim sh As Worksheet, rngEmpty As Range, LastRow2 As Long
Set sh = Sheet3
LastRow2 = sh.Cells(Rows.Count, 2).End(xlUp).Row + 1 '+ 1 for an extra row...
On Error Resume Next 'Otherwise, the following line will return an error in case of no empty cells...
Set rngEmpty = sh.Range("C6:H" & LastRow2).SpecialCells(xlCellTypeBlanks)
If Not rngEmpty Is Nothing Then
rngEmpty.value = 0
Else
MsgBox "No empty cells..."
End If
On Error GoTo 0
End Sub
In order to avoid the case of an empty cell of the column C:C (C8 in your example), it should be good to calculate LastRow2 for two columns and choose the bigger one...

Related

Run-Time Error '1004': Application Defined or Object-Defined Error

I am trying to copy and paste data from one sheet to the next from a selected range entered by the user. TxtDateStart takes the start date, and TxtDateEnd takes the end date. Then it would copy and paste the data from the range of dates to a new sheet.
When I run the code in a form, it works but I rather have the form call the module. This is where I get the run-time error. I'm no expert in VBA, help would be appreciated.
The sheet where the data is is called Unit2Data, and the sheet i want to paste the data is Graphing Sheet.
The error occurs in this line
Sheets("Unit2Data").Range(Cells(i, 1), Cells(i, 73)).Select
Sub Unit2Data()
Dim lrow As Long, i As Long, x As Date, y As Date, erow As Long
x = TxtDateStart
y = TxtDateEnd
'Find the Last Row of Sheet1
lrow = Sheets("Unit2Data").Range("A" & Rows.Count).End(xlUp).Row
'start counting from row 3 to last row
For i = 4 To lrow
' Date value converted as numeric value by multiplying with number 1
If Cells(i, 1) * 1 >= x * 1 Then
If Cells(i, 1) * 1 <= y * 1 Then
'If above conditions matched then select the matched range/ entire column
Sheets("Unit2Data").Range(Cells(i, 1), Cells(i, 73)).Select
'copy the selected row
Selection.Copy
'to make sheet2 active where we want to paste the selected row
Sheets("Graphing Sheet").Activate
'to find the empty row from where the copied row of sheet1 to be pasted in sheet2
erow = Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Row
'to activate or select the empty row of sheet2
ActiveSheet.Cells(erow, 1).Select
'paste the copied data
ActiveSheet.Paste
'to deselect the copy and selected mode
Application.CutCopyMode = False
'for above the if we need 3 end if to close if conditions
End If
End If
'to activate sheet1 for searching the matched data
Sheets("Unit2Data").Activate
'continue for look until above matched found
Next i
End Sub
Date Data
01/01/2019 2
02/02/2019 3
First you should avoid using Select in VBA. There, almost always, are better ways to achieve whatever you are using Select for.
In your case, and regarding only the specific error/question raised, delete the error-causing line and the next line (Selection.Copy) and replace with this:
With Sheets("Unit2Data")
.Range(.Cells(i, 1), .Cells(i, 73)).Copy
End With
Rewriting your entire code to avoid using Select:
Sub Unit2Data()
Dim lrow As Long, i As Long, x As Date, y As Date, erow As Long
x = TxtDateStart
y = TxtDateEnd
With Sheets("Unit2Data")
lrow = .Range("A" & .Rows.Count).End(xlUp).Row
For i = 4 To lrow
If .Cells(i, 1) * 1 >= x * 1 Then
If .Cells(i, 1) * 1 <= y * 1 Then
With Sheets("Graphing Sheet")
erow = .Cells(.Rows.Count, 1).End(xlUp).Offset(1, 0).Row
End With
.Range(.Cells(i, 1), .Cells(i, 73)).Copy _
Destination:= Sheets("Graphing Sheet").Cells(erow, 1)
End If
End If
Next i
End With
End Sub

Need help to optimize the Excel VBA code that aggregates duplicates

Below is my source table
Name Sales
---------------------------------
Thomas 100
Jay 200
Thomas 100
Mathew 50
Output I need is as below
Name Sales
---------------------------------
Thomas 200
Jay 200
Mathew 50
Basically, I have 2 columns that can have duplicates and I need to aggregate the second column based on first column.
Current code I have is as below. Its working perfectly fine. It takes around 45 seconds to run for 4500 records. I was wondering if there is a more efficient way to do this... as it seems to be a trivial requirement.
'Combine duplicate rows and sum values
Dim Rng As Range
Dim LngRow As Long, i As Long
LngLastRow = lRow 'The last row is calculated somewhere above...
'Initializing the first row
i = 1
'Looping until blank cell is encountered in first column
While Not Cells(i, 1).Value = ""
'Initializing range object
Set Rng = Cells(i, 1)
'Looping from last row to specified first row
For LngRow = LngLastRow To (i + 1) Step -1
'Checking whether value in the cell is equal to specified cell
If Cells(LngRow, 1).Value = Rng.Value Then
Rng.Offset(0, 1).Value = Rng.Offset(0, 1).Value + Cells(LngRow, 2).Value
Rows(LngRow).Delete
End If
Next LngRow
i = i + 1
Wend
Note that this is part of a larger excel app and hence I definitely need the solution to be in Excel VBA.
Here you go:
Option Explicit
Sub Consolidate()
Dim arrData As Variant
Dim i As Long
Dim Sales As New Scripting.Dictionary 'You will need the library Microsoft Scripting Runtime
Application.ScreenUpdating = False 'speed up the code since excel won't show you what is happening
'First of all, working on arrays always speeds up a lot the code because you are working on memory
'instead of working with the sheets
With ThisWorkbook.Sheets("YourSheet") 'change this
i = .Cells(.Rows.Count, 1).End(xlUp).Row 'last row on column A
arrData = .Range("A2", .Cells(i, 2)).Value 'here im assuming your row 1 has headers and we are storing the data into an array
End With
'Then we create a dictionary with the data
For i = 1 To UBound(arrData) 'from row 2 to the last on Q1 (the highest)
If Not Sales.Exists(arrData(i, 1)) Then
Sales.Add arrData(i, 1), arrData(i, 2) 'We add the worker(Key) with his sales(Item)
Else
Sales(arrData(i, 1)) = Sales(arrData(i, 1)) + arrData(i, 2) 'if the worker already exists, sum his sales
End If
Next i
'Now you have all the workers just once
'If you want to delete column A and B and just leave the consolidate data:
With ThisWorkbook.Sheets("YourSheet") 'change this
i = .Cells(.Rows.Count, 1).End(xlUp).Row 'last row on column A
.Range("A2:B" & i).ClearContents
.Cells(2, 1).Resize(Sales.Count) = Application.Transpose(Sales.Keys) 'workers
.Cells(2, 2).Resize(Sales.Count) = Application.Transpose(Sales.Items) 'Their sales
End With
Application.ScreenUpdating = True 'return excel to normal
End Sub
To learn everything about dictionaries (and more) check this
With data in cols A and B like:
Running this short macro:
Sub KopyII()
Dim cell As Range, N As Long
Columns("A:A").Copy Range("C1")
ActiveSheet.Range("C:C").RemoveDuplicates Columns:=1, Header:=xlNo
N = Cells(Rows.Count, "C").End(xlUp).Row
Range("B1").Copy Range("D1")
Range("D2:D" & N).Formula = "=SUMPRODUCT(--(A:A= C2),(B:B))"
End Sub
will produce this in cols C and D:
NOTE:
This relies on Excel's builtin RemoveDuplicates feature.
EDIT#1:
As chris neilsen points out, this function should be a bit quicker to evaluate:
Sub KopyIII()
Dim cell As Range, N As Long, A As Range, C As Range
Set A = Range("A:A")
Set C = Range("C:C")
A.Copy C
C.RemoveDuplicates Columns:=1, Header:=xlNo
N = Cells(Rows.Count, "C").End(xlUp).Row
Range("B1").Copy Range("D1") ' the header
Range("D2:D" & N).Formula = "=SUMIFS(B:B,A:A,C2)"
End Sub

I am looking for a code that selects the specified range in a row where column A has value of x

Ideally, what i need is to email the sepcified range rows with "x" in their column, i tried a code and it's working grand but the only problem is that it's sending 3 emails for 3 rows, but i want to send all the records in one email. I was able to do that using a vba code that converts the selected range in HTML and email it out. Now i want to automate the selection part.So basically i need to select the specified range of any row which have "x" in first column.
I tried a code but it's only selecting the last row that contains "x".
Link for the image showing what i am exactly looking for - https://imgur.com/a/Zq97ukL
Sub Button3_Click()
Dim lRow As Integer
Dim i As Integer
Dim toDate As Date
Dim Sheets As Worksheet
Dim r1 As Range
lRow = Cells(Rows.Count, 2).End(xlUp).Row
For i = 2 To lRow
If (Cells(i, 1)) = "x" Then 'change this range
Set r1 = Range(Cells(i, 2), Cells(i, 4))
r1.Select
On Error Resume Next
On Error GoTo 0
Cells(i, 10) = "Selected " & Date + Time 'column J
End If
Next i
ActiveWorkbook.Save
End Sub
jsheeran posted the bulk of this, but deleted his answer because it wasn't finished.
As previously stated, there is probably no reason to select anything.
Sub Button3_Click()
Dim lRow As Long
Dim i As Long
Dim toDate As Date
Dim Sheets As Worksheet
Dim r1 As Range
lRow = Cells(Rows.Count, 2).End(xlUp).Row
For i = 2 To lRow
If Cells(i, 1).Value = "x" Then
Set r1 = Union(IIf(r1 Is Nothing, Cells(i, 2).Resize(, 3), r1), Cells(i, 2).Resize(, 3))
Cells(i, 10).Value = "Selected " & Date + Time
End If
Next i
If Not r1 Is Nothing Then r1.Select
ActiveWorkbook.Save
End Sub

Assistance with slow VBA

This code has taken a couple of hours so far and less than 10% complete, please advise how I can make this faster?
I have tried to explain the code by using comments in the code
Sheet1 has nearly 500k rows on dates, from 1 July 1990 to 30/6/2017 in 30 minute intervals, i.e. 48 rows per day.
I have a table on a different worksheet with a row of 12 columns displaying months 7 to 6, then below the month number in 3 rows are 3 different years
Sub Test2()
Application.ScreenUpdating = False
'Sheet1 contains the main data set
'Sheet3 contains a table with 12 columns and 3 rows
'Sheet2 is an output sheet
'Sheet4 is an output sheet
'Sheet5 is an output sheet
Dim i As Long 'main sheet rows (Sheet1 473,379 rows)
Dim j As Long 'Columns (Table of dates with 12 columns on sheet3)
Dim LastRowMain As Long 'Last row of sheet 1
Dim LastRowStitch As Long 'Lastrow of the applicable output sheet
Dim Yr As Integer
Dim Mnth As Integer
LastRowMain = Sheet1.Cells(Rows.Count, 1).End(xlUp).Row '473,379 rows
j = 3 'First data column in the table
Do Until j = 14
For i = 4 To LastRowMain
'Sheet1 column(1) is Date format in 1/2 hour intervasls, i.e. 48 rows per day
Yr = Year(Sheet1.Cells(i, 1))
Mnth = Month(Sheet1.Cells(i, 1))
If Yr = Sheet3.Cells(2, j) And Mnth = Sheet3.Cells(1, j) Then 'If the Date in Sheet1.column(1) matches the date and month in the table, Then
LastRowStitch = Sheet2.Cells(Rows.Count, 1).End(xlUp).Row
Sheet1.Range(Sheet1.Cells(i, 1), Sheet1.Cells(i, 8)).Copy Sheet2.Cells(LastRowStitch + 1, 1) 'Copy that row and put in in to the output sheet below the lastrow
End If
If Yr = Sheet3.Cells(3, j) And Mnth = Sheet3.Cells(1, j) Then 'If the Date in Sheet1.column(1) matches the date and month in the table, Then
LastRowStitch = Sheet4.Cells(Rows.Count, 1).End(xlUp).Row
Sheet1.Range(Sheet1.Cells(i, 1), Sheet1.Cells(i, 8)).Copy Sheet4.Cells(LastRowStitch + 1, 1) 'Copy that row and put in in to the output sheet below the lastrow
End If
If Yr = Sheet3.Cells(4, j) And Mnth = Sheet3.Cells(1, j) Then 'If the Date in Sheet1.column(1) matches the date and month in the table, Then
LastRowStitch = Sheet5.Cells(Rows.Count, 1).End(xlUp).Row
Sheet1.Range(Sheet1.Cells(i, 1), Sheet1.Cells(i, 8)).Copy Sheet5.Cells(LastRowStitch + 1, 1) 'Copy that row and put in in to the output sheet below the lastrow
End If
Next i
Loop 'Go to the next set of dates in the table and loop through the rows again
Application.ScreenUpdating = True
MsgBox "Done"
End Sub
Many thanks
Refactoring a bit:
Dim YrToCompare2 As Long, YrToCompare4 As Long, YrToCompare5 As Long
Dim MnthToCompare as Long
Dim LastRow2 As Long, LastRow4 As Long, LastRow5 As Long
LastRow2 = Sheet2.Cells(Rows.Count, 1).End(xlUp).Row
LastRow4 = Sheet4.Cells(Rows.Count, 1).End(xlUp).Row
LastRow5 = Sheet5.Cells(Rows.Count, 1).End(xlUp).Row
Do Until j = 14
MnthToCompare = Sheet3.Cells(1, j)
YrToCompare2 = Sheet3.Cells(2, j)
YrToCompare4 = Sheet3.Cells(3, j)
YrToCompare5 = Sheet3.Cells(4, j)
For i = 4 To LastRowMain
'Sheet1 column(1) is Date format in 1/2 hour intervasls, i.e. 48 rows per day
Yr = Year(Sheet1.Cells(i, 1))
Mnth = Month(Sheet1.Cells(i, 1))
If Yr = YrToCompare2 And Mnth = MnthToCompare Then 'If the Date in Sheet1.column(1) matches the date and month in the table, Then
LastRow2 = LastRow2 + 1
Sheet1.Range(Sheet1.Cells(i, 1), Sheet1.Cells(i, 8)).Copy Sheet2.Cells(LastRow2, 1) 'Copy that row and put in in to the output sheet below the lastrow
End If
If Yr = YrToCompare4 And Mnth = MnthToCompare Then 'If the Date in Sheet1.column(1) matches the date and month in the table, Then
LastRow4 = LastRow4 + 1
Sheet1.Range(Sheet1.Cells(i, 1), Sheet1.Cells(i, 8)).Copy Sheet4.Cells(LastRow4, 1) 'Copy that row and put in in to the output sheet below the lastrow
End If
If Yr = YrToCompare5 And Mnth = MnthToCompare Then 'If the Date in Sheet1.column(1) matches the date and month in the table, Then
LastRow5 = LastRow5 + 1
Sheet1.Range(Sheet1.Cells(i, 1), Sheet1.Cells(i, 8)).Copy Sheet5.Cells(LastRow5, 1) 'Copy that row and put in in to the output sheet below the lastrow
End If
Next i
j = j + 1
Loop 'Go to the next set of dates in the table and loop through the rows again
The concept is to reduce VBA - Excel interactions by storing relatively fixed values in variables instead of reading them from Excel 12*500K times, and also counting last rows instead of finding them in every loop. However, you can expect significant improvement by implementing #QHarr's advise on using arrays.
Aside from ScreenUpdating and EnableEvents, you can also set the Calculation to manual before running your code. Normally, Excel will automatically recalculate a cell or a range of cells when that cell's or range's precedents have changed.
Application.Calculation = xlCalculationManual
Then once the loop is done, turn it on again:
Calculate
Application.Calculation = xlAutomatic

VBA- Copying Values from one cell to another offset cell

I am trying to go through row 6 and from column 1 to 26 and search for the sentence Earned Cumulative Hours. Once that is done then I am trying to go from row 8 to the last row(30 in this case) for the column that has Earned Cumulative Hours in row 6.
Then I am trying to paste the values of the cells from this column to 2 cells left in the same row. But I keep getting errors and the code doesn't work.
Can someone please point me in the right direction ? Thanks
Sub project()
Dim lastrow As Long
Dim i As Long
Dim j As Long
lastrow = Sheets("Progress").Cells(Rows.Count, 26).End(xlUp).Row
For j = 1 To 26
If Cells(6, j) = "Earned Cumulative Hours" Then
For i = 8 To lastrow
Cells(i, j).Copy
Cells(i, j).Offset(0, -2).Select
Selection.PasteSpeical Paste:=xlPasteValues
Next i
End If
Next j
End Sub
There are a few problems I can see straight away with your code. Firstly if you are offsetting back two columns .Cells(i, j).Offset(0, -2) then you will be overwriting existing values. If this is what you intend to do then weird but ok.
The next issue is that you have a problem if 'Earned Cumulative Hours' is in Column A. If this is your case Excel will be most unhappy trying to offset two columns to the left and will give an error.
In this case instead of copying and pasting it will be more efficient to set values in one column to the other which you can see in my code. Finally, your Cell references will be valid for the active sheet only. You need to qualify what worksheet you interest in as shown in my code. I normally put this at the start of the code if it is a self contained block.
You could also eliminate the i loop and set ranges of values at a time but we'll save that for next time!
I haven't test this code but it should be fine.
Sub projectawesome()
Dim lastrow as Long, i as Long, j as Long
'Qualify the sheet (assuming its in the activeworkbook)
With Sheets("Progress")
lastrow = .Cells(.Rows.Count, 26).End(xlUp).Row
'I've changed this to column three to prevent offset errors.
For j = 3 to 26
If .Cells(6, j) = "Earned Cumulative Hours" Then
For i = 8 to lastrow
'Assuming overwriting data is ok.
'No need to copy and paste
.Cells(i, j - 2).Value = .Cells(i, j).Value
Next i
End If
Next
End With
End Sub
Try this and we can get rid of those selects
Sub project()
Dim lastrow As Long
Dim i As Long
Dim j As Long
lastrow = Sheets("Progress").Cells(Rows.Count, 26).End(xlUp).Row
For j = 1 To 26
If Cells(6, j) = "Earned Cumulative Hours" Then
For i = 8 To lastrow
Cells(i, j).Copy
With Cells(i, j)
.Offset(0, -2).PasteSpecial xlPasteValues
End With
Next i ' next row
End If
Next j ' next col
End Sub

Resources