I am working with this macro that will look at a block of transactions, insert 3 rows between months, and then add the month and subtotal. The issue is that the break and totals are getting inserted at the beginning of the month instead of the end.
I have tried to adjust the shift but it either ends up giving me an error or the total ends up overriding an existing cell instead of going into a new row. This is a more complex macro than I have worked with before and I'm a little lost now, still working on learning VBA.
Option Explicit
Sub AddAndSum()
On Error GoTo lblError
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim shData As Worksheet, wbData As Workbook
Dim fr As Long, lr As Long, i As Long, lr2 As Long
Dim intMonth As Long, intYear As Long
Set wbData = ThisWorkbook
Set shData = wbData.Sheets("Sheet1")
fr = 13
lr = shData.Rows.Count
For i = fr To lr
With shData
If (IsDate(.Cells(i, 3).Value) And IsDate(.Cells(i - 1, 3).Value) And Month(.Cells(i, 3).Value) <> Month(.Cells(i - 1, 3).Value)) Or i = fr Then
intMonth = Month(.Cells(i, 3).Value)
intYear = Year(.Cells(i, 3).Value)
.Rows(i & ":" & i + 2).Insert Shift:=xlDown
.Cells(i + 1, 1).Value = "Monthly Total (" & MonthName(intMonth) & ")"
.Cells(i + 1, 2).Formula = "=SUMPRODUCT((MONTH($C$" & fr & ":$C$" & lr & ")=" & intMonth & ")*(YEAR($C$" & fr & ":$C$" & lr & ")=" & intYear & ")*$E$" & fr & ":$E$" & lr & ")"
i = i + 3
End If
End With
Next i
lblError:
If Err.Number <> 0 Then
MsgBox "Error (" & Err.Number & "): " & Err.Description, vbOKOnly + vbCritical
End If
GoTo lblExit
lblExit:
Application.DisplayAlerts = True
Application.ScreenUpdating = True
Application.Calculate
Application.Calculation = xlCalculationAutomatic
Exit Sub
End Sub
This line begins the insertion at Row i.
.Rows(i & ":" & i + 2).Insert Shift:=xlDown
You want to begin the insertion at row i+3, and you can accomplish that with the Offset method:
.Rows(i & ":" & i + 2).Offset(3).Insert Shift:=xlDown
You may also want to see this answer regarding best way of getting the "last row" in a column:
Error in finding last used cell in VBA
As you're currently doing lr = shData.Rows.Count that is 65,336 rows in Excel 2003, or 1,048,576 rows in Excel 2007+ and you almost certainly do not have that many data (otherwise an Insert would fail!), so your loop is cycling needlessly over a bunch of empty rows.
You need to change this row:
intMonth = Month(.Cells(i, 3).Value)
to
intMonth = Month(.Cells(i-1, 3).Value)
At the moment it is setting intMonth to the value of the current cell (which is the first cell of the next month) instead of the value of the previous cell (which contains the month you want to subtotal).
Then add a condition into your loop to add the last subtotal.
Also:
If (IsDate(.Cells(i, 3).Value) And IsDate(.Cells(i - 1, 3).Value) And Month(.Cells(i, 3).Value) <> Month(.Cells(i - 1, 3).Value)) Or i = fr Then
Should this be i = lr ? as you are checking for the last line in the sheet? At the moment will always put a subtotal after the first line. You'll need to update this value when you add the three subtotal lines in as well.
Related
In my code there is a searching order and it does as folloing:
It takes each value (about 2000 ranges) in ws.sheet range A and looks it up in another sheet named wp.sheet range A (about 90 ranges). If a particular value x in ws.sheet range e.g A3 is not found in wp.sheet range A the next search order in sheet ws.sheet is the value y in the next range B3 (same row as value x) to be searched in sheet wp.sheet in the entire range B, and so on.
This is what my "for" loop does and the issue with my code is that it takes very long as it compares each value in ws.sheet range A1-2000 to the values in wp.sheet range A1-90. Is there an alternative which does it more quickly or more efficiently?
Dim wb As Workbook, wq As Object
Dim ws, wi As Worksheet, datDatum
Dim w As Long, I As Long, t As Long
Dim DefaultMsgBox()
Dim r, i As Integer
For r = 2 To 2000
Check = True:
For i = 1 To 90
If ws.Range("A" & r).Value = wp.Sheets("ABC").Range("A" & i).Value Then
wp.Sheets("ABC").Rows(i).Columns("E:AB").Copy
ws.Range("G" & r).PasteSpecial
GoTo NextR
End If
Next i
For i = 1 To 90
If ws.Range("B" & r).Value = wp.Sheets("ABC").Range("B" & i).Value Then
wp.Sheets("ABC").Rows(i).Columns("E:AB").Copy
ws.Range("G" & r).PasteSpecial
GoTo NextR
End If
Next i
For i = 1 To 90
If ws.Range("C" & r).Value = wp.Sheets("ABC").Range("C" & i).Value And ws.Range("D" & r).Value = wp.Sheets("ABC").Range("D" & i).Value Then
wp.Sheets("ABC").Rows(i).Columns("E:AB").Copy
ws.Range("G" & r).PasteSpecial
GoTo NextR
End If
Next i
NextR:
If Not Check = ws.Range("A" & r).Value = wp.Sheets("ABC").Range("A" & i).Value Or Not Check = ws.Range("B" & r).Value = wp.Sheets("ABC").Range("A" & i).Value Or Not Check = ws.Range("C" & r).Value = wp.Sheets("ABC").Range("C" & i).Value And ws.Range("D" & r).Value = wp.Sheets("ABC").Range("D" & i).Value Then
MsgBox "......"
End If
Next r
End sub
I would suggest turning off ScreenUpdating and using the Find function instead:
Dim cell, foundValue, lookupRange As Range
Set wp = ThisWorkbook.Sheets("ABC")
Set ws = ThisWorkbook.Sheets("WS")
r = 2
number_r = 2000
ru = 1
number_ru = 90
Application.ScreenUpdating = False
'Loop through each cell in WS, offsetting through columns A to C
For Each cell In ws.Range("A" & r & ":A" & number_r)
For i = 0 To 2
'Define range to look up in ABC
Set lookupRange = wp.Range(wp.Cells(ru, i + 1), wp.Cells(number_ru, i + 1))
'Look for current WS cell on corresponding column in ABC
Set foundValue = lookupRange.Find(cell.Offset(0, i).Value)
'If cell is found in ABC...
If Not foundValue Is Nothing Then
Select Case i
Case 2 'If found cell is in column C
Do 'Lookup loop start
'If same values on columns D...
If foundValue.Offset(0, 1).Value = cell.Offset(0, 3).Value Then
'Copy data to WS and switch to the next cell
wp.Rows(foundValue.Row).Columns("E:AB").Copy
ws.Range("G" & cell.Row).PasteSpecial
GoTo nextCell
'If not same values on columns D...
Else
'Try to find next match, if any
Set foundValue = lookupRange.FindNext(foundValue)
If foundValue Is Nothing Then GoTo noMatchFound
End If
Loop 'Repeat until WS values in column C and D match ABC values in columns C and D
Case Else 'If found cell is in column A or B
'Copy data to WS and switch to the next cell
wp.Rows(foundValue.Row).Columns("E:AB").Copy
ws.Range("G" & cell.Row).PasteSpecial
GoTo nextCell
End Select
End If
Next i
noMatchFound:
MsgBox "......" 'Message appears only when no match was found in column A, column B and column C + D
nextCell:
Next cell
Application.ScreenUpdating = True
I hope you don't mind my saying so, but your code is hard to follow, including your choice of variable names. I can recommend that if you do not make use of your .copy statements, then comment them out and your code will run much faster.
I'm kind of struggeling with VBA for excel. I have a table with products, where products can have multiple categories. The categories that are linked to a product can have sub-categories, which are located in the columns next to it. If a product has multiple categories, these categories are located one row below the product. See pic1.
What I want to achieve:
Every time I execute the script, the current categories that are on the row of the product info need to be replaced with the categories below it, until you reach the next product. If there is no new category to replace, the product row can be deleted. (In this example I need to run the script 3 times). So I eventually will end up with this:
Run script first time:
Run script second time:
Run script 3rd time:
The code I've got so far is:
Sub MoveEmpty()
Dim i as Long, j as Long
Application.ScreenUpdating = False
j = Range("A" & Rows.Count).End(xlUp).Row
For i = j to 3 Step -1
If Range("A" & i) <> "" Then
Range("C" & i -1) = Range("C" & i).Resize(,3)
Range("A" & i).EntireRow.Delete
End If
Next i
End Sub
Hope this makes sense, and thanks for helping out,
Bart
You were on the right track, this should do what you want:
Sub MoveEmpty()
Dim i As Long, j As Long
Dim ws As Worksheet
Application.ScreenUpdating = False
' Set this appropriately
Set ws = ThisWorkbook.Worksheets("MyWorksheet")
j = ws.Range("A" & Rows.Count).End(xlUp).Row
For i = j To 3 Step -1
If ws.Range("A" & i) <> "" Then
' Copy the product name to be next to the 2nd category set down, if there is a category
If ws.Range("A" & (i + 1)) = "" And ws.Range("C" & (i + 1)) <> "" Then
' If you just want the values (i.e. no formatting copied)
ws.Range("A" & (i + 1)).Resize(, 2).Value = ws.Range("A" & i).Resize(, 2).Value
' If you want everything, including formats
Call ws.Range("A" & i).Resize(, 2).Copy(ws.Range("A" & (i + 1)).Resize(, 2))
End If
ws.Range("A" & i).EntireRow.Delete
End If
Next i
' Reset the screen to updating
Application.ScreenUpdating = True
End Sub
I have a workbook with 2 sheets.
Sheet1:
Column B Column C Column D Column E
Dairy Crest Ltd
Milk Farm
Tuna Family
Guiness
Sheet 2:
Column A Column B Column C Column d
Dairy Crest James james#email.com 07874565656
Milk Farm Limited Kelly kely#email.com 07874565656
Tuna's Families Dave dave#email.com 07874565656
Guiness Prep Limited Tom tom#email.com 07874565656
I want to match the similar named companies. This can't be a case of saying if value = value because the company name is usually spelt different.
Instead i want to use like or wildcard. Would this work?
If i use Value Like Value this doesn't seem to work.
Where found, i want to copy the contact name, email and contact number over to sheet 1 in the relevant columns.
For some reason this is not working. Please can someone show me where i am going wrong?
Relevant code:
'Start second loop sequence
With ThisWorkbook.Worksheets(3)
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
j2 = 2
For i2 = 1 To LastRow
' === For DEBUG ONLY ===
Debug.Print ThisWorkbook.Worksheets(2).Range("B" & j2).Value
If ThisWorkbook.Worksheets(2).Range("B" & j2).Value = .Range("A" & i2).Value Then ' check if Week No equals the value in "A1"
ThisWorkbook.Worksheets(2).Range("C" & j2).Value = .Range("B" & i2).Value
ThisWorkbook.Worksheets(2).Range("D" & j2).Value = .Range("D" & i2).Value
ThisWorkbook.Worksheets(2).Range("E" & j2).Value = .Range("C" & i2).Value
j2 = j2 + 1
End If
Next i2
End With
'End Second Loop
Full COde:
Option Explicit
Sub LoadWeekAnnouncementsFromPlanner()
Dim WB As Workbook
Dim WB2 As Workbook
Dim i As Long
Dim i2 As Long
Dim j As Long
Dim j2 As Long
Dim LastRow As Long
Dim ws As Worksheet
'Open Planner
'On Error Resume Next
Set WB = Workbooks("2017 Planner.xlsx")
On Error GoTo 0
If WB Is Nothing Then 'open workbook if not open
Set WB = Workbooks.Open("G:\BUYING\Food Specials\2. Planning\1. Planning\1. Planner\8. 2017\2017 Planner.xlsx", xlUpdateLinksNever, True, Password:="samples")
End If
'Open PhoneBook
'On Error Resume Next
'On Error GoTo 0
' ======= Edit #2 , also for DEBUG ======
With WB.Worksheets(1)
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
j = 2
For i = 1 To LastRow
' === For DEBUG ONLY ===
Debug.Print CInt(ThisWorkbook.Worksheets(1).Range("I8").Value)
If CInt(ThisWorkbook.Worksheets(1).Range("I8").Value) = .Range("A" & i).Value Then ' check if Week No equals the value in "A1"
ThisWorkbook.Worksheets(2).Range("A" & j).Value = .Range("A" & i).Value
ThisWorkbook.Worksheets(2).Range("B" & j).Value = .Range("N" & i).Value
ThisWorkbook.Worksheets(2).Range("H" & j).Value = .Range("K" & i).Value
ThisWorkbook.Worksheets(2).Range("I" & j).Value = .Range("L" & i).Value
ThisWorkbook.Worksheets(2).Range("J" & j).Value = .Range("M" & i).Value
ThisWorkbook.Worksheets(2).Range("K" & j).Value = .Range("G" & i).Value
ThisWorkbook.Worksheets(2).Range("L" & j).Value = .Range("O" & i).Value
ThisWorkbook.Worksheets(2).Range("M" & j).Value = .Range("P" & i).Value
ThisWorkbook.Worksheets(2).Range("N" & j).Value = .Range("W" & i).Value
ThisWorkbook.Worksheets(2).Range("O" & j).Value = .Range("Z" & i).Value
'Start second loop sequence
With ThisWorkbook.Worksheets(3)
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
j2 = 2
For i2 = 1 To LastRow
' === For DEBUG ONLY ===
Debug.Print ThisWorkbook.Worksheets(2).Range("B" & j2).Value
If ThisWorkbook.Worksheets(2).Range("B" & j2).Value = .Range("A" & i2).Value Then ' check if Week No equals the value in "A1"
ThisWorkbook.Worksheets(2).Range("C" & j2).Value = .Range("B" & i2).Value
ThisWorkbook.Worksheets(2).Range("D" & j2).Value = .Range("D" & i2).Value
ThisWorkbook.Worksheets(2).Range("E" & j2).Value = .Range("C" & i2).Value
j2 = j2 + 1
End If
Next i2
End With
'End Second Loop
j = j + 1
End If
Next i
End With
End Sub
Please can someone show me where i am going wrong?
This is a good example how to use Like in VBA. Try it in the console window, to get the answers.
?"Vito6" Like "V?to6"
True
?"Vito6" Like "Vito#"
True
?"Vito6" Like "V*6"
True
?"Vito6" Like "Vit[a-z]6"
True
?"Vito6" Like "Vit[A-Z]6"
False
?"Vito6" Like "Vit[!A-Z]6"
True
?"12 34" Like "## ##"
True
?"12 34" Like "1[0-9] [0-9]4"
True
If there's not a specific reason you need VBA, you could apply the solution that #Cyril gave in his comment to an Excel cell formula on Sheet1.
For example, in Sheet1, Cell F1, you can input:
=LEFT(B1, 4)
'This would return "Dair"
Then, in column A, you could use a nested IF statement:
=IF(F1 = "dair", "Dairy Crest", IF(F1 = "milk", "Milk Farm Limited, IF(F1 = "tuna", "Tuna's Families", IF(F1 = "Guiness", "Guiness Prep Limited", "No match))))
Will try to elaborate on my thought from the comment I left you:
Dim asdf as String
Dim i as Variant
Dim LR as Long
LR = Sheets("Sheet2").Cells(.Rows.Count, "A").End(xlUp).Row
For i = 2 to LR 'Sheet1 looks to start on row 3, while Sheet2 looks to start on row2
asdf = Sheets("Sheet1").Cells(i+1,2).Value
If Sheets("Sheet2").Cells(i,1).Value Like "*asdf*" Then 'you left out the asterisks
'true: copy data
Else:
'false: can just be nothing here
End If
Next i
Something similar to that is what I was suggesting. Utilized like operator, as suggested by #DougCoats.
While you can use wildcards to compare strings using the like operator , the explicit parts need to be exact. So
"*Dairy Crest*" like "Dairy Crest Ltd" will work nicely
but "*Tuna Family*" like "Tuna's Families" will not work.
You can try fuzzy lookup for matching for the 2nd scenario. It employs probability into the lookup.
Here is the link to the source code.
https://www.mrexcel.com/forum/excel-questions/195635-fuzzy-matching-new-version-plus-explanation.html
Just one note for fuzzy matching with probability, the matching may not be 100% correct if you set the accuracy % too low. If accuracy is important, then set the accuracy % higher.
I'm new to VBA and would like to learn it and get my file working. So if someone can help me with my code that would be great and it would be really nice if you can also explain in brief what a line does.
My issue: I have demand history of around 5000 products.
Articlecode: 60012
Date: 19-4-2014
Demand: -1
What I would like to do is the following:
Commbine two rows and sum the demand if:
article codes are the same and
date is the same
Checking the conditions and copying an entire row works. My code:
Sub AddDuplicateDates()
'Define variables lastrow
Dim i, Lastrow
Lastrow = Sheets("Data").Range("A" & Rows.Count).End(xlUp).Row
'Loop trough data and combine if date is the same
For i = 2 To Lastrow
If Sheets("Data").Cells(i, "A").Value = Sheets("Data").Cells(i - 1, "A").Value Then
If Sheets("Data").Cells(i, "B").Value = Sheets("Data").Cells(i - 1, "B").Value Then
'Here I would like to copy cell Sheet("Data").Cells(i, "C") and add this
'to Sheet("Datacorrected").Cells(i,"C") thus offset(0). How does this work?
Else
Sheets("Data").Cells(i, "A").EntireRow.Copy Destination:=Sheets("DataCorrected").Range("A" & Rows.Count).End(xlUp).Offset(1)
End If
End If
Next i
End Sub
Select the latest and earliest date
I already managed to select only unique values (article codes) to a different sheet: Sheet(Results)
I already found some topics, but unfortunately all these operate on the same worksheet and don't have a criteria to be met (Article code). For example: vba, excel : returning the earliest date value in this array of string . The problem is that I'm new with VBA and don't understand all the code, so I don't get how it works so i can translate it to my problem. Can some one help me with this or point out where i can find a clear explanation?
Does this have to be done in VBA? or would reporting on the data using a pivot table meet the need? Code isn't always the answer...
This code is in the click event of a command button. You can change it to some other sub if needed.
In you VBA IDE go to the tools menu and selecte references. Select "Microstoft ActiveX data objects 2.8 Library.
This assumes your first list is on sheet 1. It will write data out to sheet 2.
Private Sub CommandButton1_Click()
Dim dEarliest As Date
Dim dLatest As Date
Dim lLastCode As Long
Dim ws1 As Excel.Worksheet
Dim ws2 As Excel.Worksheet
Set ws1 = ActiveWorkbook.Sheets("Sheet1")
Set ws2 = ActiveWorkbook.Sheets("Sheet2")
Dim rs As New ADODB.Recordset
Dim lRow As Long
'Add fields to your recordset for storing data. You can store sums here.
With rs
.Fields.Append "Articlecode", adBigInt
.Fields.Append "Date", adDate
.Fields.Append "Demand", adInteger
.Open
End With
lRow = 1
ws1.Activate
'Loop through and record what is in the columns
Do While lRow <= ws1.UsedRange.Rows.count
'Filter to see if we already have this Articlecode and date
rs.Filter = ""
rs.Filter = "Articlecode=" & Trim(str(ws1.Range("A" & lRow).Value)) & " AND Date='" & Trim(str(ws1.Range("B" & lRow).Value)) & "'"
If rs.RecordCount = 1 Then
'If we find that we already have this record, add the demand to the current record.
rs.Fields("Demand").Value = rs.Fields("Demand").Value + ws1.Range("C" & lRow).Value
rs.Update
Else
'If we don't have this # and date, create a new record.
rs.AddNew
rs.Fields("Articlecode").Value = ws1.Range("A" & lRow).Value
rs.Fields("Date").Value = ws1.Range("B" & lRow).Value
rs.Fields("Demand").Value = ws1.Range("C" & lRow).Value
rs.Update
End If
lRow = lRow + 1
ws1.Range("A" & lRow).Activate
Loop
rs.Filter = ""
rs.Sort = "Articlecode, Date"
ws2.Activate
lRow = 1
'Here we loop through the data we collected and write it out.
Do While rs.EOF = False
ws2.Range("A" & lRow).Value = rs.Fields("Articlecode").Value
ws2.Range("B" & lRow).Value = Format(rs.Fields("Date").Value, "mm/dd/yyyy")
ws2.Range("C" & lRow).Value = rs.Fields("Demand").Value
lRow = lRow + 1
rs.MoveNext
Loop
'Now let's find the earliest and latest dates for each Articlecode
ws2.Range("E1").Value = "Articlecode"
ws2.Range("F1").Value = "Earliest"
ws2.Range("G1").Value = "Latest"
lRow = 1
rs.MoveFirst
Do While rs.EOF = False
If rs.Fields("Articlecode").Value <> lLastCode Then
'The first time we have an Articlecode we will write a line for it.
lRow = lRow + 1
ws2.Range("E" & lRow).Value = rs.Fields("Articlecode").Value
ws2.Range("F" & lRow).Value = Format(rs.Fields("Date").Value, "mm/dd/yyyy")
ws2.Range("G" & lRow).Value = Format(rs.Fields("Date").Value, "mm/dd/yyyy")
Else
'For other occurrences of this Articlecode we will just evaluate the dates.
If rs.Fields("Date").Value < ws2.Range("F" & lRow).Value Then
ws2.Range("F" & lRow).Value = Format(rs.Fields("Date").Value, "mm/dd/yyyy")
End If
If rs.Fields("Date").Value > ws2.Range("G" & lRow).Value Then
ws2.Range("G" & lRow).Value = Format(rs.Fields("Date").Value, "mm/dd/yyyy")
End If
End If
'Record the this articlecode
lLastCode = rs.Fields("Articlecode").Value
rs.MoveNext
Loop
End Sub
I have an excel sheet of which the data was jumbled: for example, the data that should have been in Columns AB and AC were instead in Columns B and C, but on the row after. I have the following written which moved the data from B and C to AB and AC respectively:
Dim rCell As Range
Dim rRng As Range
Set rRng = Sheet1.Range("A:A")
i = 1
lastRow = ActiveSheet.Cells(Rows.Count, "A").End(xlUp).Row
For Each rCell In rRng.Cells
If rCell.Value = "" Then
Range("AB" & i) = rCell.Offset(0, 1).Value
rCell.Offset(0, 1).ClearContents
End If
i = i + 1
If i = lastRow + 1 Then
Exit Sub
End If
Next rCell
End Sub
However, it doesn't fix the problem of the data being on the row BELOW the appropriate row now that they are in the right columns. I am new to VBA Macros so I would appreciate any help to make the data now align. I tried toggling the Offset parameter (-1,0) but it's not working.
Try something like this?
For i = Lastrow To 1 Step -1
' move data into cell AA from Cell A one row down
Cells(i, 27).Value = Cells(i + 1, 1).Value
Next
You don't need to loop through the range to accomplish what you're trying to do.
Try this instead:
Sub MoveBCtoAbAcUpOneRow()
Dim firstBRow As Integer
Dim lastBRow As Long
Dim firstCRow As Integer
Dim lastCRow As Long
' get the first row in both columns
If Range("B2").Value <> "" Then
firstBRow = 2
Else
firstBRow = Range("B1").End(xlDown).Row
End If
If Range("C2").Value <> "" Then
firstCRow = 2
Else
firstCRow = Range("C1").End(xlDown).Row
End If
' get the last row in both columns
lastBRow = Range("B" & ActiveSheet.Rows.Count).End(xlUp).Row
lastCRow = Range("C" & ActiveSheet.Rows.Count).End(xlUp).Row
' copy the data to the correct column, up one row
Range("B" & firstBRow & ":B" & lastBRow).Copy Range("AB" & firstBRow - 1)
Range("C" & firstCRow & ":C" & lastCRow).Copy Range("AC" & firstCRow - 1)
' clear the incorrect data
Range("B" & firstBRow & ":B" & lastBRow).ClearContents
Range("C" & firstCRow & ":C" & lastCRow).ClearContents
End Sub
Notes:
If the shape of data in each column is the same, you don't need to
find the first and last row for each. You'll only need one variable for each and one copy operation instead of 2.
Make sure you set variable declaration to required.
(Tools -> Options -> Require Variable Declaration) You may already be doing this, but I couldn't tell because it looks like the top of your Sub got truncated.