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
Related
I'm trying to copy some data from one Sheet to another using a vba script, it works fine but it doesn't appear to gather all the results, the data i have is split up over multiple tables so i assume it's seeing a blank space and stepping out but i'm not sure the solution! (the results i'm after are all letters i.e A-f and are all located on column C)
code below:
Sub copytoprint()
Dim LSearchRow As Integer
Dim LCopyToRow As Integer
Application.ScreenUpdating = False
On Error GoTo Err_Execute
LSearchRow = 2
LCopyToRow = 2
While Len(Range("C" & CStr(LSearchRow)).value) > 0
If InStr(1, Range("C" & CStr(LSearchRow)).value, "A") > 0 Then
Rows(CStr(LSearchRow) & ":" & CStr(LSearchRow)).Select
Selection.Copy
Sheets("dest").Select
Rows(CStr(LCopyToRow) & ":" & CStr(LCopyToRow)).Select
ActiveSheet.Paste
LCopyToRow = LCopyToRow + 1
Sheets("source").Select
End If
LSearchRow = LSearchRow + 1
Wend
Application.CutCopyMode = False
Range("A3").Select
MsgBox "All matching data has been copied."
Exit Sub
Err_Execute:
MsgBox "An error occurred."
End Sub
Input would just be a basic line of details i.e
ID person ref itemid itemname shape
Alphas1 bob A As01 Alphaselects1 circle
Alphas2 Stuart B As02 Alphaselects2 circle
Basically they are split up with many records I'd like it to grab all the A reference put them in one table then folow on with B C etc
Hope that makes a little sense?
Looks like you want to search from ActiveSheet using certain reference (A,B,C,,etc) and copy matching rows into unique destination sheets.
Below code will help you accomplish this, it separates the copying sub-procedure out into its own sub (called copyToSheet) and you can keep calling it from copytoprint() each time giving a reference and the destination sheet you desire.
Option Explicit
Private Sub copyToSheet(reference As String, shtSource As Worksheet, shtDest As Worksheet)
Dim x As Integer
Dim y As Integer
shtDest.Range("A2:Z" & shtDest.UsedRange.Rows.Count + 2).ClearContents
x = 2
y = 2
'loop until 20 consequtive rows have column C blank
While (Not shtSource.Range("C" & x).Value = "") _
And (Not shtSource.Range("C" & (x + 1)).Value = "") _
And (Not shtSource.Range("C" & (x + 5)).Value = "") _
And (Not shtSource.Range("C" & (x + 10)).Value = "") _
And (Not shtSource.Range("C" & (x + 20)).Value = "")
'If shtSource.Range("C" & x).Value, reference) > 0 Then
If shtSource.Range("C" & x).Value = reference Then
shtDest.Range("A" & y & ":Z" & y).Value = shtSource.Range("A" & x & ":Z" & x).Value
y = y + 1
End If
x = x + 1
Wend
End Sub
Public Sub copytoprint()
copyToSheet "A", ActiveSheet, Sheets("A")
copyToSheet "B", ActiveSheet, Sheets("B")
MsgBox "All matching data has been copied."
End Sub
So if I understood your problem correctly then you want to sort the data in sheet source first and then paste all of that data in another sheet.
If that's the case try this code.
Sub copytoprint()
Dim lastrow As Double
With Sheets("source")
lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
.Range("A2:F" & lastrow).Sort key1:=Range("C3:C" & lastrow), order1:=xlAscending, Header:=xlNo
End With
Sheets("dest").Range("A2:F" & lastrow).Value = Sheets("source").Range("A2:F" & lastrow).Value
End Sub
I have spent some trying getting my code to work and looking through various example but still cant get it to work properly.
I have a table where I want to loop through all rows and if "Pro" found in column B , copy the whole row and paste it once either underneath the row or at the very bottom(ideally) (Picture attached before and after the code)
I tried with the below code but all it does is finding the first instance of "Pro" in column B and copying same row until range 50 reaches:
sub Loop()
Dim i As Long
For i = 1 To 50
Range("B" & i).Select
If Range("B" & i).Value = "Pro" Then
Rows(i).EntireRow.Copy
Rows(i + 1).Insert Shift:=xlDown
End If
Next i
End Sub
I tried with (For i = 1 To Cells(Rows.Count, 1).End(xlUp).Row )
as well, defining last column but same thing happens(end up copying same row over and over again until the specified range finish).
If this will be too easy, I want for a copied row to have a value in Column A changed as well from as an exaple Req2 to Req2Pro
https://i.stack.imgur.com/AnWNH.jpg
Edit this line:
Rows(i).EntireRow.Copy
Rows(i + 1).Insert Shift:=xlDown
With:
Rows(i + 50).Value = Rows(i).Value
Range("A" & i + 50).value = Range("A" & i).value & "Pro"
This is it in the code:
Sub testloop()
Dim i As Long
Dim Find_last_row as long
Find_last_row = cells(rows.count,1).end(xlup).row
For i = 1 To Find_last_row
If Range("B" & i).Value = "Pro" Then
Rows(i + Find_last_row).Value = Rows(i).Value
Range("A" & i + Find_last_row).value = Range("A" & i).value & "Pro"
End If
Next i
End Sub
Run the for-next loop descending, e.g,
for row = 50 to 1 step -1
...
.cells(row+1,1)=.cells(row,1) & "Pro"
next
The issue is that it finds a "Pro" and inserts a copy one row down, then advances the row counter and finds the copy. By running from the bottom up instead, the rows created have already been passed
edited to add column A update
Sub Loop()
Dim i As Long
For i = 50 To 1 step -1
Range("B" & i).Select
If Range("B" & i).Value = "Pro" Then
Rows(i).EntireRow.Copy
Rows(i + 1).Insert Shift:=xlDown
Range("A" & i + 1).Value = Range("A" & i).Value & "Pro"
End If
Next i
End Sub
Edited to add the above
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 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.
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.